1/* 2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include "krb5_locl.h" 35 36struct request { 37 krb5_auth_context ac; 38 krb5_creds *creds; 39 krb5_principal target; 40 const char *password; 41}; 42 43/* 44 * Change password protocol defined by 45 * draft-ietf-cat-kerb-chg-password-02.txt 46 * 47 * Share the response part of the protocol with MS set password 48 * (RFC3244) 49 */ 50 51static krb5_error_code 52chgpw_prexmit(krb5_context context, int proto, 53 void *ctx, rk_socket_t fd, krb5_data *data) 54{ 55 struct request *request = ctx; 56 krb5_data ap_req_data, krb_priv_data, passwd_data; 57 krb5_storage *sp = NULL; 58 krb5_error_code ret; 59 krb5_ssize_t slen; 60 size_t len; 61 62 krb5_data_zero(&ap_req_data); 63 krb5_data_zero(&krb_priv_data); 64 65 ret = krb5_auth_con_genaddrs(context, request->ac, fd, 66 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR); 67 if (ret) 68 goto out; 69 70 ret = krb5_mk_req_extended(context, 71 &request->ac, 72 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY, 73 NULL, 74 request->creds, 75 &ap_req_data); 76 if (ret) 77 goto out; 78 79 passwd_data.data = rk_UNCONST(request->password); 80 passwd_data.length = strlen(request->password); 81 82 ret = krb5_mk_priv(context, 83 request->ac, 84 &passwd_data, 85 &krb_priv_data, 86 NULL); 87 if (ret) 88 goto out; 89 90 sp = krb5_storage_emem(); 91 if (sp == NULL) { 92 ret = ENOMEM; 93 goto out; 94 } 95 96 len = 6 + ap_req_data.length + krb_priv_data.length; 97 98 ret = krb5_store_uint16(sp, len); 99 if (ret) goto out; 100 ret = krb5_store_uint16(sp, 1); 101 if (ret) goto out; 102 ret = krb5_store_uint16(sp, ap_req_data.length); 103 if (ret) goto out; 104 slen = krb5_storage_write(sp, ap_req_data.data, ap_req_data.length); 105 if (slen != ap_req_data.length) { 106 ret = EINVAL; 107 goto out; 108 } 109 slen = krb5_storage_write(sp, krb_priv_data.data, krb_priv_data.length); 110 if (slen != krb_priv_data.length) { 111 ret = EINVAL; 112 goto out; 113 } 114 115 ret = krb5_storage_to_data(sp, data); 116 117 out: 118 if (ret) 119 _krb5_debugx(context, 10, "chgpw_prexmit failed with: %d", ret); 120 if (sp) 121 krb5_storage_free(sp); 122 krb5_data_free(&krb_priv_data); 123 krb5_data_free(&ap_req_data); 124 return ret; 125} 126 127/* 128 * Set password protocol as defined by RFC3244 -- 129 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols 130 */ 131 132static krb5_error_code 133setpw_prexmit(krb5_context context, int proto, 134 void *ctx, int fd, krb5_data *data) 135{ 136 struct request *request = ctx; 137 krb5_data ap_req_data, krb_priv_data, pwd_data; 138 krb5_error_code ret; 139 ChangePasswdDataMS chpw; 140 krb5_storage *sp = NULL; 141 ssize_t slen; 142 size_t len; 143 144 krb5_data_zero(&ap_req_data); 145 krb5_data_zero(&krb_priv_data); 146 krb5_data_zero(&pwd_data); 147 148 ret = krb5_auth_con_genaddrs(context, request->ac, fd, 149 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR); 150 if (ret) 151 goto out; 152 153 ret = krb5_mk_req_extended(context, 154 &request->ac, 155 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY, 156 NULL, 157 request->creds, 158 &ap_req_data); 159 if (ret) 160 goto out; 161 162 chpw.newpasswd.length = strlen(request->password); 163 chpw.newpasswd.data = rk_UNCONST(request->password); 164 if (request->target) { 165 chpw.targname = &request->target->name; 166 chpw.targrealm = &request->target->realm; 167 } else { 168 chpw.targname = NULL; 169 chpw.targrealm = NULL; 170 } 171 172 ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length, 173 &chpw, &len, ret); 174 if (ret) 175 goto out; 176 if(pwd_data.length != len) 177 krb5_abortx(context, "internal error in ASN.1 encoder"); 178 179 ret = krb5_mk_priv (context, 180 request->ac, 181 &pwd_data, 182 &krb_priv_data, 183 NULL); 184 if (ret) 185 goto out; 186 187 sp = krb5_storage_emem(); 188 if (sp == NULL) { 189 ret = ENOMEM; 190 goto out; 191 } 192 193 len = 6 + ap_req_data.length + krb_priv_data.length; 194 195 ret = krb5_store_uint16(sp, len); 196 if (ret) goto out; 197 ret = krb5_store_uint16(sp, 0xff80); 198 if (ret) goto out; 199 ret = krb5_store_uint16(sp, ap_req_data.length); 200 if (ret) goto out; 201 slen = krb5_storage_write(sp, ap_req_data.data, ap_req_data.length); 202 if (slen != ap_req_data.length) { 203 ret = EINVAL; 204 goto out; 205 } 206 slen = krb5_storage_write(sp, krb_priv_data.data, krb_priv_data.length); 207 if (slen != krb_priv_data.length) { 208 ret = EINVAL; 209 goto out; 210 } 211 212 ret = krb5_storage_to_data(sp, data); 213 214 out: 215 if (ret) 216 _krb5_debugx(context, 10, "setpw_prexmit failed with %d", ret); 217 if (sp) 218 krb5_storage_free(sp); 219 krb5_data_free(&krb_priv_data); 220 krb5_data_free(&ap_req_data); 221 krb5_data_free(&pwd_data); 222 223 return ret; 224} 225 226static krb5_error_code 227process_reply(krb5_context context, 228 krb5_auth_context auth_context, 229 krb5_data *data, 230 int *result_code, 231 krb5_data *result_code_string, 232 krb5_data *result_string) 233{ 234 krb5_error_code ret; 235 ssize_t len; 236 uint16_t pkt_len, pkt_ver; 237 krb5_data ap_rep_data; 238 uint8_t *reply; 239 240 krb5_auth_con_clear(context, auth_context, 241 KRB5_AUTH_CONTEXT_CLEAR_LOCAL_ADDR|KRB5_AUTH_CONTEXT_CLEAR_REMOTE_ADDR); 242 len = data->length; 243 reply = data->data;; 244 245 if (len < 6) { 246 krb5_data_format(result_string, "server sent to too short message " 247 "(%ld bytes)", (long)len); 248 *result_code = KRB5_KPASSWD_MALFORMED; 249 return 0; 250 } 251 252 pkt_len = (reply[0] << 8) | (reply[1]); 253 pkt_ver = (reply[2] << 8) | (reply[3]); 254 255 if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) { 256 KRB_ERROR error; 257 size_t size; 258 u_char *p; 259 260 memset(&error, 0, sizeof(error)); 261 262 ret = decode_KRB_ERROR(reply, len, &error, &size); 263 if (ret) 264 return ret; 265 266 if (error.e_data->length < 2) { 267 krb5_data_format(result_string, "server sent too short " 268 "e_data to print anything usable"); 269 free_KRB_ERROR(&error); 270 *result_code = KRB5_KPASSWD_MALFORMED; 271 return 0; 272 } 273 274 p = error.e_data->data; 275 *result_code = (p[0] << 8) | p[1]; 276 if (error.e_data->length == 2) 277 krb5_data_format(result_string, "server only sent error code"); 278 else 279 krb5_data_copy (result_string, 280 p + 2, 281 error.e_data->length - 2); 282 free_KRB_ERROR(&error); 283 return 0; 284 } 285 286 if (pkt_len != len) { 287 krb5_data_format(result_string, "client: wrong len in reply"); 288 *result_code = KRB5_KPASSWD_MALFORMED; 289 return 0; 290 } 291 if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) { 292 krb5_data_format(result_string, 293 "client: wrong version number (%d)", pkt_ver); 294 *result_code = KRB5_KPASSWD_MALFORMED; 295 return 0; 296 } 297 298 ap_rep_data.data = reply + 6; 299 ap_rep_data.length = (reply[4] << 8) | (reply[5]); 300 301 if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) { 302 krb5_data_format(result_string, "client: wrong AP len in reply"); 303 *result_code = KRB5_KPASSWD_MALFORMED; 304 return 0; 305 } 306 307 if (ap_rep_data.length) { 308 krb5_ap_rep_enc_part *ap_rep; 309 krb5_data priv_data; 310 u_char *p; 311 312 priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length; 313 priv_data.length = len - ap_rep_data.length - 6; 314 315 ret = krb5_rd_rep (context, 316 auth_context, 317 &ap_rep_data, 318 &ap_rep); 319 if (ret) 320 return ret; 321 322 krb5_free_ap_rep_enc_part (context, ap_rep); 323 324 ret = krb5_rd_priv (context, 325 auth_context, 326 &priv_data, 327 result_code_string, 328 NULL); 329 if (ret) { 330 krb5_data_free (result_code_string); 331 return ret; 332 } 333 334 if (result_code_string->length < 2) { 335 *result_code = KRB5_KPASSWD_MALFORMED; 336 krb5_data_format(result_string, 337 "client: bad length in result"); 338 return 0; 339 } 340 341 p = result_code_string->data; 342 343 *result_code = (p[0] << 8) | p[1]; 344 krb5_data_copy (result_string, 345 (unsigned char*)result_code_string->data + 2, 346 result_code_string->length - 2); 347 return 0; 348 } else { 349 KRB_ERROR error; 350 size_t size; 351 u_char *p; 352 353 ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size); 354 if (ret) { 355 return ret; 356 } 357 if (error.e_data->length < 2) { 358 krb5_warnx (context, "too short e_data to print anything usable"); 359 return 1; /* XXX */ 360 } 361 362 p = error.e_data->data; 363 *result_code = (p[0] << 8) | p[1]; 364 krb5_data_copy (result_string, 365 p + 2, 366 error.e_data->length - 2); 367 return 0; 368 } 369} 370 371 372/* 373 * change the password using the credentials in `creds' (for the 374 * principal indicated in them) to `newpw', storing the result of 375 * the operation in `result_*' and an error code or 0. 376 */ 377 378typedef krb5_error_code (*kpwd_process_reply) (krb5_context, 379 krb5_auth_context, 380 krb5_data *data, 381 int *, 382 krb5_data *, 383 krb5_data *); 384 385static struct kpwd_proc { 386 const char *name; 387 int flags; 388#define SUPPORT_TCP 1 389#define SUPPORT_UDP 2 390#define SUPPORT_ADMIN 4 391 krb5_sendto_prexmit prexmit; 392 kpwd_process_reply process_rep; 393} procs[] = { 394 { 395 "MS set password", 396 SUPPORT_TCP|SUPPORT_UDP|SUPPORT_ADMIN, 397 setpw_prexmit, 398 process_reply 399 }, 400 { 401 "change password", 402 SUPPORT_UDP, 403 chgpw_prexmit, 404 process_reply 405 }, 406 { NULL, 0, NULL, NULL } 407}; 408 409/* 410 * 411 */ 412 413static krb5_error_code 414change_password_loop(krb5_context context, 415 struct request *request, 416 int *result_code, 417 krb5_data *result_code_string, 418 krb5_data *result_string, 419 struct kpwd_proc *proc) 420{ 421 krb5_error_code ret; 422 krb5_data zero, zero2; 423 krb5_sendto_ctx ctx = NULL; 424 krb5_realm realm; 425 426 krb5_data_zero(&zero); 427 krb5_data_zero(&zero2); 428 429 if (request->target) 430 realm = request->target->realm; 431 else 432 realm = request->creds->client->realm; 433 434 _krb5_debugx(context, 1, "trying to set password using: %s in realm %s", 435 proc->name, realm); 436 437 ret = krb5_auth_con_init(context, &request->ac); 438 if (ret) 439 goto out; 440 441 krb5_auth_con_setflags(context, request->ac, KRB5_AUTH_CONTEXT_DO_SEQUENCE); 442 443 ret = krb5_sendto_ctx_alloc(context, &ctx); 444 if (ret) 445 goto out; 446 447 krb5_sendto_ctx_set_type(ctx, KRB5_KRBHST_CHANGEPW); 448 449 /* XXX this is a evil hack */ 450 if (request->creds->ticket.length > 700) { 451 _krb5_debugx(context, 1, "using TCP since the ticket is large: %lu", 452 (unsigned long)request->creds->ticket.length); 453 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG); 454 } 455 456 _krb5_sendto_ctx_set_prexmit(ctx, proc->prexmit, request); 457 458 ret = krb5_sendto_context(context, ctx, &zero, realm, &zero2); 459 460 if (ret == 0) 461 ret = proc->process_rep(context, request->ac, &zero2, 462 result_code, result_code_string, result_string); 463 464 out: 465 _krb5_debugx(context, 1, "set password using %s returned: %d result_code %d", 466 proc->name, ret, *result_code); 467 468 krb5_auth_con_free(context, request->ac); 469 if (ctx) 470 krb5_sendto_ctx_free(context, ctx); 471 472 krb5_data_free(&zero2); 473 474 if (ret == KRB5_KDC_UNREACH) { 475 krb5_set_error_message(context, 476 ret, 477 N_("Unable to reach any changepw server " 478 " in realm %s", "realm"), realm); 479 *result_code = KRB5_KPASSWD_HARDERROR; 480 } 481 return ret; 482} 483 484#ifndef HEIMDAL_SMALLER 485 486static struct kpwd_proc * 487find_chpw_proto(const char *name) 488{ 489 struct kpwd_proc *p; 490 for (p = procs; p->name != NULL; p++) { 491 if (strcmp(p->name, name) == 0) 492 return p; 493 } 494 return NULL; 495} 496 497/** 498 * Deprecated: krb5_change_password() is deprecated, use krb5_set_password(). 499 * 500 * @param context a Keberos context 501 * @param creds 502 * @param newpw 503 * @param result_code 504 * @param result_code_string 505 * @param result_string 506 * 507 * @return On sucess password is changed. 508 509 * @ingroup @krb5_deprecated 510 */ 511 512KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 513krb5_change_password (krb5_context context, 514 krb5_creds *creds, 515 const char *newpw, 516 int *result_code, 517 krb5_data *result_code_string, 518 krb5_data *result_string) 519 KRB5_DEPRECATED_FUNCTION("Use X instead") 520{ 521 struct kpwd_proc *p = find_chpw_proto("change password"); 522 struct request request; 523 524 *result_code = KRB5_KPASSWD_MALFORMED; 525 result_code_string->data = result_string->data = NULL; 526 result_code_string->length = result_string->length = 0; 527 528 if (p == NULL) 529 return KRB5_KPASSWD_MALFORMED; 530 531 request.ac = NULL; 532 request.target = targprinc; 533 request.creds = creds; 534 request.password = password; 535 536 return change_password_loop(context, &request, 537 result_code, result_code_string, 538 result_string, p); 539} 540#endif /* HEIMDAL_SMALLER */ 541 542/** 543 * Change password using creds. 544 * 545 * @param context a Keberos context 546 * @param creds The initial kadmin/passwd for the principal or an admin principal 547 * @param newpw The new password to set 548 * @param targprinc For most callers should pass NULL in this 549 * argument. Targprinc is the principal to change the password 550 * for. This argument should only be set when you want to 551 * change another Kerberos principal's password, ie you are an 552 * admin. If NULL, the default authenticating principal in the 553 * creds argument is used instead. 554 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed. 555 * @param result_code_string binary message from the server, contains 556 * at least the result_code. 557 * @param result_string A message from the kpasswd service or the 558 * library in human printable form. The string is NUL terminated. 559 * 560 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed. 561 562 * @ingroup @krb5 563 */ 564 565KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 566krb5_set_password(krb5_context context, 567 krb5_creds *creds, 568 const char *newpw, 569 krb5_principal targprinc, 570 int *result_code, 571 krb5_data *result_code_string, 572 krb5_data *result_string) 573{ 574 krb5_error_code ret = 0; 575 struct request request; 576 int i; 577 578 *result_code = KRB5_KPASSWD_MALFORMED; 579 krb5_data_zero(result_code_string); 580 krb5_data_zero(result_string); 581 582 _krb5_debugx(context, 1, "trying to set password"); 583 584 request.ac = NULL; 585 request.target = targprinc; 586 request.creds = creds; 587 request.password = newpw; 588 589 for (i = 0; procs[i].name != NULL; i++) { 590 /* don't try methods that don't support admind change password */ 591 if (targprinc && (procs[i].flags & SUPPORT_ADMIN) == 0) 592 continue; 593 594 ret = change_password_loop(context, &request, 595 result_code, result_code_string, 596 result_string, 597 &procs[i]); 598 if (ret == 0 && *result_code == 0) 599 break; 600 } 601 602 return ret; 603} 604 605/** 606 * Change password using a credential cache that contains an initial 607 * credential or an admin credential. 608 * 609 * @param context a Keberos context 610 * @param ccache the credential cache to use to find the 611 * kadmin/changepw principal. 612 * @param newpw The new password to set 613 * @param targprinc For most callers should pass NULL in this 614 * argument. Targprinc is the principal to change the password 615 * for. This argument should only be set when you want to 616 * change another Kerberos principal's password, ie you are an 617 * admin. If NULL, the default authenticating principal in the 618 * creds argument is used instead. 619 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed. 620 * @param result_code_string binary message from the server, contains 621 * at least the result_code. 622 * @param result_string A message from the kpasswd service or the 623 * library in human printable form. The string is NUL terminated. 624 * 625 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed. 626 * 627 */ 628 629KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 630krb5_set_password_using_ccache(krb5_context context, 631 krb5_ccache ccache, 632 const char *newpw, 633 krb5_principal targprinc, 634 int *result_code, 635 krb5_data *result_code_string, 636 krb5_data *result_string) 637{ 638 krb5_creds creds, *credsp = NULL; 639 krb5_error_code ret; 640 krb5_principal principal = NULL; 641 642 *result_code = KRB5_KPASSWD_MALFORMED; 643 krb5_data_zero(result_code_string); 644 krb5_data_zero(result_string); 645 646 memset(&creds, 0, sizeof(creds)); 647 648 if (targprinc == NULL) { 649 ret = krb5_cc_get_principal(context, ccache, &principal); 650 if (ret) 651 return ret; 652 } else 653 principal = targprinc; 654 655 ret = krb5_make_principal(context, &creds.server, 656 krb5_principal_get_realm(context, principal), 657 "kadmin", "changepw", NULL); 658 if (ret) 659 goto out; 660 661 ret = krb5_cc_get_principal(context, ccache, &creds.client); 662 if (ret) { 663 krb5_free_principal(context, creds.server); 664 goto out; 665 } 666 667 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp); 668 krb5_free_principal(context, creds.server); 669 krb5_free_principal(context, creds.client); 670 if (ret) 671 goto out; 672 673 ret = krb5_set_password(context, 674 credsp, 675 newpw, 676 targprinc, 677 result_code, 678 result_code_string, 679 result_string); 680 681 out: 682 if (credsp) 683 krb5_free_creds(context, credsp); 684 if (targprinc == NULL) 685 krb5_free_principal(context, principal); 686 return ret; 687} 688 689/* 690 * 691 */ 692 693KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL 694krb5_passwd_result_to_string (krb5_context context, 695 int result) 696{ 697 static const char *strings[] = { 698 "Success", 699 "Malformed", 700 "Hard error", 701 "Auth error", 702 "Soft error" , 703 "Access denied", 704 "Bad version", 705 "Initial flag needed" 706 }; 707 708 if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) 709 return "unknown result code"; 710 else 711 return strings[result]; 712} 713