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