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