1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <security/pam_appl.h> 29 #include <security/pam_modules.h> 30 #include <security/pam_impl.h> 31 #include <string.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <sys/types.h> 35 #include <sys/stat.h> 36 #include <pwd.h> 37 #include <syslog.h> 38 #include <libintl.h> 39 #include <k5-int.h> 40 #include "profile/prof_int.h" 41 #include <netdb.h> 42 #include <ctype.h> 43 #include "utils.h" 44 #include "krb5_repository.h" 45 46 #define KRB5_DEFAULT_OPTIONS 0 47 48 int forwardable_flag = 0; 49 int renewable_flag = 0; 50 int proxiable_flag = 0; 51 int no_address_flag = 0; 52 profile_options_boolean config_option[] = { 53 { "forwardable", &forwardable_flag, 0 }, 54 { "renewable", &renewable_flag, 0 }, 55 { "proxiable", &proxiable_flag, 0 }, 56 { "no_addresses", &no_address_flag, 0 }, 57 { NULL, NULL, 0 } 58 }; 59 char *renew_timeval; 60 char *life_timeval; 61 profile_option_strings config_times[] = { 62 { "max_life", &life_timeval, 0 }, 63 { "max_renewable_life", &renew_timeval, 0 }, 64 { NULL, NULL, 0 } 65 }; 66 char *realmdef[] = { "realms", NULL, NULL, NULL }; 67 char *appdef[] = { "appdefaults", "kinit", NULL }; 68 69 #define krb_realm (*(realmdef + 1)) 70 71 int attempt_krb5_auth(krb5_module_data_t *, char *, char **, boolean_t); 72 void krb5_cleanup(pam_handle_t *, void *, int); 73 74 extern errcode_t profile_get_options_boolean(); 75 extern errcode_t profile_get_options_string(); 76 extern int krb5_verifypw(char *, char *, int); 77 extern krb5_error_code krb5_verify_init_creds(krb5_context, 78 krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *, 79 krb5_verify_init_creds_opt *); 80 81 /* 82 * pam_sm_authenticate - Authenticate user 83 */ 84 int 85 pam_sm_authenticate( 86 pam_handle_t *pamh, 87 int flags, 88 int argc, 89 const char **argv) 90 { 91 char *user = NULL; 92 int err; 93 int result = PAM_AUTH_ERR; 94 /* pam.conf options */ 95 int debug = 0; 96 int warn = 1; 97 /* return an error on password expire */ 98 int err_on_exp = 0; 99 int i; 100 char *password = NULL; 101 uid_t pw_uid; 102 krb5_module_data_t *kmd = NULL; 103 krb5_repository_data_t *krb5_data = NULL; 104 pam_repository_t *rep_data = NULL; 105 106 for (i = 0; i < argc; i++) { 107 if (strcmp(argv[i], "debug") == 0) { 108 debug = 1; 109 } else if (strcmp(argv[i], "nowarn") == 0) { 110 warn = 0; 111 } else if (strcmp(argv[i], "err_on_exp") == 0) { 112 err_on_exp = 1; 113 } else { 114 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 115 "PAM-KRB5 (auth) unrecognized option %s"), 116 argv[i]); 117 } 118 } 119 if (flags & PAM_SILENT) warn = 0; 120 121 if (debug) 122 syslog(LOG_DEBUG, 123 "PAM-KRB5 (auth): pam_sm_authenticate flags=%d", 124 flags); 125 126 (void) pam_get_item(pamh, PAM_USER, (void**) &user); 127 128 if (user == NULL || *user == '\0') { 129 if (debug) 130 syslog(LOG_DEBUG, "PAM-KRB5 (auth): user empty " 131 "or null"); 132 return (PAM_USER_UNKNOWN); 133 } 134 135 /* make sure a password entry exists for this user */ 136 if (!get_pw_uid(user, &pw_uid)) 137 return (PAM_USER_UNKNOWN); 138 139 /* 140 * pam_get_data could fail if we are being called for the first time 141 * or if the module is not found, PAM_NO_MODULE_DATA is not an error 142 */ 143 err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd); 144 if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA)) 145 return (PAM_AUTH_ERR); 146 147 if (kmd == NULL) { 148 kmd = calloc(1, sizeof (krb5_module_data_t)); 149 if (kmd == NULL) { 150 result = PAM_BUF_ERR; 151 goto out; 152 } 153 154 err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup); 155 if (err != PAM_SUCCESS) { 156 free(kmd); 157 result = err; 158 goto out; 159 } 160 } 161 162 if (!kmd->env) { 163 char buffer[512]; 164 165 if (snprintf(buffer, sizeof (buffer), 166 "%s=FILE:/tmp/krb5cc_%d", 167 KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) { 168 result = PAM_SYSTEM_ERR; 169 goto out; 170 } 171 172 /* we MUST copy this to the heap for the putenv to work! */ 173 kmd->env = strdup(buffer); 174 if (!kmd->env) { 175 result = PAM_BUF_ERR; 176 goto out; 177 } else { 178 if (putenv(kmd->env)) { 179 result = PAM_SYSTEM_ERR; 180 goto out; 181 } 182 } 183 } 184 185 kmd->auth_status = PAM_AUTH_ERR; 186 kmd->debug = debug; 187 kmd->warn = warn; 188 kmd->err_on_exp = err_on_exp; 189 kmd->ccache = NULL; 190 kmd->kcontext = NULL; 191 kmd->password = NULL; 192 kmd->age_status = PAM_SUCCESS; 193 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 194 195 /* 196 * For apps that already did krb5 auth exchange... 197 * Now that we've created the kmd structure, we can 198 * return SUCCESS. 'kmd' may be needed later by other 199 * PAM functions, thats why we wait until this point to 200 * return. 201 */ 202 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 203 204 if (rep_data != NULL) { 205 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 206 if (debug) 207 syslog(LOG_DEBUG, "PAM-KRB5 (auth): wrong" 208 "repository found (%s), returning " 209 "PAM_IGNORE", rep_data->type); 210 return (PAM_IGNORE); 211 } 212 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) { 213 krb5_data = (krb5_repository_data_t *)rep_data->scope; 214 215 if (krb5_data->flags == 216 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED && 217 krb5_data->principal != NULL && 218 strlen(krb5_data->principal)) { 219 if (debug) 220 syslog(LOG_DEBUG, 221 "PAM-KRB5 (auth): Principal " 222 "%s already authenticated", 223 krb5_data->principal); 224 kmd->auth_status = PAM_SUCCESS; 225 return (PAM_SUCCESS); 226 } 227 } 228 } 229 230 /* 231 * if root key exists in the keytab, it's a random key so no 232 * need to prompt for pw and we just return IGNORE. 233 * 234 * note we don't need to force a prompt for pw as authtok_get 235 * is required to be stacked above this module. 236 */ 237 if ((strcmp(user, ROOT_UNAME) == 0) && 238 key_in_keytab(user, debug)) { 239 if (debug) 240 syslog(LOG_DEBUG, 241 "PAM-KRB5 (auth): " 242 "key for '%s' in keytab, returning IGNORE", user); 243 result = PAM_IGNORE; 244 goto out; 245 } 246 247 (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password); 248 249 result = attempt_krb5_auth(kmd, user, &password, 1); 250 251 out: 252 if (kmd) { 253 if (debug) 254 syslog(LOG_DEBUG, 255 "PAM-KRB5 (auth): pam_sm_auth finalize" 256 " ccname env, result =%d, env ='%s'," 257 " age = %d, status = %d", 258 result, kmd->env ? kmd->env : "<null>", 259 kmd->age_status, kmd->auth_status); 260 261 if (kmd->env && 262 !(kmd->age_status == PAM_NEW_AUTHTOK_REQD && 263 kmd->auth_status == PAM_SUCCESS)) { 264 265 266 if (result == PAM_SUCCESS) { 267 /* 268 * Put ccname into the pamh so that login 269 * apps can pick this up when they run 270 * pam_getenvlist(). 271 */ 272 if ((result = pam_putenv(pamh, kmd->env)) 273 != PAM_SUCCESS) { 274 /* should not happen but... */ 275 syslog(LOG_ERR, 276 dgettext(TEXT_DOMAIN, 277 "PAM-KRB5 (auth):" 278 " pam_putenv failed: result: %d"), 279 result); 280 goto cleanupccname; 281 } 282 } else { 283 cleanupccname: 284 /* for lack of a Solaris unputenv() */ 285 krb5_unsetenv(KRB5_ENV_CCNAME); 286 free(kmd->env); 287 kmd->env = NULL; 288 } 289 } 290 kmd->auth_status = result; 291 } 292 293 if (debug) 294 syslog(LOG_DEBUG, 295 "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result)); 296 297 return (result); 298 } 299 300 int 301 attempt_krb5_auth( 302 krb5_module_data_t *kmd, 303 char *user, 304 char **krb5_pass, 305 boolean_t verify_tik) 306 { 307 krb5_principal me = NULL; 308 krb5_principal server = NULL; 309 krb5_creds *my_creds; 310 krb5_timestamp now; 311 krb5_error_code code = 0; 312 char kuser[2*MAXHOSTNAMELEN]; 313 krb5_deltat lifetime; 314 krb5_deltat rlife; 315 krb5_deltat krb5_max_duration; 316 int options = KRB5_DEFAULT_OPTIONS; 317 krb5_data tgtname = { 318 0, 319 KRB5_TGS_NAME_SIZE, 320 KRB5_TGS_NAME 321 }; 322 krb5_get_init_creds_opt opts; 323 /* 324 * "result" should not be assigned PAM_SUCCESS unless 325 * authentication has succeeded and there are no other errors. 326 * 327 * "code" is sometimes used for PAM codes, sometimes for krb5 328 * codes. Be careful. 329 */ 330 int result = PAM_AUTH_ERR; 331 332 if (kmd->debug) 333 syslog(LOG_DEBUG, 334 "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'", 335 user ? user : "<null>"); 336 337 krb5_get_init_creds_opt_init(&opts); 338 339 /* need to free context with krb5_free_context */ 340 if (code = krb5_init_context(&kmd->kcontext)) { 341 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 342 "PAM-KRB5 (auth): Error initializing " 343 "krb5: %s"), 344 error_message(code)); 345 return (PAM_SYSTEM_ERR); 346 } 347 348 if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser, 349 2*MAXHOSTNAMELEN)) != 0) { 350 /* get_kmd_kuser returns proper PAM error statuses */ 351 return (code); 352 } 353 354 if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) { 355 krb5_free_context(kmd->kcontext); 356 kmd->kcontext = NULL; 357 return (PAM_AUTH_ERR); 358 } 359 360 /* call krb5_free_cred_contents() on error */ 361 my_creds = &kmd->initcreds; 362 363 if ((code = krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) 364 goto out_err; 365 366 if (code = krb5_build_principal_ext(kmd->kcontext, &server, 367 krb5_princ_realm(kmd->kcontext, me)->length, 368 krb5_princ_realm(kmd->kcontext, me)->data, 369 tgtname.length, tgtname.data, 370 krb5_princ_realm(kmd->kcontext, me)->length, 371 krb5_princ_realm(kmd->kcontext, me)->data, 0)) { 372 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 373 "PAM-KRB5 (auth): attempt_krb5_auth: " 374 "krb5_build_princ_ext failed: %s"), 375 error_message(code)); 376 goto out; 377 } 378 379 if (code = krb5_copy_principal(kmd->kcontext, server, 380 &my_creds->server)) { 381 goto out_err; 382 } 383 384 if (code = krb5_timeofday(kmd->kcontext, &now)) { 385 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 386 "PAM-KRB5 (auth): attempt_krb5_auth: " 387 "krb5_timeofday failed: %s"), 388 error_message(code)); 389 goto out; 390 } 391 392 /* 393 * set the values for lifetime and rlife to be the maximum 394 * possible 395 */ 396 krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60; 397 lifetime = krb5_max_duration; 398 rlife = krb5_max_duration; 399 400 /* 401 * Let us get the values for various options 402 * from Kerberos configuration file 403 */ 404 405 krb_realm = krb5_princ_realm(kmd->kcontext, me)->data; 406 profile_get_options_boolean(kmd->kcontext->profile, 407 realmdef, config_option); 408 profile_get_options_boolean(kmd->kcontext->profile, 409 appdef, config_option); 410 profile_get_options_string(kmd->kcontext->profile, 411 realmdef, config_times); 412 profile_get_options_string(kmd->kcontext->profile, 413 appdef, config_times); 414 415 if (renew_timeval) { 416 code = krb5_string_to_deltat(renew_timeval, &rlife); 417 if (code != 0 || rlife == 0 || rlife > krb5_max_duration) { 418 syslog(LOG_ERR, 419 dgettext(TEXT_DOMAIN, 420 "PAM-KRB5 (auth): Bad max_renewable_life " 421 " value '%s' in Kerberos config file"), 422 renew_timeval); 423 result = PAM_SYSTEM_ERR; 424 goto out; 425 } 426 } 427 if (life_timeval) { 428 code = krb5_string_to_deltat(life_timeval, &lifetime); 429 if (code != 0 || lifetime == 0 || 430 lifetime > krb5_max_duration) { 431 syslog(LOG_ERR, 432 dgettext(TEXT_DOMAIN, "PAM-KRB5 (auth): Bad " 433 "lifetime value '%s' in Kerberos config file"), 434 life_timeval); 435 result = PAM_SYSTEM_ERR; 436 goto out; 437 } 438 } 439 /* start timer when request gets to KDC */ 440 my_creds->times.starttime = 0; 441 my_creds->times.endtime = now + lifetime; 442 443 if (options & KDC_OPT_RENEWABLE) { 444 my_creds->times.renew_till = now + rlife; 445 } else 446 my_creds->times.renew_till = 0; 447 448 krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime); 449 450 if (proxiable_flag) { /* Set in config file */ 451 if (kmd->debug) 452 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, 453 "PAM-KRB5 (auth): Proxiable tickets " 454 "requested")); 455 krb5_get_init_creds_opt_set_proxiable(&opts, TRUE); 456 } 457 if (forwardable_flag) { 458 if (kmd->debug) 459 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, 460 "PAM-KRB5 (auth): Forwardable tickets " 461 "requested")); 462 krb5_get_init_creds_opt_set_forwardable(&opts, TRUE); 463 } 464 if (renewable_flag) { 465 if (kmd->debug) 466 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, 467 "PAM-KRB5 (auth): Renewable tickets " 468 "requested")); 469 krb5_get_init_creds_opt_set_renew_life(&opts, rlife); 470 } 471 if (no_address_flag) { 472 if (kmd->debug) 473 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, 474 "PAM-KRB5 (auth): Addressless tickets " 475 "requested")); 476 krb5_get_init_creds_opt_set_address_list(&opts, NULL); 477 } 478 479 /* 480 * mech_krb5 interprets empty passwords as NULL passwords 481 * and tries to read a password from stdin. Since we are in 482 * pam this is bad and should not be allowed. 483 */ 484 if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { 485 code = KRB5KRB_AP_ERR_BAD_INTEGRITY; 486 } else { 487 code = krb5_get_init_creds_password(kmd->kcontext, 488 my_creds, 489 me, 490 *krb5_pass, /* clear text passwd */ 491 NULL, /* prompter */ 492 NULL, /* data */ 493 0, /* start time */ 494 NULL, /* defaults to krbtgt@REALM */ 495 &opts); 496 } 497 498 if (kmd->debug) 499 syslog(LOG_DEBUG, 500 "PAM-KRB5 (auth): attempt_krb5_auth: " 501 "krb5_get_init_creds_password returns: %s", 502 code == 0 ? "SUCCESS" : error_message(code)); 503 504 switch (code) { 505 case 0: 506 /* got a tgt, let's verify it */ 507 if (verify_tik) { 508 krb5_verify_init_creds_opt vopts; 509 510 krb5_principal sp = NULL; 511 char kt_name[MAX_KEYTAB_NAME_LEN]; 512 char *fqdn; 513 514 krb5_verify_init_creds_opt_init(&vopts); 515 516 code = krb5_verify_init_creds(kmd->kcontext, 517 my_creds, 518 NULL, /* defaults to host/localhost@REALM */ 519 NULL, 520 NULL, 521 &vopts); 522 523 if (code) { 524 result = PAM_SYSTEM_ERR; 525 526 /* 527 * Give a better error message when the 528 * keytable entry isn't found or the keytab 529 * file cannot be found. 530 */ 531 if (krb5_sname_to_principal(kmd->kcontext, NULL, 532 NULL, KRB5_NT_SRV_HST, &sp)) 533 fqdn = "<fqdn>"; 534 else 535 fqdn = sp->data[1].data; 536 537 if (krb5_kt_default_name(kmd->kcontext, kt_name, 538 sizeof (kt_name))) 539 (void) strncpy(kt_name, 540 "default keytab", 541 sizeof (kt_name)); 542 543 switch (code) { 544 case KRB5_KT_NOTFOUND: 545 syslog(LOG_ERR, 546 dgettext(TEXT_DOMAIN, 547 "PAM-KRB5 (auth): " 548 "krb5_verify_init_creds failed:" 549 " Key table entry \"host/%s\"" 550 " not found in %s"), 551 fqdn, kt_name); 552 break; 553 case ENOENT: 554 syslog(LOG_ERR, 555 dgettext(TEXT_DOMAIN, 556 "PAM-KRB5 (auth): " 557 "krb5_verify_init_creds failed:" 558 " Keytab file \"%s\"" 559 " does not exist.\n"), 560 kt_name); 561 break; 562 default: 563 syslog(LOG_ERR, 564 dgettext(TEXT_DOMAIN, 565 "PAM-KRB5 (auth): " 566 "krb5_verify_init_creds failed:" 567 " %s"), 568 error_message(code)); 569 break; 570 } 571 572 if (sp) 573 krb5_free_principal(kmd->kcontext, sp); 574 } 575 } 576 break; 577 578 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: 579 /* 580 * Since this principal is not part of the local 581 * Kerberos realm, we just return PAM_USER_UNKNOWN. 582 */ 583 result = PAM_USER_UNKNOWN; 584 585 if (kmd->debug) 586 syslog(LOG_DEBUG, "PAM-KRB5 (auth): attempt_krb5_auth:" 587 " User is not part of the local Kerberos" 588 " realm: %s", error_message(code)); 589 break; 590 591 case KRB5KDC_ERR_PREAUTH_FAILED: 592 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 593 /* 594 * We could be trying the password from a previous 595 * pam authentication module, but we don't want to 596 * generate an error if the unix password is different 597 * than the Kerberos password... 598 */ 599 break; 600 601 case KRB5KDC_ERR_KEY_EXP: 602 if (!kmd->err_on_exp) { 603 /* 604 * Request a tik for changepw service 605 * and it will tell us if pw is good or not. 606 */ 607 code = krb5_verifypw(kuser, *krb5_pass, kmd->debug); 608 609 if (kmd->debug) 610 syslog(LOG_DEBUG, 611 "PAM-KRB5 (auth): attempt_krb5_auth: " 612 "verifypw %d", code); 613 614 if (code == 0) { 615 /* pw is good, set age status for acct_mgmt */ 616 kmd->age_status = PAM_NEW_AUTHTOK_REQD; 617 } 618 } 619 break; 620 621 default: 622 result = PAM_SYSTEM_ERR; 623 if (kmd->debug) 624 syslog(LOG_DEBUG, "PAM-KRB5 (auth): error %d - %s", 625 code, error_message(code)); 626 break; 627 } 628 629 if (code == 0) { 630 /* 631 * success for the entered pw 632 * 633 * we can't rely on the pw in PAM_AUTHTOK 634 * to be the (correct) krb5 one so 635 * store krb5 pw in module data for 636 * use in acct_mgmt 637 */ 638 if (!(kmd->password = strdup(*krb5_pass))) { 639 syslog(LOG_ERR, "Cannot strdup password"); 640 result = PAM_BUF_ERR; 641 goto out_err; 642 } 643 result = PAM_SUCCESS; 644 goto out; 645 } 646 647 out_err: 648 /* jump (or reach) here if error and cred cache has been init */ 649 650 if (kmd->debug) 651 syslog(LOG_DEBUG, 652 "PAM-KRB5 (auth): clearing initcreds in " 653 "pam_authenticate()"); 654 655 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 656 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 657 658 out: 659 if (server) 660 krb5_free_principal(kmd->kcontext, server); 661 if (me) 662 krb5_free_principal(kmd->kcontext, me); 663 if (kmd->kcontext) { 664 krb5_free_context(kmd->kcontext); 665 kmd->kcontext = NULL; 666 } 667 668 if (kmd->debug) 669 syslog(LOG_DEBUG, 670 "PAM-KRB5 (auth): attempt_krb5_auth returning %d", 671 result); 672 673 return (kmd->auth_status = result); 674 } 675 676 /*ARGSUSED*/ 677 void 678 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status) 679 { 680 krb5_module_data_t *kmd = (krb5_module_data_t *)data; 681 682 if (kmd == NULL) 683 return; 684 685 if (kmd->debug) { 686 syslog(LOG_DEBUG, 687 dgettext(TEXT_DOMAIN, 688 "PAM-KRB5 (auth): krb5_cleanup auth_status = %d"), 689 kmd->auth_status); 690 } 691 692 /* 693 * if pam_status is PAM_SUCCESS, clean up based on value in 694 * auth_status, otherwise just purge the context 695 */ 696 if ((pam_status == PAM_SUCCESS) && 697 (kmd->auth_status == PAM_SUCCESS) && kmd->ccache) 698 krb5_cc_close(kmd->kcontext, kmd->ccache); 699 700 if (kmd->password) { 701 (void) memset(kmd->password, 0, strlen(kmd->password)); 702 free(kmd->password); 703 } 704 705 if ((pam_status != PAM_SUCCESS) || 706 (kmd->auth_status != PAM_SUCCESS)) { 707 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 708 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 709 } 710 711 free(kmd); 712 } 713