1 /* 2 * Copyright 2006 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 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 krb5_klog_syslog(LOG_NOTICE, "chpw request from %s for %s: %s", 383 inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr), 384 clientstr, ret ? error_message(ret) : "success"); 385 krb5_free_unparsed_name(context, clientstr); 386 387 if (ret) { 388 if ((ret != KADM5_PASS_Q_TOOSHORT) && 389 (ret != KADM5_PASS_REUSE) && 390 (ret != KADM5_PASS_Q_CLASS) && 391 (ret != KADM5_PASS_Q_DICT) && 392 (ret != KADM5_PASS_TOOSOON)) 393 numresult = KRB5_KPASSWD_HARDERROR; 394 else 395 numresult = KRB5_KPASSWD_SOFTERROR; 396 /* 397 * strresult set by kadb5_chpass_principal_util() 398 */ 399 goto chpwfail; 400 } 401 402 /* 403 * Success! 404 */ 405 numresult = KRB5_KPASSWD_SUCCESS; 406 (void) strlcpy(strresult, "", sizeof (strresult)); 407 408 chpwfail: 409 410 clear.length = 2 + strlen(strresult); 411 if (clear.data != NULL) { 412 krb5_xfree(clear.data); 413 clear.data = NULL; 414 } 415 if ((clear.data = (char *)malloc(clear.length)) == NULL) { 416 ret = errno; 417 numresult = KRB5_KPASSWD_HARDERROR; 418 (void) strlcpy(strresult, "Malloc failed for clear.data", 419 sizeof (strresult)); 420 } 421 422 ptr = clear.data; 423 424 *ptr++ = (numresult>>8) & 0xff; 425 *ptr++ = numresult & 0xff; 426 427 (void) memcpy(ptr, strresult, strlen(strresult)); 428 429 cipher.length = 0; 430 431 if (ap_rep.length) { 432 if (ret = krb5_auth_con_setaddrs(context, auth_context, 433 &local_kaddr, NULL)) { 434 numresult = KRB5_KPASSWD_HARDERROR; 435 (void) strlcpy(strresult, 436 "Failed storing client and server internet addresses", 437 sizeof (strresult)); 438 } else { 439 if (ret = krb5_mk_priv(context, auth_context, &clear, 440 &cipher, &replay)) { 441 numresult = KRB5_KPASSWD_HARDERROR; 442 (void) strlcpy(strresult, 443 "Failed encrypting reply", 444 sizeof (strresult)); 445 } 446 } 447 } 448 449 /* 450 * If no KRB-PRIV was constructed, then we need a KRB-ERROR. 451 * If this fails, just bail. There's nothing else we can do. 452 */ 453 if (cipher.length == 0) { 454 /* 455 * Clear out ap_rep now, so that it won't be inserted 456 * in the reply 457 */ 458 if (ap_rep.length) { 459 if (ap_rep.data != NULL) 460 krb5_xfree(ap_rep.data); 461 ap_rep.data = NULL; 462 ap_rep.length = 0; 463 } 464 465 krberror.ctime = 0; 466 krberror.cusec = 0; 467 krberror.susec = 0; 468 if (ret = krb5_timeofday(context, &krberror.stime)) 469 goto bailout; 470 471 /* 472 * This is really icky. but it's what all the other callers 473 * to mk_error do. 474 */ 475 krberror.error = ret; 476 krberror.error -= ERROR_TABLE_BASE_krb5; 477 if (krberror.error < 0 || krberror.error > 128) 478 krberror.error = KRB_ERR_GENERIC; 479 480 krberror.client = NULL; 481 if (ret = krb5_build_principal(context, &krberror.server, 482 strlen(realm), realm, 483 "kadmin", "changepw", NULL)) { 484 goto bailout; 485 } 486 487 krberror.text.length = 0; 488 krberror.e_data = clear; 489 490 ret = krb5_mk_error(context, &krberror, &cipher); 491 492 krb5_free_principal(context, krberror.server); 493 494 if (ret) 495 goto bailout; 496 } 497 498 /* 499 * Construct the reply 500 */ 501 rep->length = 6 + ap_rep.length + cipher.length; 502 if ((rep->data = (char *)malloc(rep->length)) == NULL) { 503 ret = errno; 504 goto bailout; 505 } 506 ptr = rep->data; 507 508 /* 509 * Length 510 */ 511 *ptr++ = (rep->length>>8) & 0xff; 512 *ptr++ = rep->length & 0xff; 513 514 /* 515 * Version == 0x0001 big-endian 516 */ 517 *ptr++ = 0; 518 *ptr++ = 1; 519 520 /* 521 * ap_rep length, big-endian 522 */ 523 *ptr++ = (ap_rep.length>>8) & 0xff; 524 *ptr++ = ap_rep.length & 0xff; 525 526 /* 527 * ap-rep data 528 */ 529 if (ap_rep.length) { 530 (void) memcpy(ptr, ap_rep.data, ap_rep.length); 531 ptr += ap_rep.length; 532 } 533 534 /* 535 * krb-priv or krb-error 536 */ 537 (void) memcpy(ptr, cipher.data, cipher.length); 538 539 bailout: 540 if (auth_context) 541 krb5_auth_con_free(context, auth_context); 542 if (changepw) 543 krb5_free_principal(context, changepw); 544 if (ap_rep.data != NULL) 545 krb5_xfree(ap_rep.data); 546 if (ticket) 547 krb5_free_ticket(context, ticket); 548 if (clear.data != NULL) 549 krb5_xfree(clear.data); 550 if (cipher.data != NULL) 551 krb5_xfree(cipher.data); 552 if (allocated_mem) 553 krb5_xfree(local_kaddr.contents); 554 555 return (ret); 556 } 557 558 559 /* 560 * This routine is used to handle password-change requests received 561 * on kpasswd-port 464 from MIT/M$ clients. 562 */ 563 void 564 handle_chpw(krb5_context context, int s1, 565 void *serverhandle, kadm5_config_params *params) 566 { 567 krb5_error_code ret; 568 char req[MAXAPREQ]; 569 int len; 570 struct sockaddr_in from; 571 int fromlen; 572 krb5_keytab kt; 573 krb5_data reqdata, repdata; 574 int s2 = -1; 575 576 reqdata.length = 0; 577 reqdata.data = NULL; 578 repdata.length = 0; 579 repdata.data = NULL; 580 581 fromlen = sizeof (from); 582 583 if ((len = recvfrom(s1, req, sizeof (req), 0, (struct sockaddr *)&from, 584 &fromlen)) < 0) { 585 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't receive " 586 "request: %s"), error_message(errno)); 587 return; 588 } 589 590 if ((ret = krb5_kt_resolve(context, params->admin_keytab, &kt))) { 591 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't open " 592 "admin keytab %s"), error_message(ret)); 593 return; 594 } 595 596 reqdata.length = len; 597 reqdata.data = req; 598 599 /* 600 * This is really obscure. s1 is used for all communications. it 601 * is left unconnected in case the server is multihomed and routes 602 * are asymmetric. s2 is connected to resolve routes and get 603 * addresses. this is the *only* way to get proper addresses for 604 * multihomed hosts if routing is asymmetric. 605 * 606 * A related problem in the server, but not the client, is that 607 * many os's have no way to disconnect a connected udp socket, so 608 * the s2 socket needs to be closed and recreated for each 609 * request. The s1 socket must not be closed, or else queued 610 * requests will be lost. 611 * 612 * A "naive" client implementation (one socket, no connect, 613 * hostname resolution to get the local ip addr) will work and 614 * interoperate if the client is single-homed. 615 */ 616 617 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 618 krb5_klog_syslog(LOG_ERR, gettext("chpw: Cannot create " 619 "connecting socket: %s"), error_message(errno)); 620 goto cleanup; 621 } 622 623 if (connect(s2, (struct sockaddr *)&from, sizeof (from)) < 0) { 624 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't connect " 625 "to client: %s"), error_message(errno)); 626 if (s2 > 0) 627 (void) close(s2); 628 goto cleanup; 629 } 630 631 if ((ret = process_chpw_request(context, serverhandle, 632 params->realm, s2, kt, &from, 633 &reqdata, &repdata))) { 634 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error processing " 635 "request: %s"), error_message(ret)); 636 } 637 638 if (s2 > 0) 639 (void) close(s2); 640 641 if (repdata.length == 0 || repdata.data == NULL) { 642 /* 643 * Just return. This means something really bad happened 644 */ 645 goto cleanup; 646 } 647 648 len = sendto(s1, repdata.data, repdata.length, 0, 649 (struct sockaddr *)&from, sizeof (from)); 650 651 if (len < repdata.length) { 652 krb5_xfree(repdata.data); 653 654 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error sending reply:" 655 " %s"), error_message(errno)); 656 goto cleanup; 657 } 658 659 if (repdata.data != NULL) 660 krb5_xfree(repdata.data); 661 cleanup: 662 krb5_kt_close(context, kt); 663 } 664