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