1 /* 2 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 #pragma ident "%Z%%M% %I% %E% SMI" 7 8 /* 9 * lib/krb5/kadm5/srv/chgpwd.c 10 * 11 * Copyright 1998 by the Massachusetts Institute of Technology. 12 * All Rights Reserved. 13 * 14 * Export of this software from the United States of America may 15 * require a specific license from the United States Government. 16 * It is the responsibility of any person or organization contemplating 17 * export to obtain such a license before exporting. 18 * 19 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 20 * distribute this software and its documentation for any purpose and 21 * without fee is hereby granted, provided that the above copyright 22 * notice appear in all copies and that both that copyright notice and 23 * this permission notice appear in supporting documentation, and that 24 * the name of M.I.T. not be used in advertising or publicity pertaining 25 * to distribution of the software without specific, written prior 26 * permission. Furthermore if you modify this software you must label 27 * your software as modified software and not distribute it in such a 28 * fashion that it might be confused with the original M.I.T. software. 29 * M.I.T. makes no representations about the suitability of 30 * this software for any purpose. It is provided "as is" without express 31 * or implied warranty. 32 * 33 */ 34 35 /* 36 * chgpwd.c - Handles changepw requests issued from non-Solaris krb5 clients. 37 */ 38 39 #include <libintl.h> 40 #include <locale.h> 41 #include <kadm5/admin.h> 42 #include <syslog.h> 43 #include <krb5/adm_proto.h> 44 45 #define MAXAPREQ 1500 46 47 static krb5_error_code 48 process_chpw_request(krb5_context context, void *server_handle, 49 char *realm, int s, krb5_keytab keytab, 50 struct sockaddr_in *sin, krb5_data *req, 51 krb5_data *rep) 52 { 53 krb5_error_code ret; 54 char *ptr; 55 int plen, vno; 56 krb5_address local_kaddr, remote_kaddr; 57 int allocated_mem = 0; 58 krb5_data ap_req, ap_rep; 59 krb5_auth_context auth_context; 60 krb5_principal changepw; 61 krb5_ticket *ticket; 62 krb5_data cipher, clear; 63 struct sockaddr local_addr, remote_addr; 64 int addrlen; 65 krb5_replay_data replay; 66 krb5_error krberror; 67 int numresult; 68 char strresult[1024]; 69 char *clientstr; 70 size_t clen; 71 char *cdots; 72 73 ret = 0; 74 rep->length = 0; 75 76 auth_context = NULL; 77 changepw = NULL; 78 ap_rep.length = 0; 79 ap_rep.data = NULL; 80 ticket = NULL; 81 clear.length = 0; 82 clear.data = NULL; 83 cipher.length = 0; 84 cipher.data = NULL; 85 86 if (req->length < 4) { 87 /* 88 * either this, or the server is printing bad messages, 89 * or the caller passed in garbage 90 */ 91 ret = KRB5KRB_AP_ERR_MODIFIED; 92 numresult = KRB5_KPASSWD_MALFORMED; 93 (void) strlcpy(strresult, "Request was truncated", 94 sizeof (strresult)); 95 goto chpwfail; 96 } 97 98 ptr = req->data; 99 100 /* 101 * Verify length 102 */ 103 plen = (*ptr++ & 0xff); 104 plen = (plen<<8) | (*ptr++ & 0xff); 105 106 if (plen != req->length) 107 return (KRB5KRB_AP_ERR_MODIFIED); 108 109 /* 110 * Verify version number 111 */ 112 vno = (*ptr++ & 0xff); 113 vno = (vno<<8) | (*ptr++ & 0xff); 114 115 if (vno != 1) { 116 ret = KRB5KDC_ERR_BAD_PVNO; 117 numresult = KRB5_KPASSWD_MALFORMED; 118 (void) snprintf(strresult, sizeof (strresult), 119 "Request contained unknown protocol version number %d", 120 vno); 121 goto chpwfail; 122 } 123 124 /* 125 * Read, check ap-req length 126 */ 127 ap_req.length = (*ptr++ & 0xff); 128 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff); 129 130 if (ptr + ap_req.length >= req->data + req->length) { 131 ret = KRB5KRB_AP_ERR_MODIFIED; 132 numresult = KRB5_KPASSWD_MALFORMED; 133 (void) strlcpy(strresult, "Request was truncated in AP-REQ", 134 sizeof (strresult)); 135 goto chpwfail; 136 } 137 138 /* 139 * Verify ap_req 140 */ 141 ap_req.data = ptr; 142 ptr += ap_req.length; 143 144 if (ret = krb5_auth_con_init(context, &auth_context)) { 145 krb5_klog_syslog(LOG_ERR, 146 gettext("Change password request failed. " 147 "Failed initializing auth context: %s"), 148 error_message(ret)); 149 numresult = KRB5_KPASSWD_HARDERROR; 150 (void) strlcpy(strresult, "Failed initializing auth context", 151 sizeof (strresult)); 152 goto chpwfail; 153 } 154 155 if (ret = krb5_auth_con_setflags(context, auth_context, 156 KRB5_AUTH_CONTEXT_DO_SEQUENCE)) { 157 krb5_klog_syslog(LOG_ERR, 158 gettext("Change password request failed. " 159 "Failed setting auth " 160 "context flags: %s"), 161 error_message(ret)); 162 numresult = KRB5_KPASSWD_HARDERROR; 163 (void) strlcpy(strresult, "Failed initializing auth context", 164 sizeof (strresult)); 165 goto chpwfail; 166 } 167 168 if (ret = krb5_build_principal(context, &changepw, strlen(realm), realm, 169 "kadmin", "changepw", NULL)) { 170 krb5_klog_syslog(LOG_ERR, 171 gettext("Change password request failed " 172 "Failed to build kadmin/changepw " 173 "principal: %s"), 174 error_message(ret)); 175 numresult = KRB5_KPASSWD_HARDERROR; 176 (void) strlcpy(strresult, 177 "Failed building kadmin/changepw principal", 178 sizeof (strresult)); 179 goto chpwfail; 180 } 181 182 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab, 183 NULL, &ticket); 184 185 if (ret) { 186 char kt_name[MAX_KEYTAB_NAME_LEN]; 187 if (krb5_kt_get_name(context, keytab, 188 kt_name, sizeof (kt_name))) 189 strncpy(kt_name, "default keytab", sizeof (kt_name)); 190 191 switch (ret) { 192 case KRB5_KT_NOTFOUND: 193 krb5_klog_syslog(LOG_ERR, 194 gettext("Change password request failed because " 195 "keytab entry \"kadmin/changepw\" " 196 "is missing from \"%s\""), 197 kt_name); 198 break; 199 case ENOENT: 200 krb5_klog_syslog(LOG_ERR, 201 gettext("Change password request failed because " 202 "keytab file \"%s\" does not exist"), 203 kt_name); 204 break; 205 default: 206 krb5_klog_syslog(LOG_ERR, 207 gettext("Change password request failed. " 208 "Failed to parse Kerberos AP_REQ message: %s"), 209 error_message(ret)); 210 } 211 212 numresult = KRB5_KPASSWD_AUTHERROR; 213 (void) strlcpy(strresult, "Failed reading application request", 214 sizeof (strresult)); 215 goto chpwfail; 216 } 217 218 /* 219 * Set up address info 220 */ 221 addrlen = sizeof (local_addr); 222 223 if (getsockname(s, &local_addr, &addrlen) < 0) { 224 ret = errno; 225 numresult = KRB5_KPASSWD_HARDERROR; 226 (void) strlcpy(strresult, 227 "Failed getting server internet address", 228 sizeof (strresult)); 229 goto chpwfail; 230 } 231 232 /* 233 * Some brain-dead OS's don't return useful information from 234 * the getsockname call. Namely, windows and solaris. 235 */ 236 if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0) { 237 local_kaddr.addrtype = ADDRTYPE_INET; 238 local_kaddr.length = sizeof (((struct sockaddr_in *) 239 &local_addr)->sin_addr); 240 /* CSTYLED */ 241 local_kaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&local_addr)->sin_addr); 242 } else { 243 krb5_address **addrs; 244 245 krb5_os_localaddr(context, &addrs); 246 247 local_kaddr.magic = addrs[0]->magic; 248 local_kaddr.addrtype = addrs[0]->addrtype; 249 local_kaddr.length = addrs[0]->length; 250 if ((local_kaddr.contents = malloc(addrs[0]->length)) == 0) { 251 ret = errno; 252 numresult = KRB5_KPASSWD_HARDERROR; 253 (void) strlcpy(strresult, 254 "Malloc failed for local_kaddr", 255 sizeof (strresult)); 256 goto chpwfail; 257 } 258 259 (void) memcpy(local_kaddr.contents, addrs[0]->contents, 260 addrs[0]->length); 261 allocated_mem++; 262 263 krb5_free_addresses(context, addrs); 264 } 265 266 addrlen = sizeof (remote_addr); 267 268 if (getpeername(s, &remote_addr, &addrlen) < 0) { 269 ret = errno; 270 numresult = KRB5_KPASSWD_HARDERROR; 271 (void) strlcpy(strresult, 272 "Failed getting client internet address", 273 sizeof (strresult)); 274 goto chpwfail; 275 } 276 277 remote_kaddr.addrtype = ADDRTYPE_INET; 278 remote_kaddr.length = sizeof (((struct sockaddr_in *) 279 &remote_addr)->sin_addr); 280 /* CSTYLED */ 281 remote_kaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&remote_addr)->sin_addr); 282 remote_kaddr.addrtype = ADDRTYPE_INET; 283 remote_kaddr.length = sizeof (sin->sin_addr); 284 remote_kaddr.contents = (krb5_octet *) &sin->sin_addr; 285 286 /* 287 * mk_priv requires that the local address be set. 288 * getsockname is used for this. rd_priv requires that the 289 * remote address be set. recvfrom is used for this. If 290 * rd_priv is given a local address, and the message has the 291 * recipient addr in it, this will be checked. However, there 292 * is simply no way to know ahead of time what address the 293 * message will be delivered *to*. Therefore, it is important 294 * that either no recipient address is in the messages when 295 * mk_priv is called, or that no local address is passed to 296 * rd_priv. Both is a better idea, and I have done that. In 297 * summary, when mk_priv is called, *only* a local address is 298 * specified. when rd_priv is called, *only* a remote address 299 * is specified. Are we having fun yet? 300 */ 301 if (ret = krb5_auth_con_setaddrs(context, auth_context, NULL, 302 &remote_kaddr)) { 303 numresult = KRB5_KPASSWD_HARDERROR; 304 (void) strlcpy(strresult, 305 "Failed storing client internet address", 306 sizeof (strresult)); 307 goto chpwfail; 308 } 309 310 /* 311 * Verify that this is an AS_REQ ticket 312 */ 313 if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) { 314 numresult = KRB5_KPASSWD_AUTHERROR; 315 (void) strlcpy(strresult, 316 "Ticket must be derived from a password", 317 sizeof (strresult)); 318 goto chpwfail; 319 } 320 321 /* 322 * Construct the ap-rep 323 */ 324 if (ret = krb5_mk_rep(context, auth_context, &ap_rep)) { 325 numresult = KRB5_KPASSWD_AUTHERROR; 326 (void) strlcpy(strresult, 327 "Failed replying to application request", 328 sizeof (strresult)); 329 goto chpwfail; 330 } 331 332 /* 333 * Decrypt the new password 334 */ 335 cipher.length = (req->data + req->length) - ptr; 336 cipher.data = ptr; 337 338 if (ret = krb5_rd_priv(context, auth_context, &cipher, 339 &clear, &replay)) { 340 numresult = KRB5_KPASSWD_HARDERROR; 341 (void) strlcpy(strresult, "Failed decrypting request", 342 sizeof (strresult)); 343 goto chpwfail; 344 } 345 346 ret = krb5_unparse_name(context, ticket->enc_part2->client, &clientstr); 347 if (ret) { 348 numresult = KRB5_KPASSWD_HARDERROR; 349 (void) strcpy(strresult, "Failed decrypting request"); 350 goto chpwfail; 351 } 352 353 /* 354 * Change the password 355 */ 356 if ((ptr = (char *)malloc(clear.length + 1)) == NULL) { 357 ret = errno; 358 numresult = KRB5_KPASSWD_HARDERROR; 359 (void) strlcpy(strresult, "Malloc failed for ptr", 360 sizeof (strresult)); 361 krb5_free_unparsed_name(context, clientstr); 362 goto chpwfail; 363 } 364 (void) memcpy(ptr, clear.data, clear.length); 365 ptr[clear.length] = '\0'; 366 367 ret = (kadm5_ret_t)kadm5_chpass_principal_util(server_handle, 368 ticket->enc_part2->client, 369 ptr, NULL, strresult, 370 sizeof (strresult)); 371 372 /* 373 * Zap the password 374 */ 375 (void) memset(clear.data, 0, clear.length); 376 (void) memset(ptr, 0, clear.length); 377 if (clear.data != NULL) { 378 krb5_xfree(clear.data); 379 clear.data = NULL; 380 } 381 free(ptr); 382 clear.length = 0; 383 384 clen = strlen(clientstr); 385 trunc_name(&clen, &cdots); 386 krb5_klog_syslog(LOG_NOTICE, "chpw request from %s for %.*s%s: %s", 387 inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr), 388 clen, clientstr, cdots, ret ? error_message(ret) : "success"); 389 krb5_free_unparsed_name(context, clientstr); 390 391 if (ret) { 392 if ((ret != KADM5_PASS_Q_TOOSHORT) && 393 (ret != KADM5_PASS_REUSE) && 394 (ret != KADM5_PASS_Q_CLASS) && 395 (ret != KADM5_PASS_Q_DICT) && 396 (ret != KADM5_PASS_TOOSOON)) 397 numresult = KRB5_KPASSWD_HARDERROR; 398 else 399 numresult = KRB5_KPASSWD_SOFTERROR; 400 /* 401 * strresult set by kadb5_chpass_principal_util() 402 */ 403 goto chpwfail; 404 } 405 406 /* 407 * Success! 408 */ 409 numresult = KRB5_KPASSWD_SUCCESS; 410 (void) strlcpy(strresult, "", sizeof (strresult)); 411 412 chpwfail: 413 414 clear.length = 2 + strlen(strresult); 415 if (clear.data != NULL) { 416 krb5_xfree(clear.data); 417 clear.data = NULL; 418 } 419 if ((clear.data = (char *)malloc(clear.length)) == NULL) { 420 ret = errno; 421 numresult = KRB5_KPASSWD_HARDERROR; 422 (void) strlcpy(strresult, "Malloc failed for clear.data", 423 sizeof (strresult)); 424 } 425 426 ptr = clear.data; 427 428 *ptr++ = (numresult>>8) & 0xff; 429 *ptr++ = numresult & 0xff; 430 431 (void) memcpy(ptr, strresult, strlen(strresult)); 432 433 cipher.length = 0; 434 435 if (ap_rep.length) { 436 if (ret = krb5_auth_con_setaddrs(context, auth_context, 437 &local_kaddr, NULL)) { 438 numresult = KRB5_KPASSWD_HARDERROR; 439 (void) strlcpy(strresult, 440 "Failed storing client and server internet addresses", 441 sizeof (strresult)); 442 } else { 443 if (ret = krb5_mk_priv(context, auth_context, &clear, 444 &cipher, &replay)) { 445 numresult = KRB5_KPASSWD_HARDERROR; 446 (void) strlcpy(strresult, 447 "Failed encrypting reply", 448 sizeof (strresult)); 449 } 450 } 451 } 452 453 /* 454 * If no KRB-PRIV was constructed, then we need a KRB-ERROR. 455 * If this fails, just bail. There's nothing else we can do. 456 */ 457 if (cipher.length == 0) { 458 /* 459 * Clear out ap_rep now, so that it won't be inserted 460 * in the reply 461 */ 462 if (ap_rep.length) { 463 if (ap_rep.data != NULL) 464 krb5_xfree(ap_rep.data); 465 ap_rep.data = NULL; 466 ap_rep.length = 0; 467 } 468 469 krberror.ctime = 0; 470 krberror.cusec = 0; 471 krberror.susec = 0; 472 if (ret = krb5_timeofday(context, &krberror.stime)) 473 goto bailout; 474 475 /* 476 * This is really icky. but it's what all the other callers 477 * to mk_error do. 478 */ 479 krberror.error = ret; 480 krberror.error -= ERROR_TABLE_BASE_krb5; 481 if (krberror.error < 0 || krberror.error > 128) 482 krberror.error = KRB_ERR_GENERIC; 483 484 krberror.client = NULL; 485 if (ret = krb5_build_principal(context, &krberror.server, 486 strlen(realm), realm, 487 "kadmin", "changepw", NULL)) { 488 goto bailout; 489 } 490 491 krberror.text.length = 0; 492 krberror.e_data = clear; 493 494 ret = krb5_mk_error(context, &krberror, &cipher); 495 496 krb5_free_principal(context, krberror.server); 497 498 if (ret) 499 goto bailout; 500 } 501 502 /* 503 * Construct the reply 504 */ 505 rep->length = 6 + ap_rep.length + cipher.length; 506 if ((rep->data = (char *)malloc(rep->length)) == NULL) { 507 ret = errno; 508 goto bailout; 509 } 510 ptr = rep->data; 511 512 /* 513 * Length 514 */ 515 *ptr++ = (rep->length>>8) & 0xff; 516 *ptr++ = rep->length & 0xff; 517 518 /* 519 * Version == 0x0001 big-endian 520 */ 521 *ptr++ = 0; 522 *ptr++ = 1; 523 524 /* 525 * ap_rep length, big-endian 526 */ 527 *ptr++ = (ap_rep.length>>8) & 0xff; 528 *ptr++ = ap_rep.length & 0xff; 529 530 /* 531 * ap-rep data 532 */ 533 if (ap_rep.length) { 534 (void) memcpy(ptr, ap_rep.data, ap_rep.length); 535 ptr += ap_rep.length; 536 } 537 538 /* 539 * krb-priv or krb-error 540 */ 541 (void) memcpy(ptr, cipher.data, cipher.length); 542 543 bailout: 544 if (auth_context) 545 krb5_auth_con_free(context, auth_context); 546 if (changepw) 547 krb5_free_principal(context, changepw); 548 if (ap_rep.data != NULL) 549 krb5_xfree(ap_rep.data); 550 if (ticket) 551 krb5_free_ticket(context, ticket); 552 if (clear.data != NULL) 553 krb5_xfree(clear.data); 554 if (cipher.data != NULL) 555 krb5_xfree(cipher.data); 556 if (allocated_mem) 557 krb5_xfree(local_kaddr.contents); 558 559 return (ret); 560 } 561 562 563 /* 564 * This routine is used to handle password-change requests received 565 * on kpasswd-port 464 from MIT/M$ clients. 566 */ 567 void 568 handle_chpw(krb5_context context, int s1, 569 void *serverhandle, kadm5_config_params *params) 570 { 571 krb5_error_code ret; 572 char req[MAXAPREQ]; 573 int len; 574 struct sockaddr_in from; 575 int fromlen; 576 krb5_keytab kt; 577 krb5_data reqdata, repdata; 578 int s2 = -1; 579 580 reqdata.length = 0; 581 reqdata.data = NULL; 582 repdata.length = 0; 583 repdata.data = NULL; 584 585 fromlen = sizeof (from); 586 587 if ((len = recvfrom(s1, req, sizeof (req), 0, (struct sockaddr *)&from, 588 &fromlen)) < 0) { 589 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't receive " 590 "request: %s"), error_message(errno)); 591 return; 592 } 593 594 if ((ret = krb5_kt_resolve(context, params->admin_keytab, &kt))) { 595 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't open " 596 "admin keytab %s"), error_message(ret)); 597 return; 598 } 599 600 reqdata.length = len; 601 reqdata.data = req; 602 603 /* 604 * This is really obscure. s1 is used for all communications. it 605 * is left unconnected in case the server is multihomed and routes 606 * are asymmetric. s2 is connected to resolve routes and get 607 * addresses. this is the *only* way to get proper addresses for 608 * multihomed hosts if routing is asymmetric. 609 * 610 * A related problem in the server, but not the client, is that 611 * many os's have no way to disconnect a connected udp socket, so 612 * the s2 socket needs to be closed and recreated for each 613 * request. The s1 socket must not be closed, or else queued 614 * requests will be lost. 615 * 616 * A "naive" client implementation (one socket, no connect, 617 * hostname resolution to get the local ip addr) will work and 618 * interoperate if the client is single-homed. 619 */ 620 621 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 622 krb5_klog_syslog(LOG_ERR, gettext("chpw: Cannot create " 623 "connecting socket: %s"), error_message(errno)); 624 goto cleanup; 625 } 626 627 if (connect(s2, (struct sockaddr *)&from, sizeof (from)) < 0) { 628 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't connect " 629 "to client: %s"), error_message(errno)); 630 if (s2 > 0) 631 (void) close(s2); 632 goto cleanup; 633 } 634 635 if ((ret = process_chpw_request(context, serverhandle, 636 params->realm, s2, kt, &from, 637 &reqdata, &repdata))) { 638 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error processing " 639 "request: %s"), error_message(ret)); 640 } 641 642 if (s2 > 0) 643 (void) close(s2); 644 645 if (repdata.length == 0 || repdata.data == NULL) { 646 /* 647 * Just return. This means something really bad happened 648 */ 649 goto cleanup; 650 } 651 652 len = sendto(s1, repdata.data, repdata.length, 0, 653 (struct sockaddr *)&from, sizeof (from)); 654 655 if (len < repdata.length) { 656 krb5_xfree(repdata.data); 657 658 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error sending reply:" 659 " %s"), error_message(errno)); 660 goto cleanup; 661 } 662 663 if (repdata.data != NULL) 664 krb5_xfree(repdata.data); 665 cleanup: 666 krb5_kt_close(context, kt); 667 } 668