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