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 36 RCSID("$Id: changepw.c 21505 2007-07-12 12:28:38Z lha $"); 37 38 static void 39 str2data (krb5_data *d, 40 const char *fmt, 41 ...) __attribute__ ((format (printf, 2, 3))); 42 43 static void 44 str2data (krb5_data *d, 45 const char *fmt, 46 ...) 47 { 48 va_list args; 49 char *str; 50 51 va_start(args, fmt); 52 d->length = vasprintf (&str, fmt, args); 53 va_end(args); 54 d->data = str; 55 } 56 57 /* 58 * Change password protocol defined by 59 * draft-ietf-cat-kerb-chg-password-02.txt 60 * 61 * Share the response part of the protocol with MS set password 62 * (RFC3244) 63 */ 64 65 static krb5_error_code 66 chgpw_send_request (krb5_context context, 67 krb5_auth_context *auth_context, 68 krb5_creds *creds, 69 krb5_principal targprinc, 70 int is_stream, 71 int sock, 72 const char *passwd, 73 const char *host) 74 { 75 krb5_error_code ret; 76 krb5_data ap_req_data; 77 krb5_data krb_priv_data; 78 krb5_data passwd_data; 79 size_t len; 80 u_char header[6]; 81 u_char *p; 82 struct iovec iov[3]; 83 struct msghdr msghdr; 84 85 if (is_stream) 86 return KRB5_KPASSWD_MALFORMED; 87 88 if (targprinc && 89 krb5_principal_compare(context, creds->client, targprinc) != TRUE) 90 return KRB5_KPASSWD_MALFORMED; 91 92 krb5_data_zero (&ap_req_data); 93 94 ret = krb5_mk_req_extended (context, 95 auth_context, 96 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY, 97 NULL, /* in_data */ 98 creds, 99 &ap_req_data); 100 if (ret) 101 return ret; 102 103 passwd_data.data = rk_UNCONST(passwd); 104 passwd_data.length = strlen(passwd); 105 106 krb5_data_zero (&krb_priv_data); 107 108 ret = krb5_mk_priv (context, 109 *auth_context, 110 &passwd_data, 111 &krb_priv_data, 112 NULL); 113 if (ret) 114 goto out2; 115 116 len = 6 + ap_req_data.length + krb_priv_data.length; 117 p = header; 118 *p++ = (len >> 8) & 0xFF; 119 *p++ = (len >> 0) & 0xFF; 120 *p++ = 0; 121 *p++ = 1; 122 *p++ = (ap_req_data.length >> 8) & 0xFF; 123 *p++ = (ap_req_data.length >> 0) & 0xFF; 124 125 memset(&msghdr, 0, sizeof(msghdr)); 126 msghdr.msg_name = NULL; 127 msghdr.msg_namelen = 0; 128 msghdr.msg_iov = iov; 129 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); 130 #if 0 131 msghdr.msg_control = NULL; 132 msghdr.msg_controllen = 0; 133 #endif 134 135 iov[0].iov_base = (void*)header; 136 iov[0].iov_len = 6; 137 iov[1].iov_base = ap_req_data.data; 138 iov[1].iov_len = ap_req_data.length; 139 iov[2].iov_base = krb_priv_data.data; 140 iov[2].iov_len = krb_priv_data.length; 141 142 if (sendmsg (sock, &msghdr, 0) < 0) { 143 ret = errno; 144 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret)); 145 } 146 147 krb5_data_free (&krb_priv_data); 148 out2: 149 krb5_data_free (&ap_req_data); 150 return ret; 151 } 152 153 /* 154 * Set password protocol as defined by RFC3244 -- 155 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols 156 */ 157 158 static krb5_error_code 159 setpw_send_request (krb5_context context, 160 krb5_auth_context *auth_context, 161 krb5_creds *creds, 162 krb5_principal targprinc, 163 int is_stream, 164 int sock, 165 const char *passwd, 166 const char *host) 167 { 168 krb5_error_code ret; 169 krb5_data ap_req_data; 170 krb5_data krb_priv_data; 171 krb5_data pwd_data; 172 ChangePasswdDataMS chpw; 173 size_t len; 174 u_char header[4 + 6]; 175 u_char *p; 176 struct iovec iov[3]; 177 struct msghdr msghdr; 178 179 krb5_data_zero (&ap_req_data); 180 181 ret = krb5_mk_req_extended (context, 182 auth_context, 183 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY, 184 NULL, /* in_data */ 185 creds, 186 &ap_req_data); 187 if (ret) 188 return ret; 189 190 chpw.newpasswd.length = strlen(passwd); 191 chpw.newpasswd.data = rk_UNCONST(passwd); 192 if (targprinc) { 193 chpw.targname = &targprinc->name; 194 chpw.targrealm = &targprinc->realm; 195 } else { 196 chpw.targname = NULL; 197 chpw.targrealm = NULL; 198 } 199 200 ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length, 201 &chpw, &len, ret); 202 if (ret) { 203 krb5_data_free (&ap_req_data); 204 return ret; 205 } 206 207 if(pwd_data.length != len) 208 krb5_abortx(context, "internal error in ASN.1 encoder"); 209 210 ret = krb5_mk_priv (context, 211 *auth_context, 212 &pwd_data, 213 &krb_priv_data, 214 NULL); 215 if (ret) 216 goto out2; 217 218 len = 6 + ap_req_data.length + krb_priv_data.length; 219 p = header; 220 if (is_stream) { 221 _krb5_put_int(p, len, 4); 222 p += 4; 223 } 224 *p++ = (len >> 8) & 0xFF; 225 *p++ = (len >> 0) & 0xFF; 226 *p++ = 0xff; 227 *p++ = 0x80; 228 *p++ = (ap_req_data.length >> 8) & 0xFF; 229 *p++ = (ap_req_data.length >> 0) & 0xFF; 230 231 memset(&msghdr, 0, sizeof(msghdr)); 232 msghdr.msg_name = NULL; 233 msghdr.msg_namelen = 0; 234 msghdr.msg_iov = iov; 235 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov); 236 #if 0 237 msghdr.msg_control = NULL; 238 msghdr.msg_controllen = 0; 239 #endif 240 241 iov[0].iov_base = (void*)header; 242 if (is_stream) 243 iov[0].iov_len = 10; 244 else 245 iov[0].iov_len = 6; 246 iov[1].iov_base = ap_req_data.data; 247 iov[1].iov_len = ap_req_data.length; 248 iov[2].iov_base = krb_priv_data.data; 249 iov[2].iov_len = krb_priv_data.length; 250 251 if (sendmsg (sock, &msghdr, 0) < 0) { 252 ret = errno; 253 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret)); 254 } 255 256 krb5_data_free (&krb_priv_data); 257 out2: 258 krb5_data_free (&ap_req_data); 259 krb5_data_free (&pwd_data); 260 return ret; 261 } 262 263 static krb5_error_code 264 process_reply (krb5_context context, 265 krb5_auth_context auth_context, 266 int is_stream, 267 int sock, 268 int *result_code, 269 krb5_data *result_code_string, 270 krb5_data *result_string, 271 const char *host) 272 { 273 krb5_error_code ret; 274 u_char reply[1024 * 3]; 275 ssize_t len; 276 uint16_t pkt_len, pkt_ver; 277 krb5_data ap_rep_data; 278 int save_errno; 279 280 len = 0; 281 if (is_stream) { 282 while (len < sizeof(reply)) { 283 unsigned long size; 284 285 ret = recvfrom (sock, reply + len, sizeof(reply) - len, 286 0, NULL, NULL); 287 if (ret < 0) { 288 save_errno = errno; 289 krb5_set_error_string(context, "recvfrom %s: %s", 290 host, strerror(save_errno)); 291 return save_errno; 292 } else if (ret == 0) { 293 krb5_set_error_string(context, "recvfrom timeout %s", host); 294 return 1; 295 } 296 len += ret; 297 if (len < 4) 298 continue; 299 _krb5_get_int(reply, &size, 4); 300 if (size + 4 < len) 301 continue; 302 memmove(reply, reply + 4, size); 303 len = size; 304 break; 305 } 306 if (len == sizeof(reply)) { 307 krb5_set_error_string(context, "message too large from %s", 308 host); 309 return ENOMEM; 310 } 311 } else { 312 ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL); 313 if (ret < 0) { 314 save_errno = errno; 315 krb5_set_error_string(context, "recvfrom %s: %s", 316 host, strerror(save_errno)); 317 return save_errno; 318 } 319 len = ret; 320 } 321 322 if (len < 6) { 323 str2data (result_string, "server %s sent to too short message " 324 "(%ld bytes)", host, (long)len); 325 *result_code = KRB5_KPASSWD_MALFORMED; 326 return 0; 327 } 328 329 pkt_len = (reply[0] << 8) | (reply[1]); 330 pkt_ver = (reply[2] << 8) | (reply[3]); 331 332 if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) { 333 KRB_ERROR error; 334 size_t size; 335 u_char *p; 336 337 memset(&error, 0, sizeof(error)); 338 339 ret = decode_KRB_ERROR(reply, len, &error, &size); 340 if (ret) 341 return ret; 342 343 if (error.e_data->length < 2) { 344 str2data(result_string, "server %s sent too short " 345 "e_data to print anything usable", host); 346 free_KRB_ERROR(&error); 347 *result_code = KRB5_KPASSWD_MALFORMED; 348 return 0; 349 } 350 351 p = error.e_data->data; 352 *result_code = (p[0] << 8) | p[1]; 353 if (error.e_data->length == 2) 354 str2data(result_string, "server only sent error code"); 355 else 356 krb5_data_copy (result_string, 357 p + 2, 358 error.e_data->length - 2); 359 free_KRB_ERROR(&error); 360 return 0; 361 } 362 363 if (pkt_len != len) { 364 str2data (result_string, "client: wrong len in reply"); 365 *result_code = KRB5_KPASSWD_MALFORMED; 366 return 0; 367 } 368 if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) { 369 str2data (result_string, 370 "client: wrong version number (%d)", pkt_ver); 371 *result_code = KRB5_KPASSWD_MALFORMED; 372 return 0; 373 } 374 375 ap_rep_data.data = reply + 6; 376 ap_rep_data.length = (reply[4] << 8) | (reply[5]); 377 378 if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) { 379 str2data (result_string, "client: wrong AP len in reply"); 380 *result_code = KRB5_KPASSWD_MALFORMED; 381 return 0; 382 } 383 384 if (ap_rep_data.length) { 385 krb5_ap_rep_enc_part *ap_rep; 386 krb5_data priv_data; 387 u_char *p; 388 389 priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length; 390 priv_data.length = len - ap_rep_data.length - 6; 391 392 ret = krb5_rd_rep (context, 393 auth_context, 394 &ap_rep_data, 395 &ap_rep); 396 if (ret) 397 return ret; 398 399 krb5_free_ap_rep_enc_part (context, ap_rep); 400 401 ret = krb5_rd_priv (context, 402 auth_context, 403 &priv_data, 404 result_code_string, 405 NULL); 406 if (ret) { 407 krb5_data_free (result_code_string); 408 return ret; 409 } 410 411 if (result_code_string->length < 2) { 412 *result_code = KRB5_KPASSWD_MALFORMED; 413 str2data (result_string, 414 "client: bad length in result"); 415 return 0; 416 } 417 418 p = result_code_string->data; 419 420 *result_code = (p[0] << 8) | p[1]; 421 krb5_data_copy (result_string, 422 (unsigned char*)result_code_string->data + 2, 423 result_code_string->length - 2); 424 return 0; 425 } else { 426 KRB_ERROR error; 427 size_t size; 428 u_char *p; 429 430 ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size); 431 if (ret) { 432 return ret; 433 } 434 if (error.e_data->length < 2) { 435 krb5_warnx (context, "too short e_data to print anything usable"); 436 return 1; /* XXX */ 437 } 438 439 p = error.e_data->data; 440 *result_code = (p[0] << 8) | p[1]; 441 krb5_data_copy (result_string, 442 p + 2, 443 error.e_data->length - 2); 444 return 0; 445 } 446 } 447 448 449 /* 450 * change the password using the credentials in `creds' (for the 451 * principal indicated in them) to `newpw', storing the result of 452 * the operation in `result_*' and an error code or 0. 453 */ 454 455 typedef krb5_error_code (*kpwd_send_request) (krb5_context, 456 krb5_auth_context *, 457 krb5_creds *, 458 krb5_principal, 459 int, 460 int, 461 const char *, 462 const char *); 463 typedef krb5_error_code (*kpwd_process_reply) (krb5_context, 464 krb5_auth_context, 465 int, 466 int, 467 int *, 468 krb5_data *, 469 krb5_data *, 470 const char *); 471 472 static struct kpwd_proc { 473 const char *name; 474 int flags; 475 #define SUPPORT_TCP 1 476 #define SUPPORT_UDP 2 477 kpwd_send_request send_req; 478 kpwd_process_reply process_rep; 479 } procs[] = { 480 { 481 "MS set password", 482 SUPPORT_TCP|SUPPORT_UDP, 483 setpw_send_request, 484 process_reply 485 }, 486 { 487 "change password", 488 SUPPORT_UDP, 489 chgpw_send_request, 490 process_reply 491 }, 492 { NULL } 493 }; 494 495 static struct kpwd_proc * 496 find_chpw_proto(const char *name) 497 { 498 struct kpwd_proc *p; 499 for (p = procs; p->name != NULL; p++) { 500 if (strcmp(p->name, name) == 0) 501 return p; 502 } 503 return NULL; 504 } 505 506 /* 507 * 508 */ 509 510 static krb5_error_code 511 change_password_loop (krb5_context context, 512 krb5_creds *creds, 513 krb5_principal targprinc, 514 const char *newpw, 515 int *result_code, 516 krb5_data *result_code_string, 517 krb5_data *result_string, 518 struct kpwd_proc *proc) 519 { 520 krb5_error_code ret; 521 krb5_auth_context auth_context = NULL; 522 krb5_krbhst_handle handle = NULL; 523 krb5_krbhst_info *hi; 524 int sock; 525 int i; 526 int done = 0; 527 krb5_realm realm; 528 529 if (targprinc) 530 realm = targprinc->realm; 531 else 532 realm = creds->client->realm; 533 534 ret = krb5_auth_con_init (context, &auth_context); 535 if (ret) 536 return ret; 537 538 krb5_auth_con_setflags (context, auth_context, 539 KRB5_AUTH_CONTEXT_DO_SEQUENCE); 540 541 ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle); 542 if (ret) 543 goto out; 544 545 while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) { 546 struct addrinfo *ai, *a; 547 int is_stream; 548 549 switch (hi->proto) { 550 case KRB5_KRBHST_UDP: 551 if ((proc->flags & SUPPORT_UDP) == 0) 552 continue; 553 is_stream = 0; 554 break; 555 case KRB5_KRBHST_TCP: 556 if ((proc->flags & SUPPORT_TCP) == 0) 557 continue; 558 is_stream = 1; 559 break; 560 default: 561 continue; 562 } 563 564 ret = krb5_krbhst_get_addrinfo(context, hi, &ai); 565 if (ret) 566 continue; 567 568 for (a = ai; !done && a != NULL; a = a->ai_next) { 569 int replied = 0; 570 571 sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol); 572 if (sock < 0) 573 continue; 574 575 ret = connect(sock, a->ai_addr, a->ai_addrlen); 576 if (ret < 0) { 577 close (sock); 578 goto out; 579 } 580 581 ret = krb5_auth_con_genaddrs (context, auth_context, sock, 582 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR); 583 if (ret) { 584 close (sock); 585 goto out; 586 } 587 588 for (i = 0; !done && i < 5; ++i) { 589 fd_set fdset; 590 struct timeval tv; 591 592 if (!replied) { 593 replied = 0; 594 595 ret = (*proc->send_req) (context, 596 &auth_context, 597 creds, 598 targprinc, 599 is_stream, 600 sock, 601 newpw, 602 hi->hostname); 603 if (ret) { 604 close(sock); 605 goto out; 606 } 607 } 608 609 if (sock >= FD_SETSIZE) { 610 krb5_set_error_string(context, "fd %d too large", sock); 611 ret = ERANGE; 612 close (sock); 613 goto out; 614 } 615 616 FD_ZERO(&fdset); 617 FD_SET(sock, &fdset); 618 tv.tv_usec = 0; 619 tv.tv_sec = 1 + (1 << i); 620 621 ret = select (sock + 1, &fdset, NULL, NULL, &tv); 622 if (ret < 0 && errno != EINTR) { 623 close(sock); 624 goto out; 625 } 626 if (ret == 1) { 627 ret = (*proc->process_rep) (context, 628 auth_context, 629 is_stream, 630 sock, 631 result_code, 632 result_code_string, 633 result_string, 634 hi->hostname); 635 if (ret == 0) 636 done = 1; 637 else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL) 638 replied = 1; 639 } else { 640 ret = KRB5_KDC_UNREACH; 641 } 642 } 643 close (sock); 644 } 645 } 646 647 out: 648 krb5_krbhst_free (context, handle); 649 krb5_auth_con_free (context, auth_context); 650 if (done) 651 return 0; 652 else { 653 if (ret == KRB5_KDC_UNREACH) { 654 krb5_set_error_string(context, 655 "unable to reach any changepw server " 656 " in realm %s", realm); 657 *result_code = KRB5_KPASSWD_HARDERROR; 658 } 659 return ret; 660 } 661 } 662 663 664 /* 665 * change the password using the credentials in `creds' (for the 666 * principal indicated in them) to `newpw', storing the result of 667 * the operation in `result_*' and an error code or 0. 668 */ 669 670 krb5_error_code KRB5_LIB_FUNCTION 671 krb5_change_password (krb5_context context, 672 krb5_creds *creds, 673 const char *newpw, 674 int *result_code, 675 krb5_data *result_code_string, 676 krb5_data *result_string) 677 { 678 struct kpwd_proc *p = find_chpw_proto("change password"); 679 680 *result_code = KRB5_KPASSWD_MALFORMED; 681 result_code_string->data = result_string->data = NULL; 682 result_code_string->length = result_string->length = 0; 683 684 if (p == NULL) 685 return KRB5_KPASSWD_MALFORMED; 686 687 return change_password_loop(context, creds, NULL, newpw, 688 result_code, result_code_string, 689 result_string, p); 690 } 691 692 /* 693 * 694 */ 695 696 krb5_error_code KRB5_LIB_FUNCTION 697 krb5_set_password(krb5_context context, 698 krb5_creds *creds, 699 const char *newpw, 700 krb5_principal targprinc, 701 int *result_code, 702 krb5_data *result_code_string, 703 krb5_data *result_string) 704 { 705 krb5_principal principal = NULL; 706 krb5_error_code ret = 0; 707 int i; 708 709 *result_code = KRB5_KPASSWD_MALFORMED; 710 result_code_string->data = result_string->data = NULL; 711 result_code_string->length = result_string->length = 0; 712 713 if (targprinc == NULL) { 714 ret = krb5_get_default_principal(context, &principal); 715 if (ret) 716 return ret; 717 } else 718 principal = targprinc; 719 720 for (i = 0; procs[i].name != NULL; i++) { 721 *result_code = 0; 722 ret = change_password_loop(context, creds, principal, newpw, 723 result_code, result_code_string, 724 result_string, 725 &procs[i]); 726 if (ret == 0 && *result_code == 0) 727 break; 728 } 729 730 if (targprinc == NULL) 731 krb5_free_principal(context, principal); 732 return ret; 733 } 734 735 /* 736 * 737 */ 738 739 krb5_error_code KRB5_LIB_FUNCTION 740 krb5_set_password_using_ccache(krb5_context context, 741 krb5_ccache ccache, 742 const char *newpw, 743 krb5_principal targprinc, 744 int *result_code, 745 krb5_data *result_code_string, 746 krb5_data *result_string) 747 { 748 krb5_creds creds, *credsp; 749 krb5_error_code ret; 750 krb5_principal principal = NULL; 751 752 *result_code = KRB5_KPASSWD_MALFORMED; 753 result_code_string->data = result_string->data = NULL; 754 result_code_string->length = result_string->length = 0; 755 756 memset(&creds, 0, sizeof(creds)); 757 758 if (targprinc == NULL) { 759 ret = krb5_cc_get_principal(context, ccache, &principal); 760 if (ret) 761 return ret; 762 } else 763 principal = targprinc; 764 765 ret = krb5_make_principal(context, &creds.server, 766 krb5_principal_get_realm(context, principal), 767 "kadmin", "changepw", NULL); 768 if (ret) 769 goto out; 770 771 ret = krb5_cc_get_principal(context, ccache, &creds.client); 772 if (ret) { 773 krb5_free_principal(context, creds.server); 774 goto out; 775 } 776 777 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp); 778 krb5_free_principal(context, creds.server); 779 krb5_free_principal(context, creds.client); 780 if (ret) 781 goto out; 782 783 ret = krb5_set_password(context, 784 credsp, 785 newpw, 786 principal, 787 result_code, 788 result_code_string, 789 result_string); 790 791 krb5_free_creds(context, credsp); 792 793 return ret; 794 out: 795 if (targprinc == NULL) 796 krb5_free_principal(context, principal); 797 return ret; 798 } 799 800 /* 801 * 802 */ 803 804 const char* KRB5_LIB_FUNCTION 805 krb5_passwd_result_to_string (krb5_context context, 806 int result) 807 { 808 static const char *strings[] = { 809 "Success", 810 "Malformed", 811 "Hard error", 812 "Auth error", 813 "Soft error" , 814 "Access denied", 815 "Bad version", 816 "Initial flag needed" 817 }; 818 819 if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) 820 return "unknown result code"; 821 else 822 return strings[result]; 823 } 824