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 /* 23 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. 24 */ 25 26 #include <security/pam_appl.h> 27 #include <security/pam_modules.h> 28 #include <security/pam_impl.h> 29 #include <string.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <sys/types.h> 33 #include <sys/stat.h> 34 #include <pwd.h> 35 #include <syslog.h> 36 #include <libintl.h> 37 #include <k5-int.h> 38 #include "profile/prof_int.h" 39 #include <netdb.h> 40 #include <ctype.h> 41 #include "utils.h" 42 #include "krb5_repository.h" 43 44 #define KRB5_DEFAULT_OPTIONS 0 45 46 int forwardable_flag = 0; 47 int renewable_flag = 0; 48 int proxiable_flag = 0; 49 int no_address_flag = 0; 50 profile_options_boolean config_option[] = { 51 { "forwardable", &forwardable_flag, 0 }, 52 { "renewable", &renewable_flag, 0 }, 53 { "proxiable", &proxiable_flag, 0 }, 54 { "no_addresses", &no_address_flag, 0 }, 55 { NULL, NULL, 0 } 56 }; 57 char *renew_timeval; 58 char *life_timeval; 59 profile_option_strings config_times[] = { 60 { "max_life", &life_timeval, 0 }, 61 { "max_renewable_life", &renew_timeval, 0 }, 62 { NULL, NULL, 0 } 63 }; 64 char *realmdef[] = { "realms", NULL, NULL, NULL }; 65 char *appdef[] = { "appdefaults", "kinit", NULL }; 66 67 #define krb_realm (*(realmdef + 1)) 68 69 int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *, 70 char **, boolean_t); 71 void krb5_cleanup(pam_handle_t *, void *, int); 72 73 extern errcode_t profile_get_options_boolean(); 74 extern errcode_t profile_get_options_string(); 75 extern int krb5_verifypw(char *, char *, int); 76 extern krb5_error_code krb5_verify_init_creds(krb5_context, 77 krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *, 78 krb5_verify_init_creds_opt *); 79 extern krb5_error_code __krb5_get_init_creds_password(krb5_context, 80 krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *, 81 krb5_deltat, char *, krb5_get_init_creds_opt *, 82 krb5_kdc_rep **); 83 84 /* 85 * pam_sm_authenticate - Authenticate user 86 */ 87 int 88 pam_sm_authenticate( 89 pam_handle_t *pamh, 90 int flags, 91 int argc, 92 const char **argv) 93 { 94 char *user = NULL; 95 int err; 96 int result = PAM_AUTH_ERR; 97 /* pam.conf options */ 98 int debug = 0; 99 int warn = 1; 100 /* return an error on password expire */ 101 int err_on_exp = 0; 102 int i; 103 char *password = NULL; 104 uid_t pw_uid; 105 krb5_module_data_t *kmd = NULL; 106 krb5_repository_data_t *krb5_data = NULL; 107 pam_repository_t *rep_data = NULL; 108 boolean_t do_pkinit = FALSE; 109 110 for (i = 0; i < argc; i++) { 111 if (strcmp(argv[i], "debug") == 0) { 112 debug = 1; 113 } else if (strcmp(argv[i], "nowarn") == 0) { 114 warn = 0; 115 } else if (strcmp(argv[i], "err_on_exp") == 0) { 116 err_on_exp = 1; 117 } else if (strcmp(argv[i], "pkinit") == 0) { 118 do_pkinit = TRUE; 119 } else { 120 __pam_log(LOG_AUTH | LOG_ERR, 121 "PAM-KRB5 (auth) unrecognized option %s", argv[i]); 122 } 123 } 124 if (flags & PAM_SILENT) warn = 0; 125 126 if (debug) 127 __pam_log(LOG_AUTH | LOG_DEBUG, 128 "PAM-KRB5 (auth): pam_sm_authenticate flags=%d", 129 flags); 130 131 /* 132 * pam_get_data could fail if we are being called for the first time 133 * or if the module is not found, PAM_NO_MODULE_DATA is not an error 134 */ 135 err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd); 136 if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA)) 137 return (PAM_SYSTEM_ERR); 138 139 /* 140 * If pam_krb5 was stacked higher in the auth stack and did PKINIT 141 * preauth sucessfully then this instance is a fallback to password 142 * based preauth and should just return PAM_IGNORE. 143 * 144 * The else clause is handled further down. 145 */ 146 if (kmd != NULL) { 147 if (++(kmd->auth_calls) > 2) { 148 /* 149 * pam_krb5 has been stacked > 2 times in the auth 150 * stack. Clear out the current kmd and proceed as if 151 * this is the first time pam_krb5 auth has been called. 152 */ 153 if (debug) { 154 __pam_log(LOG_AUTH | LOG_DEBUG, 155 "PAM-KRB5 (auth): stacked more than" 156 " two times, clearing kmd"); 157 } 158 /* clear out/free current kmd */ 159 err = pam_set_data(pamh, KRB5_DATA, NULL, NULL); 160 if (err != PAM_SUCCESS) { 161 krb5_cleanup(pamh, kmd, err); 162 result = err; 163 goto out; 164 } 165 kmd = NULL; 166 } else if (kmd->auth_calls == 2 && 167 kmd->auth_status == PAM_SUCCESS) { 168 /* 169 * The previous instance of pam_krb5 succeeded and this 170 * instance was a fall back in case it didn't succeed so 171 * return ignore. 172 */ 173 if (debug) { 174 __pam_log(LOG_AUTH | LOG_DEBUG, 175 "PAM-KRB5 (auth): PKINIT succeeded " 176 "earlier so returning PAM_IGNORE"); 177 } 178 return (PAM_IGNORE); 179 } 180 } 181 182 (void) pam_get_item(pamh, PAM_USER, (void**) &user); 183 184 if (user == NULL || *user == '\0') { 185 if (do_pkinit) { 186 /* 187 * If doing PKINIT it is okay to prompt for the user 188 * name. 189 */ 190 if ((err = pam_get_user(pamh, &user, NULL)) != 191 PAM_SUCCESS) { 192 if (debug) { 193 __pam_log(LOG_AUTH | LOG_DEBUG, 194 "PAM-KRB5 (auth): get user failed: " 195 "%s", pam_strerror(pamh, err)); 196 } 197 return (err); 198 } 199 } else { 200 if (debug) 201 __pam_log(LOG_AUTH | LOG_DEBUG, 202 "PAM-KRB5 (auth): user empty or null"); 203 return (PAM_USER_UNKNOWN); 204 } 205 } 206 207 /* make sure a password entry exists for this user */ 208 if (!get_pw_uid(user, &pw_uid)) 209 return (PAM_USER_UNKNOWN); 210 211 if (kmd == NULL) { 212 kmd = calloc(1, sizeof (krb5_module_data_t)); 213 if (kmd == NULL) { 214 result = PAM_BUF_ERR; 215 goto out; 216 } 217 218 err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup); 219 if (err != PAM_SUCCESS) { 220 free(kmd); 221 result = err; 222 goto out; 223 } 224 } 225 226 if (!kmd->env) { 227 char buffer[512]; 228 229 if (snprintf(buffer, sizeof (buffer), 230 "%s=FILE:/tmp/krb5cc_%d", 231 KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) { 232 result = PAM_SYSTEM_ERR; 233 goto out; 234 } 235 236 /* we MUST copy this to the heap for the putenv to work! */ 237 kmd->env = strdup(buffer); 238 if (!kmd->env) { 239 result = PAM_BUF_ERR; 240 goto out; 241 } else { 242 if (putenv(kmd->env)) { 243 result = PAM_SYSTEM_ERR; 244 goto out; 245 } 246 } 247 } 248 249 if (kmd->user != NULL) 250 free(kmd->user); 251 if ((kmd->user = strdup(user)) == NULL) { 252 result = PAM_BUF_ERR; 253 goto out; 254 } 255 256 kmd->auth_status = PAM_AUTH_ERR; 257 kmd->debug = debug; 258 kmd->warn = warn; 259 kmd->err_on_exp = err_on_exp; 260 kmd->ccache = NULL; 261 kmd->kcontext = NULL; 262 kmd->password = NULL; 263 kmd->age_status = PAM_SUCCESS; 264 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 265 kmd->auth_calls = 1; 266 kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD; 267 268 /* 269 * For apps that already did krb5 auth exchange... 270 * Now that we've created the kmd structure, we can 271 * return SUCCESS. 'kmd' may be needed later by other 272 * PAM functions, thats why we wait until this point to 273 * return. 274 */ 275 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 276 277 if (rep_data != NULL) { 278 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 279 if (debug) 280 __pam_log(LOG_AUTH | LOG_DEBUG, 281 "PAM-KRB5 (auth): wrong" 282 "repository found (%s), returning " 283 "PAM_IGNORE", rep_data->type); 284 return (PAM_IGNORE); 285 } 286 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) { 287 krb5_data = (krb5_repository_data_t *)rep_data->scope; 288 289 if (krb5_data->flags == 290 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED && 291 krb5_data->principal != NULL && 292 strlen(krb5_data->principal)) { 293 if (debug) 294 __pam_log(LOG_AUTH | LOG_DEBUG, 295 "PAM-KRB5 (auth): Principal " 296 "%s already authenticated", 297 krb5_data->principal); 298 kmd->auth_status = PAM_SUCCESS; 299 return (PAM_SUCCESS); 300 } 301 } 302 } 303 304 /* 305 * if root key exists in the keytab, it's a random key so no 306 * need to prompt for pw and we just return IGNORE. 307 * 308 * note we don't need to force a prompt for pw as authtok_get 309 * is required to be stacked above this module. 310 */ 311 if ((strcmp(user, ROOT_UNAME) == 0) && 312 key_in_keytab(user, debug)) { 313 if (debug) 314 __pam_log(LOG_AUTH | LOG_DEBUG, 315 "PAM-KRB5 (auth): " 316 "key for '%s' in keytab, returning IGNORE", user); 317 result = PAM_IGNORE; 318 goto out; 319 } 320 321 (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password); 322 323 result = attempt_krb5_auth(pamh, kmd, user, &password, 1); 324 325 out: 326 if (kmd) { 327 if (debug) 328 __pam_log(LOG_AUTH | LOG_DEBUG, 329 "PAM-KRB5 (auth): pam_sm_auth finalize" 330 " ccname env, result =%d, env ='%s'," 331 " age = %d, status = %d", 332 result, kmd->env ? kmd->env : "<null>", 333 kmd->age_status, kmd->auth_status); 334 335 if (kmd->env && 336 !(kmd->age_status == PAM_NEW_AUTHTOK_REQD && 337 kmd->auth_status == PAM_SUCCESS)) { 338 339 340 if (result == PAM_SUCCESS) { 341 /* 342 * Put ccname into the pamh so that login 343 * apps can pick this up when they run 344 * pam_getenvlist(). 345 */ 346 if ((result = pam_putenv(pamh, kmd->env)) 347 != PAM_SUCCESS) { 348 /* should not happen but... */ 349 __pam_log(LOG_AUTH | LOG_ERR, 350 "PAM-KRB5 (auth):" 351 " pam_putenv failed: result: %d", 352 result); 353 goto cleanupccname; 354 } 355 } else { 356 cleanupccname: 357 /* for lack of a Solaris unputenv() */ 358 krb5_unsetenv(KRB5_ENV_CCNAME); 359 free(kmd->env); 360 kmd->env = NULL; 361 } 362 } 363 kmd->auth_status = result; 364 } 365 366 if (debug) 367 __pam_log(LOG_AUTH | LOG_DEBUG, 368 "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result)); 369 370 return (result); 371 } 372 373 static krb5_error_code 374 pam_krb5_prompter( 375 krb5_context ctx, 376 void *data, 377 /* ARGSUSED1 */ 378 const char *name, 379 const char *banner, 380 int num_prompts, 381 krb5_prompt prompts[]) 382 { 383 krb5_error_code rc = KRB5_LIBOS_CANTREADPWD; 384 pam_handle_t *pamh = (pam_handle_t *)data; 385 struct pam_conv *pam_convp; 386 struct pam_message *msgs = NULL; 387 struct pam_response *ret_respp = NULL; 388 int i; 389 krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx); 390 char tmpbuf[PAM_MAX_MSG_SIZE]; 391 392 if (prompts) { 393 assert(num_prompts > 0); 394 } 395 /* 396 * Because this function should never be used for password prompts, 397 * disallow password prompts. 398 */ 399 for (i = 0; i < num_prompts; i++) { 400 switch (prompt_type[i]) { 401 case KRB5_PROMPT_TYPE_PASSWORD: 402 case KRB5_PROMPT_TYPE_NEW_PASSWORD: 403 case KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN: 404 return (rc); 405 } 406 } 407 408 if (pam_get_item(pamh, PAM_CONV, (void **)&pam_convp) != PAM_SUCCESS) { 409 return (rc); 410 } 411 if (pam_convp == NULL) { 412 return (rc); 413 } 414 415 msgs = (struct pam_message *)calloc(num_prompts, 416 sizeof (struct pam_message)); 417 if (msgs == NULL) { 418 return (rc); 419 } 420 (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts); 421 422 for (i = 0; i < num_prompts; i++) { 423 /* convert krb prompt style to PAM style */ 424 if (prompts[i].hidden) { 425 msgs[i].msg_style = PAM_PROMPT_ECHO_OFF; 426 } else { 427 msgs[i].msg_style = PAM_PROMPT_ECHO_ON; 428 } 429 /* 430 * krb expects the prompting function to append ": " to the 431 * prompt string. 432 */ 433 if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ", 434 prompts[i].prompt) < 0) { 435 goto cleanup; 436 } 437 msgs[i].msg = strdup(tmpbuf); 438 if (msgs[i].msg == NULL) { 439 goto cleanup; 440 } 441 } 442 443 /* 444 * Call PAM conv function to display the prompt. 445 */ 446 447 if ((pam_convp->conv)(num_prompts, &msgs, &ret_respp, 448 pam_convp->appdata_ptr) == PAM_SUCCESS) { 449 for (i = 0; i < num_prompts; i++) { 450 /* convert PAM response to krb prompt reply format */ 451 assert(prompts[i].reply->data != NULL); 452 assert(ret_respp[i].resp != NULL); 453 454 if (strlcpy(prompts[i].reply->data, 455 ret_respp[i].resp, prompts[i].reply->length) >= 456 prompts[i].reply->length) { 457 char errmsg[1][PAM_MAX_MSG_SIZE]; 458 459 (void) snprintf(errmsg[0], PAM_MAX_MSG_SIZE, 460 "%s", dgettext(TEXT_DOMAIN, 461 "Reply too long: ")); 462 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 463 1, errmsg, NULL); 464 goto cleanup; 465 } else { 466 char *retp; 467 468 /* 469 * newline must be replaced with \0 terminator 470 */ 471 retp = strchr(prompts[i].reply->data, '\n'); 472 if (retp != NULL) 473 *retp = '\0'; 474 /* NULL terminator should not be counted */ 475 prompts[i].reply->length = 476 strlen(prompts[i].reply->data); 477 } 478 } 479 rc = 0; 480 } 481 482 cleanup: 483 for (i = 0; i < num_prompts; i++) { 484 if (msgs[i].msg) { 485 free(msgs[i].msg); 486 } 487 if (ret_respp[i].resp) { 488 /* 0 out sensitive data before free() */ 489 (void) memset(ret_respp[i].resp, 0, 490 strlen(ret_respp[i].resp)); 491 free(ret_respp[i].resp); 492 } 493 } 494 free(msgs); 495 free(ret_respp); 496 return (rc); 497 } 498 499 int 500 attempt_krb5_auth( 501 pam_handle_t *pamh, 502 krb5_module_data_t *kmd, 503 char *user, 504 char **krb5_pass, 505 boolean_t verify_tik) 506 { 507 krb5_principal me = NULL, clientp = NULL; 508 krb5_principal server = NULL, serverp = NULL; 509 krb5_creds *my_creds; 510 krb5_timestamp now; 511 krb5_error_code code = 0; 512 char kuser[2*MAXHOSTNAMELEN]; 513 krb5_deltat lifetime; 514 krb5_deltat rlife; 515 krb5_deltat krb5_max_duration; 516 int options = KRB5_DEFAULT_OPTIONS; 517 krb5_data tgtname = { 518 0, 519 KRB5_TGS_NAME_SIZE, 520 KRB5_TGS_NAME 521 }; 522 krb5_get_init_creds_opt *opts = NULL; 523 krb5_kdc_rep *as_reply = NULL; 524 /* 525 * "result" should not be assigned PAM_SUCCESS unless 526 * authentication has succeeded and there are no other errors. 527 * 528 * "code" is sometimes used for PAM codes, sometimes for krb5 529 * codes. Be careful. 530 */ 531 int result = PAM_AUTH_ERR; 532 533 if (kmd->debug) 534 __pam_log(LOG_AUTH | LOG_DEBUG, 535 "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'", 536 user ? user : "<null>"); 537 538 /* need to free context with krb5_free_context */ 539 if (code = krb5_init_secure_context(&kmd->kcontext)) { 540 __pam_log(LOG_AUTH | LOG_ERR, 541 "PAM-KRB5 (auth): Error initializing " 542 "krb5: %s", 543 error_message(code)); 544 return (PAM_SYSTEM_ERR); 545 } 546 547 if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser, 548 2*MAXHOSTNAMELEN)) != 0) { 549 /* get_kmd_kuser returns proper PAM error statuses */ 550 return (code); 551 } 552 553 if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) { 554 krb5_free_context(kmd->kcontext); 555 kmd->kcontext = NULL; 556 return (PAM_SYSTEM_ERR); 557 } 558 559 /* call krb5_free_cred_contents() on error */ 560 my_creds = &kmd->initcreds; 561 562 if ((code = 563 krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) { 564 result = PAM_SYSTEM_ERR; 565 goto out_err; 566 } 567 clientp = my_creds->client; 568 569 if (code = krb5_build_principal_ext(kmd->kcontext, &server, 570 krb5_princ_realm(kmd->kcontext, me)->length, 571 krb5_princ_realm(kmd->kcontext, me)->data, 572 tgtname.length, tgtname.data, 573 krb5_princ_realm(kmd->kcontext, me)->length, 574 krb5_princ_realm(kmd->kcontext, me)->data, 0)) { 575 __pam_log(LOG_AUTH | LOG_ERR, 576 "PAM-KRB5 (auth): attempt_krb5_auth: " 577 "krb5_build_princ_ext failed: %s", 578 error_message(code)); 579 result = PAM_SYSTEM_ERR; 580 goto out; 581 } 582 583 if (code = krb5_copy_principal(kmd->kcontext, server, 584 &my_creds->server)) { 585 result = PAM_SYSTEM_ERR; 586 goto out_err; 587 } 588 serverp = my_creds->server; 589 590 if (code = krb5_timeofday(kmd->kcontext, &now)) { 591 __pam_log(LOG_AUTH | LOG_ERR, 592 "PAM-KRB5 (auth): attempt_krb5_auth: " 593 "krb5_timeofday failed: %s", 594 error_message(code)); 595 result = PAM_SYSTEM_ERR; 596 goto out; 597 } 598 599 /* 600 * set the values for lifetime and rlife to be the maximum 601 * possible 602 */ 603 krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60; 604 lifetime = krb5_max_duration; 605 rlife = krb5_max_duration; 606 607 /* 608 * Let us get the values for various options 609 * from Kerberos configuration file 610 */ 611 612 krb_realm = krb5_princ_realm(kmd->kcontext, me)->data; 613 profile_get_options_boolean(kmd->kcontext->profile, 614 realmdef, config_option); 615 profile_get_options_boolean(kmd->kcontext->profile, 616 appdef, config_option); 617 profile_get_options_string(kmd->kcontext->profile, 618 realmdef, config_times); 619 profile_get_options_string(kmd->kcontext->profile, 620 appdef, config_times); 621 622 if (renew_timeval) { 623 code = krb5_string_to_deltat(renew_timeval, &rlife); 624 if (code != 0 || rlife == 0 || rlife > krb5_max_duration) { 625 __pam_log(LOG_AUTH | LOG_ERR, 626 "PAM-KRB5 (auth): Bad max_renewable_life " 627 " value '%s' in Kerberos config file", 628 renew_timeval); 629 result = PAM_SYSTEM_ERR; 630 goto out; 631 } 632 } 633 if (life_timeval) { 634 code = krb5_string_to_deltat(life_timeval, &lifetime); 635 if (code != 0 || lifetime == 0 || 636 lifetime > krb5_max_duration) { 637 __pam_log(LOG_AUTH | LOG_ERR, 638 "lifetime value '%s' in Kerberos config file", 639 life_timeval); 640 result = PAM_SYSTEM_ERR; 641 goto out; 642 } 643 } 644 /* start timer when request gets to KDC */ 645 my_creds->times.starttime = 0; 646 my_creds->times.endtime = now + lifetime; 647 648 if (options & KDC_OPT_RENEWABLE) { 649 my_creds->times.renew_till = now + rlife; 650 } else 651 my_creds->times.renew_till = 0; 652 653 code = krb5_get_init_creds_opt_alloc(kmd->kcontext, &opts); 654 if (code != 0) { 655 __pam_log(LOG_AUTH | LOG_ERR, 656 "Error allocating gic opts: %s", 657 error_message(code)); 658 result = PAM_SYSTEM_ERR; 659 goto out; 660 } 661 662 krb5_get_init_creds_opt_set_tkt_life(opts, lifetime); 663 664 if (proxiable_flag) { /* Set in config file */ 665 if (kmd->debug) 666 __pam_log(LOG_AUTH | LOG_DEBUG, 667 "PAM-KRB5 (auth): Proxiable tickets " 668 "requested"); 669 krb5_get_init_creds_opt_set_proxiable(opts, TRUE); 670 } 671 if (forwardable_flag) { 672 if (kmd->debug) 673 __pam_log(LOG_AUTH | LOG_DEBUG, 674 "PAM-KRB5 (auth): Forwardable tickets " 675 "requested"); 676 krb5_get_init_creds_opt_set_forwardable(opts, TRUE); 677 } 678 if (renewable_flag) { 679 if (kmd->debug) 680 __pam_log(LOG_AUTH | LOG_DEBUG, 681 "PAM-KRB5 (auth): Renewable tickets " 682 "requested"); 683 krb5_get_init_creds_opt_set_renew_life(opts, rlife); 684 } 685 if (no_address_flag) { 686 if (kmd->debug) 687 __pam_log(LOG_AUTH | LOG_DEBUG, 688 "PAM-KRB5 (auth): Addressless tickets " 689 "requested"); 690 krb5_get_init_creds_opt_set_address_list(opts, NULL); 691 } 692 693 /* 694 * mech_krb5 interprets empty passwords as NULL passwords and tries to 695 * read a password from stdin. Since we are in pam this is bad and 696 * should not be allowed. 697 * 698 * Note, the logic now is that if the preauth_type is PKINIT then 699 * provide a proper PAMcentric prompt function that the underlying 700 * PKINIT preauth plugin will use to prompt for the PIN. 701 */ 702 if (kmd->preauth_type == KRB_PKINIT) { 703 /* 704 * Do PKINIT preauth 705 * 706 * Note: we want to limit preauth types to just those for PKINIT 707 * but krb5_get_init_creds() doesn't support that at this point. 708 * Instead we rely on pam_krb5_prompter() to limit prompts to 709 * non-password types. So all we can do here is set the preauth 710 * list so krb5_get_init_creds() will try that first. 711 */ 712 krb5_preauthtype pk_pa_list[] = { 713 KRB5_PADATA_PK_AS_REQ, 714 KRB5_PADATA_PK_AS_REQ_OLD 715 }; 716 krb5_get_init_creds_opt_set_preauth_list(opts, pk_pa_list, 2); 717 718 if (*krb5_pass == NULL || strlen(*krb5_pass) != 0) { 719 if (*krb5_pass != NULL) { 720 /* treat the krb5_pass as a PIN */ 721 code = krb5_get_init_creds_opt_set_pa( 722 kmd->kcontext, opts, "PIN", *krb5_pass); 723 } 724 725 if (!code) { 726 code = __krb5_get_init_creds_password( 727 kmd->kcontext, 728 my_creds, 729 me, 730 NULL, /* clear text passwd */ 731 pam_krb5_prompter, /* prompter */ 732 pamh, /* prompter data */ 733 0, /* start time */ 734 NULL, /* defaults to krbtgt@REALM */ 735 opts, 736 &as_reply); 737 } 738 } else { 739 /* invalid PIN */ 740 code = KRB5KRB_AP_ERR_BAD_INTEGRITY; 741 } 742 } else { 743 /* 744 * Do password based preauths 745 * 746 * See earlier PKINIT comment. We are doing something similar 747 * here but we do not pass in a prompter (we assume 748 * pam_authtok_get has already prompted for that). 749 */ 750 if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { 751 code = KRB5KRB_AP_ERR_BAD_INTEGRITY; 752 } else { 753 krb5_preauthtype pk_pa_list[] = { 754 KRB5_PADATA_ENC_TIMESTAMP 755 }; 756 757 krb5_get_init_creds_opt_set_preauth_list(opts, 758 pk_pa_list, 1); 759 760 /* 761 * We call our own private version of gic_pwd, because 762 * we need more information, such as password/account 763 * expiration, that is found in the as_reply. The 764 * "prompter" interface is not granular enough for PAM 765 * to make use of. 766 */ 767 code = __krb5_get_init_creds_password(kmd->kcontext, 768 my_creds, 769 me, 770 *krb5_pass, /* clear text passwd */ 771 NULL, /* prompter */ 772 NULL, /* data */ 773 0, /* start time */ 774 NULL, /* defaults to krbtgt@REALM */ 775 opts, 776 &as_reply); 777 } 778 } 779 780 if (kmd->debug) 781 __pam_log(LOG_AUTH | LOG_DEBUG, 782 "PAM-KRB5 (auth): attempt_krb5_auth: " 783 "krb5_get_init_creds_password returns: %s", 784 code == 0 ? "SUCCESS" : error_message(code)); 785 786 switch (code) { 787 case 0: 788 /* got a tgt, let's verify it */ 789 if (verify_tik) { 790 krb5_verify_init_creds_opt vopts; 791 792 krb5_principal sp = NULL; 793 char kt_name[MAX_KEYTAB_NAME_LEN]; 794 char *fqdn; 795 796 krb5_verify_init_creds_opt_init(&vopts); 797 798 code = krb5_verify_init_creds(kmd->kcontext, 799 my_creds, 800 NULL, /* defaults to host/localhost@REALM */ 801 NULL, 802 NULL, 803 &vopts); 804 805 if (code) { 806 result = PAM_SYSTEM_ERR; 807 808 /* 809 * Give a better error message when the 810 * keytable entry isn't found or the keytab 811 * file cannot be found. 812 */ 813 if (krb5_sname_to_principal(kmd->kcontext, NULL, 814 NULL, KRB5_NT_SRV_HST, &sp)) 815 fqdn = "<fqdn>"; 816 else 817 fqdn = sp->data[1].data; 818 819 if (krb5_kt_default_name(kmd->kcontext, kt_name, 820 sizeof (kt_name))) 821 (void) strlcpy(kt_name, 822 "default keytab", 823 sizeof (kt_name)); 824 825 switch (code) { 826 case KRB5_KT_NOTFOUND: 827 __pam_log(LOG_AUTH | LOG_ERR, 828 "PAM-KRB5 (auth): " 829 "krb5_verify_init_creds failed:" 830 " Key table entry \"host/%s\"" 831 " not found in %s", 832 fqdn, kt_name); 833 break; 834 case ENOENT: 835 __pam_log(LOG_AUTH | LOG_ERR, 836 "PAM-KRB5 (auth): " 837 "krb5_verify_init_creds failed:" 838 " Keytab file \"%s\"" 839 " does not exist.\n", 840 kt_name); 841 break; 842 default: 843 __pam_log(LOG_AUTH | LOG_ERR, 844 "PAM-KRB5 (auth): " 845 "krb5_verify_init_creds failed:" 846 " %s", 847 error_message(code)); 848 break; 849 } 850 851 if (sp) 852 krb5_free_principal(kmd->kcontext, sp); 853 } 854 } 855 856 if (code == 0) 857 kmd->expiration = as_reply->enc_part2->key_exp; 858 859 break; 860 861 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: 862 /* 863 * Since this principal is not part of the local 864 * Kerberos realm, we just return PAM_USER_UNKNOWN. 865 */ 866 result = PAM_USER_UNKNOWN; 867 868 if (kmd->debug) 869 __pam_log(LOG_AUTH | LOG_DEBUG, 870 "PAM-KRB5 (auth): attempt_krb5_auth:" 871 " User is not part of the local Kerberos" 872 " realm: %s", error_message(code)); 873 break; 874 875 case KRB5KDC_ERR_PREAUTH_FAILED: 876 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 877 /* 878 * We could be trying the password from a previous 879 * pam authentication module, but we don't want to 880 * generate an error if the unix password is different 881 * than the Kerberos password... 882 */ 883 break; 884 885 case KRB5KDC_ERR_KEY_EXP: 886 if (!kmd->err_on_exp) { 887 /* 888 * Request a tik for changepw service and it will tell 889 * us if pw is good or not. If PKINIT is being done it 890 * is possible that *krb5_pass may be NULL so check for 891 * that. If that is the case this function will return 892 * an error. 893 */ 894 if (*krb5_pass != NULL) { 895 code = krb5_verifypw(kuser, *krb5_pass, 896 kmd->debug); 897 if (kmd->debug) { 898 __pam_log(LOG_AUTH | LOG_DEBUG, 899 "PAM-KRB5 (auth): " 900 "attempt_krb5_auth: " 901 "verifypw %d", code); 902 } 903 if (code == 0) { 904 /* 905 * pw is good, set age status for 906 * acct_mgmt. 907 */ 908 kmd->age_status = PAM_NEW_AUTHTOK_REQD; 909 } 910 } 911 912 } 913 break; 914 915 default: 916 result = PAM_SYSTEM_ERR; 917 if (kmd->debug) 918 __pam_log(LOG_AUTH | LOG_DEBUG, 919 "PAM-KRB5 (auth): error %d - %s", 920 code, error_message(code)); 921 break; 922 } 923 924 if (code == 0) { 925 /* 926 * success for the entered pw or PKINIT succeeded. 927 * 928 * we can't rely on the pw in PAM_AUTHTOK 929 * to be the (correct) krb5 one so 930 * store krb5 pw in module data for 931 * use in acct_mgmt. Note that *krb5_pass may be NULL if we're 932 * doing PKINIT. 933 */ 934 if (*krb5_pass != NULL && 935 !(kmd->password = strdup(*krb5_pass))) { 936 __pam_log(LOG_AUTH | LOG_ERR, 937 "Cannot strdup password"); 938 result = PAM_BUF_ERR; 939 goto out_err; 940 } 941 942 result = PAM_SUCCESS; 943 goto out; 944 } 945 946 out_err: 947 /* jump (or reach) here if error and cred cache has been init */ 948 949 if (kmd->debug) 950 __pam_log(LOG_AUTH | LOG_DEBUG, 951 "PAM-KRB5 (auth): clearing initcreds in " 952 "pam_authenticate()"); 953 954 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 955 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 956 957 out: 958 if (server) 959 krb5_free_principal(kmd->kcontext, server); 960 if (me) 961 krb5_free_principal(kmd->kcontext, me); 962 if (as_reply) 963 krb5_free_kdc_rep(kmd->kcontext, as_reply); 964 965 /* 966 * clientp or serverp could be NULL in certain error cases in this 967 * function. mycreds->[client|server] could also be NULL in case 968 * of error in this function, see out_err above. The pointers clientp 969 * and serverp reference the input argument in my_creds for 970 * get_init_creds and must be freed if the input argument does not 971 * match the output argument, which occurs during a successful call 972 * to get_init_creds. 973 */ 974 if (clientp && my_creds->client && clientp != my_creds->client) 975 krb5_free_principal(kmd->kcontext, clientp); 976 if (serverp && my_creds->server && serverp != my_creds->server) 977 krb5_free_principal(kmd->kcontext, serverp); 978 979 if (kmd->kcontext) { 980 krb5_free_context(kmd->kcontext); 981 kmd->kcontext = NULL; 982 } 983 if (opts) 984 krb5_get_init_creds_opt_free(kmd->kcontext, opts); 985 986 if (kmd->debug) 987 __pam_log(LOG_AUTH | LOG_DEBUG, 988 "PAM-KRB5 (auth): attempt_krb5_auth returning %d", 989 result); 990 991 return (kmd->auth_status = result); 992 } 993 994 /*ARGSUSED*/ 995 void 996 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status) 997 { 998 krb5_module_data_t *kmd = (krb5_module_data_t *)data; 999 1000 if (kmd == NULL) 1001 return; 1002 1003 if (kmd->debug) { 1004 __pam_log(LOG_AUTH | LOG_DEBUG, 1005 "PAM-KRB5 (auth): krb5_cleanup auth_status = %d", 1006 kmd->auth_status); 1007 } 1008 1009 /* 1010 * Apps could be calling pam_end here, so we should always clean 1011 * up regardless of success or failure here. 1012 */ 1013 if (kmd->ccache) 1014 (void) krb5_cc_close(kmd->kcontext, kmd->ccache); 1015 1016 if (kmd->password) { 1017 (void) memset(kmd->password, 0, strlen(kmd->password)); 1018 free(kmd->password); 1019 } 1020 1021 if (kmd->user) 1022 free(kmd->user); 1023 1024 if (kmd->env) 1025 free(kmd->env); 1026 1027 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 1028 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 1029 1030 free(kmd); 1031 } 1032