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