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