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