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; 384 pam_handle_t *pamh = (pam_handle_t *)data; 385 struct pam_conv *pam_convp; 386 struct pam_message *msgs; 387 struct pam_response *ret_respp; 388 int i; 389 krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx); 390 char tmpbuf[PAM_MAX_MSG_SIZE]; 391 392 /* 393 * Because this function should never be used for password prompts, 394 * disallow password prompts. 395 */ 396 for (i = 0; i < num_prompts; i++) { 397 switch (prompt_type[i]) { 398 case KRB5_PROMPT_TYPE_PASSWORD: 399 case KRB5_PROMPT_TYPE_NEW_PASSWORD: 400 case KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN: 401 return (KRB5_LIBOS_CANTREADPWD); 402 } 403 } 404 405 if (num_prompts == 0) { 406 if (prompts) { 407 /* This shouldn't happen */ 408 return (PAM_SYSTEM_ERR); 409 } else { 410 /* no prompts so return */ 411 return (0); 412 } 413 } 414 if ((rc = pam_get_item(pamh, PAM_CONV, (void **)&pam_convp)) 415 != PAM_SUCCESS) { 416 return (rc); 417 } 418 if (pam_convp == NULL) { 419 return (PAM_SYSTEM_ERR); 420 } 421 422 msgs = (struct pam_message *)calloc(num_prompts, 423 sizeof (struct pam_message)); 424 if (msgs == NULL) { 425 return (PAM_BUF_ERR); 426 } 427 (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts); 428 429 for (i = 0; i < num_prompts; i++) { 430 /* convert krb prompt style to PAM style */ 431 if (prompts[i].hidden) { 432 msgs[i].msg_style = PAM_PROMPT_ECHO_OFF; 433 } else { 434 msgs[i].msg_style = PAM_PROMPT_ECHO_ON; 435 } 436 /* 437 * krb expects the prompting function to append ": " to the 438 * prompt string. 439 */ 440 if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ", 441 prompts[i].prompt) < 0) { 442 rc = PAM_BUF_ERR; 443 goto cleanup; 444 } 445 msgs[i].msg = strdup(tmpbuf); 446 if (msgs[i].msg == NULL) { 447 rc = PAM_BUF_ERR; 448 goto cleanup; 449 } 450 } 451 452 /* 453 * Call PAM conv function to display the prompt. 454 */ 455 rc = (pam_convp->conv)(num_prompts, &msgs, &ret_respp, 456 pam_convp->appdata_ptr); 457 458 if (rc == PAM_SUCCESS) { 459 for (i = 0; i < num_prompts; i++) { 460 /* convert PAM response to krb prompt reply format */ 461 prompts[i].reply->length = strlen(ret_respp[i].resp) + 462 1; /* adding 1 for NULL terminator */ 463 prompts[i].reply->data = ret_respp[i].resp; 464 } 465 /* 466 * Note, just free ret_respp, not the resp data since that is 467 * being referenced by the krb prompt reply data pointer. 468 */ 469 free(ret_respp); 470 } 471 472 cleanup: 473 for (i = 0; i < num_prompts; i++) { 474 if (msgs[i].msg) { 475 free(msgs[i].msg); 476 } 477 } 478 free(msgs); 479 return (rc); 480 } 481 482 int 483 attempt_krb5_auth( 484 pam_handle_t *pamh, 485 krb5_module_data_t *kmd, 486 char *user, 487 char **krb5_pass, 488 boolean_t verify_tik) 489 { 490 krb5_principal me = NULL, clientp = NULL; 491 krb5_principal server = NULL, serverp = NULL; 492 krb5_creds *my_creds; 493 krb5_timestamp now; 494 krb5_error_code code = 0; 495 char kuser[2*MAXHOSTNAMELEN]; 496 krb5_deltat lifetime; 497 krb5_deltat rlife; 498 krb5_deltat krb5_max_duration; 499 int options = KRB5_DEFAULT_OPTIONS; 500 krb5_data tgtname = { 501 0, 502 KRB5_TGS_NAME_SIZE, 503 KRB5_TGS_NAME 504 }; 505 krb5_get_init_creds_opt *opts = NULL; 506 krb5_kdc_rep *as_reply = NULL; 507 /* 508 * "result" should not be assigned PAM_SUCCESS unless 509 * authentication has succeeded and there are no other errors. 510 * 511 * "code" is sometimes used for PAM codes, sometimes for krb5 512 * codes. Be careful. 513 */ 514 int result = PAM_AUTH_ERR; 515 516 if (kmd->debug) 517 __pam_log(LOG_AUTH | LOG_DEBUG, 518 "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'", 519 user ? user : "<null>"); 520 521 /* need to free context with krb5_free_context */ 522 if (code = krb5_init_secure_context(&kmd->kcontext)) { 523 __pam_log(LOG_AUTH | LOG_ERR, 524 "PAM-KRB5 (auth): Error initializing " 525 "krb5: %s", 526 error_message(code)); 527 return (PAM_SYSTEM_ERR); 528 } 529 530 if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser, 531 2*MAXHOSTNAMELEN)) != 0) { 532 /* get_kmd_kuser returns proper PAM error statuses */ 533 return (code); 534 } 535 536 if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) { 537 krb5_free_context(kmd->kcontext); 538 kmd->kcontext = NULL; 539 return (PAM_SYSTEM_ERR); 540 } 541 542 /* call krb5_free_cred_contents() on error */ 543 my_creds = &kmd->initcreds; 544 545 if ((code = 546 krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) { 547 result = PAM_SYSTEM_ERR; 548 goto out_err; 549 } 550 clientp = my_creds->client; 551 552 if (code = krb5_build_principal_ext(kmd->kcontext, &server, 553 krb5_princ_realm(kmd->kcontext, me)->length, 554 krb5_princ_realm(kmd->kcontext, me)->data, 555 tgtname.length, tgtname.data, 556 krb5_princ_realm(kmd->kcontext, me)->length, 557 krb5_princ_realm(kmd->kcontext, me)->data, 0)) { 558 __pam_log(LOG_AUTH | LOG_ERR, 559 "PAM-KRB5 (auth): attempt_krb5_auth: " 560 "krb5_build_princ_ext failed: %s", 561 error_message(code)); 562 result = PAM_SYSTEM_ERR; 563 goto out; 564 } 565 566 if (code = krb5_copy_principal(kmd->kcontext, server, 567 &my_creds->server)) { 568 result = PAM_SYSTEM_ERR; 569 goto out_err; 570 } 571 serverp = my_creds->server; 572 573 if (code = krb5_timeofday(kmd->kcontext, &now)) { 574 __pam_log(LOG_AUTH | LOG_ERR, 575 "PAM-KRB5 (auth): attempt_krb5_auth: " 576 "krb5_timeofday failed: %s", 577 error_message(code)); 578 result = PAM_SYSTEM_ERR; 579 goto out; 580 } 581 582 /* 583 * set the values for lifetime and rlife to be the maximum 584 * possible 585 */ 586 krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60; 587 lifetime = krb5_max_duration; 588 rlife = krb5_max_duration; 589 590 /* 591 * Let us get the values for various options 592 * from Kerberos configuration file 593 */ 594 595 krb_realm = krb5_princ_realm(kmd->kcontext, me)->data; 596 profile_get_options_boolean(kmd->kcontext->profile, 597 realmdef, config_option); 598 profile_get_options_boolean(kmd->kcontext->profile, 599 appdef, config_option); 600 profile_get_options_string(kmd->kcontext->profile, 601 realmdef, config_times); 602 profile_get_options_string(kmd->kcontext->profile, 603 appdef, config_times); 604 605 if (renew_timeval) { 606 code = krb5_string_to_deltat(renew_timeval, &rlife); 607 if (code != 0 || rlife == 0 || rlife > krb5_max_duration) { 608 __pam_log(LOG_AUTH | LOG_ERR, 609 "PAM-KRB5 (auth): Bad max_renewable_life " 610 " value '%s' in Kerberos config file", 611 renew_timeval); 612 result = PAM_SYSTEM_ERR; 613 goto out; 614 } 615 } 616 if (life_timeval) { 617 code = krb5_string_to_deltat(life_timeval, &lifetime); 618 if (code != 0 || lifetime == 0 || 619 lifetime > krb5_max_duration) { 620 __pam_log(LOG_AUTH | LOG_ERR, 621 "lifetime value '%s' in Kerberos config file", 622 life_timeval); 623 result = PAM_SYSTEM_ERR; 624 goto out; 625 } 626 } 627 /* start timer when request gets to KDC */ 628 my_creds->times.starttime = 0; 629 my_creds->times.endtime = now + lifetime; 630 631 if (options & KDC_OPT_RENEWABLE) { 632 my_creds->times.renew_till = now + rlife; 633 } else 634 my_creds->times.renew_till = 0; 635 636 code = krb5_get_init_creds_opt_alloc(kmd->kcontext, &opts); 637 if (code != 0) { 638 __pam_log(LOG_AUTH | LOG_ERR, 639 "Error allocating gic opts: %s", 640 error_message(code)); 641 result = PAM_SYSTEM_ERR; 642 goto out; 643 } 644 645 krb5_get_init_creds_opt_set_tkt_life(opts, lifetime); 646 647 if (proxiable_flag) { /* Set in config file */ 648 if (kmd->debug) 649 __pam_log(LOG_AUTH | LOG_DEBUG, 650 "PAM-KRB5 (auth): Proxiable tickets " 651 "requested"); 652 krb5_get_init_creds_opt_set_proxiable(opts, TRUE); 653 } 654 if (forwardable_flag) { 655 if (kmd->debug) 656 __pam_log(LOG_AUTH | LOG_DEBUG, 657 "PAM-KRB5 (auth): Forwardable tickets " 658 "requested"); 659 krb5_get_init_creds_opt_set_forwardable(opts, TRUE); 660 } 661 if (renewable_flag) { 662 if (kmd->debug) 663 __pam_log(LOG_AUTH | LOG_DEBUG, 664 "PAM-KRB5 (auth): Renewable tickets " 665 "requested"); 666 krb5_get_init_creds_opt_set_renew_life(opts, rlife); 667 } 668 if (no_address_flag) { 669 if (kmd->debug) 670 __pam_log(LOG_AUTH | LOG_DEBUG, 671 "PAM-KRB5 (auth): Addressless tickets " 672 "requested"); 673 krb5_get_init_creds_opt_set_address_list(opts, NULL); 674 } 675 676 /* 677 * mech_krb5 interprets empty passwords as NULL passwords and tries to 678 * read a password from stdin. Since we are in pam this is bad and 679 * should not be allowed. 680 * 681 * Note, the logic now is that if the preauth_type is PKINIT then 682 * provide a proper PAMcentric prompt function that the underlying 683 * PKINIT preauth plugin will use to prompt for the PIN. 684 */ 685 if (kmd->preauth_type == KRB_PKINIT) { 686 /* 687 * Do PKINIT preauth 688 * 689 * Note: we want to limit preauth types to just those for PKINIT 690 * but krb5_get_init_creds() doesn't support that at this point. 691 * Instead we rely on pam_krb5_prompter() to limit prompts to 692 * non-password types. So all we can do here is set the preauth 693 * list so krb5_get_init_creds() will try that first. 694 */ 695 krb5_preauthtype pk_pa_list[] = { 696 KRB5_PADATA_PK_AS_REQ, 697 KRB5_PADATA_PK_AS_REQ_OLD 698 }; 699 krb5_get_init_creds_opt_set_preauth_list(opts, pk_pa_list, 2); 700 701 if (*krb5_pass == NULL || strlen(*krb5_pass) != 0) { 702 if (*krb5_pass != NULL) { 703 /* treat the krb5_pass as a PIN */ 704 code = krb5_get_init_creds_opt_set_pa( 705 kmd->kcontext, opts, "PIN", *krb5_pass); 706 } 707 708 if (!code) { 709 code = __krb5_get_init_creds_password( 710 kmd->kcontext, 711 my_creds, 712 me, 713 NULL, /* clear text passwd */ 714 pam_krb5_prompter, /* prompter */ 715 pamh, /* prompter data */ 716 0, /* start time */ 717 NULL, /* defaults to krbtgt@REALM */ 718 opts, 719 &as_reply); 720 } 721 } else { 722 /* invalid PIN */ 723 code = KRB5KRB_AP_ERR_BAD_INTEGRITY; 724 } 725 } else { 726 /* 727 * Do password based preauths 728 * 729 * See earlier PKINIT comment. We are doing something similar 730 * here but we do not pass in a prompter (we assume 731 * pam_authtok_get has already prompted for that). 732 */ 733 if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) { 734 code = KRB5KRB_AP_ERR_BAD_INTEGRITY; 735 } else { 736 krb5_preauthtype pk_pa_list[] = { 737 KRB5_PADATA_ENC_TIMESTAMP 738 }; 739 740 krb5_get_init_creds_opt_set_preauth_list(opts, 741 pk_pa_list, 1); 742 743 /* 744 * We call our own private version of gic_pwd, because 745 * we need more information, such as password/account 746 * expiration, that is found in the as_reply. The 747 * "prompter" interface is not granular enough for PAM 748 * to make use of. 749 */ 750 code = __krb5_get_init_creds_password(kmd->kcontext, 751 my_creds, 752 me, 753 *krb5_pass, /* clear text passwd */ 754 NULL, /* prompter */ 755 NULL, /* data */ 756 0, /* start time */ 757 NULL, /* defaults to krbtgt@REALM */ 758 opts, 759 &as_reply); 760 } 761 } 762 763 if (kmd->debug) 764 __pam_log(LOG_AUTH | LOG_DEBUG, 765 "PAM-KRB5 (auth): attempt_krb5_auth: " 766 "krb5_get_init_creds_password returns: %s", 767 code == 0 ? "SUCCESS" : error_message(code)); 768 769 switch (code) { 770 case 0: 771 /* got a tgt, let's verify it */ 772 if (verify_tik) { 773 krb5_verify_init_creds_opt vopts; 774 775 krb5_principal sp = NULL; 776 char kt_name[MAX_KEYTAB_NAME_LEN]; 777 char *fqdn; 778 779 krb5_verify_init_creds_opt_init(&vopts); 780 781 code = krb5_verify_init_creds(kmd->kcontext, 782 my_creds, 783 NULL, /* defaults to host/localhost@REALM */ 784 NULL, 785 NULL, 786 &vopts); 787 788 if (code) { 789 result = PAM_SYSTEM_ERR; 790 791 /* 792 * Give a better error message when the 793 * keytable entry isn't found or the keytab 794 * file cannot be found. 795 */ 796 if (krb5_sname_to_principal(kmd->kcontext, NULL, 797 NULL, KRB5_NT_SRV_HST, &sp)) 798 fqdn = "<fqdn>"; 799 else 800 fqdn = sp->data[1].data; 801 802 if (krb5_kt_default_name(kmd->kcontext, kt_name, 803 sizeof (kt_name))) 804 (void) strlcpy(kt_name, 805 "default keytab", 806 sizeof (kt_name)); 807 808 switch (code) { 809 case KRB5_KT_NOTFOUND: 810 __pam_log(LOG_AUTH | LOG_ERR, 811 "PAM-KRB5 (auth): " 812 "krb5_verify_init_creds failed:" 813 " Key table entry \"host/%s\"" 814 " not found in %s", 815 fqdn, kt_name); 816 break; 817 case ENOENT: 818 __pam_log(LOG_AUTH | LOG_ERR, 819 "PAM-KRB5 (auth): " 820 "krb5_verify_init_creds failed:" 821 " Keytab file \"%s\"" 822 " does not exist.\n", 823 kt_name); 824 break; 825 default: 826 __pam_log(LOG_AUTH | LOG_ERR, 827 "PAM-KRB5 (auth): " 828 "krb5_verify_init_creds failed:" 829 " %s", 830 error_message(code)); 831 break; 832 } 833 834 if (sp) 835 krb5_free_principal(kmd->kcontext, sp); 836 } 837 } 838 839 if (code == 0) 840 kmd->expiration = as_reply->enc_part2->key_exp; 841 842 break; 843 844 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: 845 /* 846 * Since this principal is not part of the local 847 * Kerberos realm, we just return PAM_USER_UNKNOWN. 848 */ 849 result = PAM_USER_UNKNOWN; 850 851 if (kmd->debug) 852 __pam_log(LOG_AUTH | LOG_DEBUG, 853 "PAM-KRB5 (auth): attempt_krb5_auth:" 854 " User is not part of the local Kerberos" 855 " realm: %s", error_message(code)); 856 break; 857 858 case KRB5KDC_ERR_PREAUTH_FAILED: 859 case KRB5KRB_AP_ERR_BAD_INTEGRITY: 860 /* 861 * We could be trying the password from a previous 862 * pam authentication module, but we don't want to 863 * generate an error if the unix password is different 864 * than the Kerberos password... 865 */ 866 break; 867 868 case KRB5KDC_ERR_KEY_EXP: 869 if (!kmd->err_on_exp) { 870 /* 871 * Request a tik for changepw service and it will tell 872 * us if pw is good or not. If PKINIT is being done it 873 * is possible that *krb5_pass may be NULL so check for 874 * that. If that is the case this function will return 875 * an error. 876 */ 877 if (*krb5_pass != NULL) { 878 code = krb5_verifypw(kuser, *krb5_pass, 879 kmd->debug); 880 if (kmd->debug) { 881 __pam_log(LOG_AUTH | LOG_DEBUG, 882 "PAM-KRB5 (auth): " 883 "attempt_krb5_auth: " 884 "verifypw %d", code); 885 } 886 if (code == 0) { 887 /* 888 * pw is good, set age status for 889 * acct_mgmt. 890 */ 891 kmd->age_status = PAM_NEW_AUTHTOK_REQD; 892 } 893 } 894 895 } 896 break; 897 898 default: 899 result = PAM_SYSTEM_ERR; 900 if (kmd->debug) 901 __pam_log(LOG_AUTH | LOG_DEBUG, 902 "PAM-KRB5 (auth): error %d - %s", 903 code, error_message(code)); 904 break; 905 } 906 907 if (code == 0) { 908 /* 909 * success for the entered pw or PKINIT succeeded. 910 * 911 * we can't rely on the pw in PAM_AUTHTOK 912 * to be the (correct) krb5 one so 913 * store krb5 pw in module data for 914 * use in acct_mgmt. Note that *krb5_pass may be NULL if we're 915 * doing PKINIT. 916 */ 917 if (*krb5_pass != NULL && 918 !(kmd->password = strdup(*krb5_pass))) { 919 __pam_log(LOG_AUTH | LOG_ERR, 920 "Cannot strdup password"); 921 result = PAM_BUF_ERR; 922 goto out_err; 923 } 924 925 result = PAM_SUCCESS; 926 goto out; 927 } 928 929 out_err: 930 /* jump (or reach) here if error and cred cache has been init */ 931 932 if (kmd->debug) 933 __pam_log(LOG_AUTH | LOG_DEBUG, 934 "PAM-KRB5 (auth): clearing initcreds in " 935 "pam_authenticate()"); 936 937 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 938 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 939 940 out: 941 if (server) 942 krb5_free_principal(kmd->kcontext, server); 943 if (me) 944 krb5_free_principal(kmd->kcontext, me); 945 if (as_reply) 946 krb5_free_kdc_rep(kmd->kcontext, as_reply); 947 948 /* 949 * clientp or serverp could be NULL in certain error cases in this 950 * function. mycreds->[client|server] could also be NULL in case 951 * of error in this function, see out_err above. The pointers clientp 952 * and serverp reference the input argument in my_creds for 953 * get_init_creds and must be freed if the input argument does not 954 * match the output argument, which occurs during a successful call 955 * to get_init_creds. 956 */ 957 if (clientp && my_creds->client && clientp != my_creds->client) 958 krb5_free_principal(kmd->kcontext, clientp); 959 if (serverp && my_creds->server && serverp != my_creds->server) 960 krb5_free_principal(kmd->kcontext, serverp); 961 962 if (kmd->kcontext) { 963 krb5_free_context(kmd->kcontext); 964 kmd->kcontext = NULL; 965 } 966 if (opts) 967 krb5_get_init_creds_opt_free(kmd->kcontext, opts); 968 969 if (kmd->debug) 970 __pam_log(LOG_AUTH | LOG_DEBUG, 971 "PAM-KRB5 (auth): attempt_krb5_auth returning %d", 972 result); 973 974 return (kmd->auth_status = result); 975 } 976 977 /*ARGSUSED*/ 978 void 979 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status) 980 { 981 krb5_module_data_t *kmd = (krb5_module_data_t *)data; 982 983 if (kmd == NULL) 984 return; 985 986 if (kmd->debug) { 987 __pam_log(LOG_AUTH | LOG_DEBUG, 988 "PAM-KRB5 (auth): krb5_cleanup auth_status = %d", 989 kmd->auth_status); 990 } 991 992 /* 993 * Apps could be calling pam_end here, so we should always clean 994 * up regardless of success or failure here. 995 */ 996 if (kmd->ccache) 997 (void) krb5_cc_close(kmd->kcontext, kmd->ccache); 998 999 if (kmd->password) { 1000 (void) memset(kmd->password, 0, strlen(kmd->password)); 1001 free(kmd->password); 1002 } 1003 1004 if (kmd->user) 1005 free(kmd->user); 1006 1007 if (kmd->env) 1008 free(kmd->env); 1009 1010 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 1011 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 1012 1013 free(kmd); 1014 } 1015