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