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