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