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 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. 27 */ 28 29 #include <libintl.h> 30 #include <security/pam_appl.h> 31 #include <security/pam_modules.h> 32 #include <string.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <sys/types.h> 36 #include <pwd.h> 37 #include <syslog.h> 38 #include <libintl.h> 39 #include <k5-int.h> 40 #include <netdb.h> 41 #include <unistd.h> 42 #include <sys/stat.h> 43 #include <fcntl.h> 44 #include <errno.h> 45 #include <com_err.h> 46 47 #include "utils.h" 48 #include "krb5_repository.h" 49 50 #define PAMTXD "SUNW_OST_SYSOSPAM" 51 #define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */ 52 53 extern void krb5_cleanup(pam_handle_t *, void *, int); 54 55 static int attempt_refresh_cred(krb5_module_data_t *, const char *, int); 56 static int attempt_delete_initcred(krb5_module_data_t *); 57 static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal, 58 krb5_principal, int); 59 60 extern uint_t kwarn_add_warning(char *, int); 61 extern uint_t kwarn_del_warning(char *); 62 63 /* 64 * pam_sm_setcred 65 */ 66 int 67 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 68 { 69 int i; 70 int err = 0; 71 int debug = 0; 72 krb5_module_data_t *kmd = NULL; 73 const char *user = NULL; 74 krb5_repository_data_t *krb5_data = NULL; 75 const pam_repository_t *rep_data = NULL; 76 77 for (i = 0; i < argc; i++) { 78 if (strcasecmp(argv[i], "debug") == 0) 79 debug = 1; 80 else if (strcasecmp(argv[i], "nowarn") == 0) 81 flags = flags | PAM_SILENT; 82 } 83 84 if (debug) 85 __pam_log(LOG_AUTH | LOG_DEBUG, 86 "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x", 87 flags & PAM_SILENT ? 1 : 0, flags); 88 89 /* make sure flags are valid */ 90 if (flags && 91 !(flags & PAM_ESTABLISH_CRED) && 92 !(flags & PAM_REINITIALIZE_CRED) && 93 !(flags & PAM_REFRESH_CRED) && 94 !(flags & PAM_DELETE_CRED) && 95 !(flags & PAM_SILENT)) { 96 __pam_log(LOG_AUTH | LOG_ERR, 97 "PAM-KRB5 (setcred): illegal flag %d", flags); 98 err = PAM_SYSTEM_ERR; 99 goto out; 100 } 101 102 (void) pam_get_item(pamh, PAM_USER, (const void **)&user); 103 104 if (user == NULL || *user == '\0') 105 return (PAM_USER_UNKNOWN); 106 107 if (pam_get_data(pamh, KRB5_DATA, (const void **)&kmd) != PAM_SUCCESS) { 108 if (debug) { 109 __pam_log(LOG_AUTH | LOG_DEBUG, 110 "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p", 111 kmd); 112 } 113 114 /* 115 * User doesn't need to authenticate for PAM_REFRESH_CRED 116 * or for PAM_DELETE_CRED 117 */ 118 if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) { 119 __pam_log(LOG_AUTH | LOG_DEBUG, 120 "PAM-KRB5 (setcred): inst kmd structure"); 121 122 kmd = calloc(1, sizeof (krb5_module_data_t)); 123 124 if (kmd == NULL) 125 return (PAM_BUF_ERR); 126 127 128 /* 129 * Need to initialize auth_status here to 130 * PAM_AUTHINFO_UNAVAIL else there is a false positive 131 * of PAM_SUCCESS. 132 */ 133 kmd->auth_status = PAM_AUTHINFO_UNAVAIL; 134 135 if ((err = pam_set_data(pamh, KRB5_DATA, 136 kmd, &krb5_cleanup)) != PAM_SUCCESS) { 137 free(kmd); 138 return (PAM_SYSTEM_ERR); 139 } 140 } else { 141 /* 142 * This could mean that we are not the account authority 143 * for the authenticated user. Therefore we should 144 * return PAM_IGNORE in order to not affect the 145 * login process of said user. 146 */ 147 err = PAM_IGNORE; 148 goto out; 149 } 150 151 } else { /* pam_get_data success */ 152 if (kmd == NULL) { 153 if (debug) { 154 __pam_log(LOG_AUTH | LOG_DEBUG, 155 "PAM-KRB5 (setcred): kmd structure" 156 " gotten but is NULL for user %s", user); 157 } 158 err = PAM_SYSTEM_ERR; 159 goto out; 160 } 161 162 if (debug) 163 __pam_log(LOG_AUTH | LOG_DEBUG, 164 "PAM-KRB5 (setcred): kmd auth_status: %s", 165 pam_strerror(pamh, kmd->auth_status)); 166 167 /* 168 * pam_auth has set status to ignore, so we also return ignore 169 */ 170 if (kmd->auth_status == PAM_IGNORE) { 171 err = PAM_IGNORE; 172 goto out; 173 } 174 } 175 176 kmd->debug = debug; 177 178 /* 179 * User must have passed pam_authenticate() 180 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED 181 */ 182 if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) && 183 (kmd->auth_status != PAM_SUCCESS)) { 184 if (kmd->debug) 185 __pam_log(LOG_AUTH | LOG_DEBUG, 186 "PAM-KRB5 (setcred): unable to " 187 "setcreds, not authenticated!"); 188 return (PAM_CRED_UNAVAIL); 189 } 190 191 /* 192 * We cannot assume that kmd->kcontext being non-NULL 193 * means it is valid. Other pam_krb5 mods may have 194 * freed it but not reset it to NULL. 195 * Log a message when debugging to track down memory 196 * leaks. 197 */ 198 if (kmd->kcontext != NULL && kmd->debug) 199 __pam_log(LOG_AUTH | LOG_DEBUG, 200 "PAM-KRB5 (setcred): kcontext != NULL, " 201 "possible memory leak."); 202 203 /* 204 * Use the authenticated and validated user, if applicable. 205 */ 206 if (kmd->user != NULL) 207 user = kmd->user; 208 209 /* 210 * If auth was short-circuited we will not have anything to 211 * renew, so just return here. 212 */ 213 (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&rep_data); 214 215 if (rep_data != NULL) { 216 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 217 if (debug) 218 __pam_log(LOG_AUTH | LOG_DEBUG, 219 "PAM-KRB5 (setcred): wrong" 220 "repository found (%s), returning " 221 "PAM_IGNORE", rep_data->type); 222 return (PAM_IGNORE); 223 } 224 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) { 225 krb5_data = (krb5_repository_data_t *)rep_data->scope; 226 227 if (krb5_data->flags == 228 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED && 229 krb5_data->principal != NULL && 230 strlen(krb5_data->principal)) { 231 if (debug) 232 __pam_log(LOG_AUTH | LOG_DEBUG, 233 "PAM-KRB5 (setcred): " 234 "Principal %s already " 235 "authenticated, " 236 "cannot setcred", 237 krb5_data->principal); 238 return (PAM_SUCCESS); 239 } 240 } 241 } 242 243 if (flags & PAM_REINITIALIZE_CRED) 244 err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED); 245 else if (flags & PAM_REFRESH_CRED) 246 err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED); 247 else if (flags & PAM_DELETE_CRED) 248 err = attempt_delete_initcred(kmd); 249 else { 250 /* 251 * Default case: PAM_ESTABLISH_CRED 252 */ 253 err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED); 254 } 255 256 if (err != PAM_SUCCESS) 257 __pam_log(LOG_AUTH | LOG_ERR, 258 "PAM-KRB5 (setcred): pam_setcred failed " 259 "for %s (%s).", user, pam_strerror(pamh, err)); 260 261 out: 262 if (kmd && kmd->kcontext) { 263 /* 264 * free 'kcontext' field if it is allocated, 265 * kcontext is local to the operation being performed 266 * not considered global to the entire pam module. 267 */ 268 krb5_free_context(kmd->kcontext); 269 kmd->kcontext = NULL; 270 } 271 272 /* 273 * 'kmd' is not freed here, it is handled in krb5_cleanup 274 */ 275 if (debug) 276 __pam_log(LOG_AUTH | LOG_DEBUG, 277 "PAM-KRB5 (setcred): end: %s", 278 pam_strerror(pamh, err)); 279 return (err); 280 } 281 282 static int 283 attempt_refresh_cred( 284 krb5_module_data_t *kmd, 285 const char *user, 286 int flag) 287 { 288 krb5_principal me; 289 krb5_principal server; 290 krb5_error_code code; 291 char kuser[2*MAXHOSTNAMELEN]; 292 krb5_data tgtname = { 293 0, 294 KRB5_TGS_NAME_SIZE, 295 KRB5_TGS_NAME 296 }; 297 298 /* Create a new context here. */ 299 if (krb5_init_secure_context(&kmd->kcontext) != 0) { 300 if (kmd->debug) 301 __pam_log(LOG_AUTH | LOG_DEBUG, 302 "PAM-KRB5 (setcred): unable to " 303 "initialize krb5 context"); 304 return (PAM_SYSTEM_ERR); 305 } 306 307 if (krb5_cc_default(kmd->kcontext, &kmd->ccache) != 0) { 308 return (PAM_SYSTEM_ERR); 309 } 310 311 if ((code = get_kmd_kuser(kmd->kcontext, user, kuser, 312 2 * MAXHOSTNAMELEN)) != 0) { 313 return (code); 314 } 315 316 if (krb5_parse_name(kmd->kcontext, kuser, &me) != 0) { 317 return (PAM_SYSTEM_ERR); 318 } 319 320 if (code = krb5_build_principal_ext(kmd->kcontext, &server, 321 krb5_princ_realm(kmd->kcontext, me)->length, 322 krb5_princ_realm(kmd->kcontext, me)->data, 323 tgtname.length, tgtname.data, 324 krb5_princ_realm(kmd->kcontext, me)->length, 325 krb5_princ_realm(kmd->kcontext, me)->data, 0)) { 326 krb5_free_principal(kmd->kcontext, me); 327 return (PAM_SYSTEM_ERR); 328 } 329 330 code = krb5_renew_tgt(kmd, me, server, flag); 331 332 krb5_free_principal(kmd->kcontext, server); 333 krb5_free_principal(kmd->kcontext, me); 334 335 if (code) { 336 if (kmd->debug) 337 __pam_log(LOG_AUTH | LOG_DEBUG, 338 "PAM-KRB5(setcred): krb5_renew_tgt() " 339 "failed: %s", error_message((errcode_t)code)); 340 return (PAM_CRED_ERR); 341 } else { 342 return (PAM_SUCCESS); 343 } 344 } 345 346 /* 347 * This code will update the credential matching "server" in the user's 348 * credential cache. The flag may be set to one of: 349 * PAM_REINITIALIZE_CRED/PAM_ESTABLISH_CRED - If we have new credentials then 350 * create a new cred cache with these credentials else return failure. 351 * PAM_REFRESH_CRED - If we have new credentials then create a new cred cache 352 * with these credentials else attempt to renew the credentials. 353 * 354 * Note for any of the flags that if a new credential does exist from the 355 * previous auth pass then this will overwrite any existing credentials in the 356 * credential cache. 357 */ 358 static krb5_error_code 359 krb5_renew_tgt( 360 krb5_module_data_t *kmd, 361 krb5_principal me, 362 krb5_principal server, 363 int flag) 364 { 365 krb5_error_code retval; 366 krb5_creds creds; 367 krb5_creds *renewed_cred = NULL; 368 char *client_name = NULL; 369 char *username = NULL; 370 371 #define my_creds (kmd->initcreds) 372 373 if ((flag != PAM_REFRESH_CRED) && 374 (flag != PAM_REINITIALIZE_CRED) && 375 (flag != PAM_ESTABLISH_CRED)) 376 return (KRB5KRB_ERR_GENERIC); 377 378 /* this is needed only for the ktkt_warnd */ 379 if ((retval = krb5_unparse_name(kmd->kcontext, me, &client_name)) != 0) 380 return (retval); 381 382 (void) memset(&creds, 0, sizeof (krb5_creds)); 383 if ((retval = krb5_copy_principal(kmd->kcontext, 384 server, &creds.server))) { 385 if (kmd->debug) 386 __pam_log(LOG_AUTH | LOG_DEBUG, 387 "PAM-KRB5 (setcred): krb5_copy_principal " 388 "failed: %s", 389 error_message((errcode_t)retval)); 390 goto cleanup_creds; 391 } 392 393 /* obtain ticket & session key */ 394 retval = krb5_cc_get_principal(kmd->kcontext, 395 kmd->ccache, &creds.client); 396 if (retval && (kmd->debug)) 397 __pam_log(LOG_AUTH | LOG_DEBUG, 398 "PAM-KRB5 (setcred): User not in cred " 399 "cache (%s)", error_message((errcode_t)retval)); 400 401 /* 402 * We got here either with the ESTABLISH | REINIT | REFRESH flag and 403 * auth_status returns SUCCESS or REFRESH and auth_status failure. 404 * 405 * Rules: 406 * - If the prior auth pass was successful then store the new 407 * credentials in the cache, regardless of which flag. 408 * 409 * - Else if REFRESH flag is used and there are no new 410 * credentials then attempt to refresh the existing credentials. 411 * 412 * - Note, refresh will not work if "R" flag is not set in 413 * original credential. We don't want to 2nd guess the 414 * intention of the person who created the existing credential. 415 */ 416 if (kmd->auth_status == PAM_SUCCESS) { 417 /* 418 * Create a fresh ccache, and store the credentials 419 * we got from pam_authenticate() 420 */ 421 if ((retval = krb5_cc_initialize(kmd->kcontext, 422 kmd->ccache, me)) != 0) { 423 __pam_log(LOG_AUTH | LOG_DEBUG, 424 "PAM-KRB5 (setcred): krb5_cc_initialize " 425 "failed: %s", 426 error_message((errcode_t)retval)); 427 } else if ((retval = krb5_cc_store_cred(kmd->kcontext, 428 kmd->ccache, &my_creds)) != 0) { 429 __pam_log(LOG_AUTH | LOG_DEBUG, 430 "PAM-KRB5 (setcred): krb5_cc_store_cred " 431 "failed: %s", 432 error_message((errcode_t)retval)); 433 } 434 } else if ((retval == 0) && (flag & PAM_REFRESH_CRED)) { 435 /* 436 * If we only wanted to refresh the creds but failed 437 * due to expiration, lack of "R" flag, or other 438 * problems, return an error. 439 */ 440 if (retval = krb5_get_credentials_renew(kmd->kcontext, 441 0, kmd->ccache, &creds, &renewed_cred)) { 442 if (kmd->debug) { 443 __pam_log(LOG_AUTH | LOG_DEBUG, 444 "PAM-KRB5 (setcred): " 445 "krb5_get_credentials" 446 "_renew(update) failed: %s", 447 error_message((errcode_t)retval)); 448 } 449 } 450 } else { 451 /* 452 * We failed to get the user's credentials. 453 * This might be due to permission error on the cache, 454 * or maybe we are looking in the wrong cache file! 455 */ 456 __pam_log(LOG_AUTH | LOG_ERR, 457 "PAM-KRB5 (setcred): Cannot find creds" 458 " for %s (%s)", 459 client_name ? client_name : "(unknown)", 460 error_message((errcode_t)retval)); 461 } 462 463 cleanup_creds: 464 465 if ((retval == 0) && (client_name != NULL)) { 466 /* 467 * Credential update was successful! 468 * 469 * We now chown the ccache to the appropriate uid/gid 470 * combination, if its a FILE based ccache. 471 */ 472 if (!kmd->env || strstr(kmd->env, "FILE:")) { 473 uid_t uuid; 474 gid_t ugid; 475 char *tmpname = NULL; 476 char *filepath = NULL; 477 478 username = strdup(client_name); 479 if (username == NULL) { 480 __pam_log(LOG_AUTH | LOG_ERR, 481 "PAM-KRB5 (setcred): Out of memory"); 482 retval = KRB5KRB_ERR_GENERIC; 483 goto error; 484 } 485 if ((tmpname = strchr(username, '@'))) 486 *tmpname = '\0'; 487 488 if (get_pw_uid(username, &uuid) == 0 || 489 get_pw_gid(username, &ugid) == 0) { 490 __pam_log(LOG_AUTH | LOG_ERR, 491 "PAM-KRB5 (setcred): Unable to " 492 "find matching uid/gid pair for user `%s'", 493 username); 494 retval = KRB5KRB_ERR_GENERIC; 495 goto error; 496 } 497 498 if (!kmd->env) { 499 char buffer[512]; 500 501 if (snprintf(buffer, sizeof (buffer), 502 "%s=FILE:/tmp/krb5cc_%d", KRB5_ENV_CCNAME, 503 (int)uuid) >= sizeof (buffer)) { 504 retval = KRB5KRB_ERR_GENERIC; 505 goto error; 506 } 507 508 /* 509 * We MUST copy this to the heap for the putenv 510 * to work! 511 */ 512 kmd->env = strdup(buffer); 513 if (!kmd->env) { 514 retval = ENOMEM; 515 goto error; 516 } else { 517 if (putenv(kmd->env)) { 518 retval = ENOMEM; 519 goto error; 520 } 521 } 522 } 523 524 /* 525 * We know at this point that kmd->env must start 526 * with the literal string "FILE:". Set filepath 527 * character string to point to ":" 528 */ 529 530 filepath = strchr(kmd->env, ':'); 531 532 /* 533 * Now check if first char after ":" is null char 534 */ 535 if (filepath[1] == '\0') { 536 __pam_log(LOG_AUTH | LOG_ERR, 537 "PAM-KRB5 (setcred): Invalid pathname " 538 "for credential cache of user `%s'", 539 username); 540 retval = KRB5KRB_ERR_GENERIC; 541 goto error; 542 } 543 if (chown(filepath+1, uuid, ugid)) { 544 if (kmd->debug) 545 __pam_log(LOG_AUTH | LOG_DEBUG, 546 "PAM-KRB5 (setcred): chown to user " 547 "`%s' failed for FILE=%s", 548 username, filepath); 549 } 550 } 551 } 552 553 error: 554 if (retval == 0) { 555 krb5_timestamp endtime; 556 557 if (renewed_cred && renewed_cred->times.endtime != 0) 558 endtime = renewed_cred->times.endtime; 559 else 560 endtime = my_creds.times.endtime; 561 562 if (kmd->debug) 563 __pam_log(LOG_AUTH | LOG_DEBUG, 564 "PAM-KRB5 (setcred): delete/add warning"); 565 566 if (kwarn_del_warning(client_name) != 0) { 567 __pam_log(LOG_AUTH | LOG_NOTICE, 568 "PAM-KRB5 (setcred): kwarn_del_warning" 569 " failed: ktkt_warnd(8) down?"); 570 } 571 572 if (kwarn_add_warning(client_name, endtime) != 0) { 573 __pam_log(LOG_AUTH | LOG_NOTICE, 574 "PAM-KRB5 (setcred): kwarn_add_warning" 575 " failed: ktkt_warnd(8) down?"); 576 } 577 } 578 579 if (renewed_cred != NULL) 580 krb5_free_creds(kmd->kcontext, renewed_cred); 581 582 if (client_name != NULL) 583 free(client_name); 584 585 if (username) 586 free(username); 587 588 krb5_free_cred_contents(kmd->kcontext, &creds); 589 590 return (retval); 591 } 592 593 /* 594 * Delete the user's credentials for this session 595 */ 596 static int 597 attempt_delete_initcred(krb5_module_data_t *kmd) 598 { 599 if (kmd == NULL) 600 return (PAM_SUCCESS); 601 602 if (kmd->debug) { 603 __pam_log(LOG_AUTH | LOG_DEBUG, 604 "PAM-KRB5 (setcred): deleting user's " 605 "credentials (initcreds)"); 606 } 607 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 608 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 609 kmd->auth_status = PAM_AUTHINFO_UNAVAIL; 610 return (PAM_SUCCESS); 611 } 612