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 < 0 || (size_t)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 < 0 || (size_t)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 = 0; 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 < 0 || (size_t)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 < 0 || (size_t)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 * @ingroup @krb5_deprecated 501 */ 502 503KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 504krb5_change_password (krb5_context context, 505 krb5_creds *creds, 506 const char *newpw, 507 int *result_code, 508 krb5_data *result_code_string, 509 krb5_data *result_string) 510 KRB5_DEPRECATED_FUNCTION("Use krb5_set_password instead") 511{ 512 struct kpwd_proc *p = find_chpw_proto("change password"); 513 struct request request; 514 515 *result_code = KRB5_KPASSWD_MALFORMED; 516 result_code_string->data = result_string->data = NULL; 517 result_code_string->length = result_string->length = 0; 518 519 if (p == NULL) 520 return KRB5_KPASSWD_MALFORMED; 521 522 request.ac = NULL; 523 request.target = targprinc; 524 request.creds = creds; 525 request.password = password; 526 527 return change_password_loop(context, &request, 528 result_code, result_code_string, 529 result_string, p); 530} 531#endif /* HEIMDAL_SMALLER */ 532 533/** 534 * Change password using creds. 535 * 536 * @param context a Keberos context 537 * @param creds The initial kadmin/passwd for the principal or an admin principal 538 * @param newpw The new password to set 539 * @param targprinc For most callers should pass NULL in this 540 * argument. Targprinc is the principal to change the password 541 * for. This argument should only be set when you want to 542 * change another Kerberos principal's password, ie you are an 543 * admin. If NULL, the default authenticating principal in the 544 * creds argument is used instead. 545 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed. 546 * @param result_code_string binary message from the server, contains 547 * at least the result_code. 548 * @param result_string A message from the kpasswd service or the 549 * library in human printable form. The string is NUL terminated. 550 * 551 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed. 552 553 * @ingroup @krb5 554 */ 555 556KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 557krb5_set_password(krb5_context context, 558 krb5_creds *creds, 559 const char *newpw, 560 krb5_principal targprinc, 561 int *result_code, 562 krb5_data *result_code_string, 563 krb5_data *result_string) 564{ 565 krb5_error_code ret = 0; 566 struct request request; 567 int i; 568 569 *result_code = KRB5_KPASSWD_MALFORMED; 570 krb5_data_zero(result_code_string); 571 krb5_data_zero(result_string); 572 573 _krb5_debugx(context, 1, "trying to set password"); 574 575 request.ac = NULL; 576 request.target = targprinc; 577 request.creds = creds; 578 request.password = newpw; 579 580 for (i = 0; procs[i].name != NULL; i++) { 581 /* don't try methods that don't support admind change password */ 582 if (targprinc && (procs[i].flags & SUPPORT_ADMIN) == 0) 583 continue; 584 585 ret = change_password_loop(context, &request, 586 result_code, result_code_string, 587 result_string, 588 &procs[i]); 589 if (ret == 0 && *result_code == 0) 590 break; 591 } 592 593 return ret; 594} 595 596/** 597 * Change password using a credential cache that contains an initial 598 * credential or an admin credential. 599 * 600 * @param context a Keberos context 601 * @param ccache the credential cache to use to find the 602 * kadmin/changepw principal. 603 * @param newpw The new password to set 604 * @param targprinc For most callers should pass NULL in this 605 * argument. Targprinc is the principal to change the password 606 * for. This argument should only be set when you want to 607 * change another Kerberos principal's password, ie you are an 608 * admin. If NULL, the default authenticating principal in the 609 * creds argument is used instead. 610 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed. 611 * @param result_code_string binary message from the server, contains 612 * at least the result_code. 613 * @param result_string A message from the kpasswd service or the 614 * library in human printable form. The string is NUL terminated. 615 * 616 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed. 617 * 618 */ 619 620KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 621krb5_set_password_using_ccache(krb5_context context, 622 krb5_ccache ccache, 623 const char *newpw, 624 krb5_principal targprinc, 625 int *result_code, 626 krb5_data *result_code_string, 627 krb5_data *result_string) 628{ 629 krb5_creds creds, *credsp = NULL; 630 krb5_error_code ret; 631 krb5_principal principal = NULL; 632 633 *result_code = KRB5_KPASSWD_MALFORMED; 634 krb5_data_zero(result_code_string); 635 krb5_data_zero(result_string); 636 637 memset(&creds, 0, sizeof(creds)); 638 639 if (targprinc == NULL) { 640 ret = krb5_cc_get_principal(context, ccache, &principal); 641 if (ret) 642 return ret; 643 } else 644 principal = targprinc; 645 646 ret = krb5_make_principal(context, &creds.server, 647 krb5_principal_get_realm(context, principal), 648 "kadmin", "changepw", NULL); 649 if (ret) 650 goto out; 651 652 ret = krb5_cc_get_principal(context, ccache, &creds.client); 653 if (ret) { 654 krb5_free_principal(context, creds.server); 655 goto out; 656 } 657 658 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp); 659 krb5_free_principal(context, creds.server); 660 krb5_free_principal(context, creds.client); 661 if (ret) 662 goto out; 663 664 ret = krb5_set_password(context, 665 credsp, 666 newpw, 667 targprinc, 668 result_code, 669 result_code_string, 670 result_string); 671 672 out: 673 if (credsp) 674 krb5_free_creds(context, credsp); 675 if (targprinc == NULL) 676 krb5_free_principal(context, principal); 677 return ret; 678} 679 680/* 681 * 682 */ 683 684KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL 685krb5_passwd_result_to_string (krb5_context context, 686 int result) 687{ 688 static const char *strings[] = { 689 "Success", 690 "Malformed", 691 "Hard error", 692 "Auth error", 693 "Soft error" , 694 "Access denied", 695 "Bad version", 696 "Initial flag needed" 697 }; 698 699 if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) 700 return "unknown result code"; 701 else 702 return strings[result]; 703} 704