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