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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 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 <krb5.h> 40 #include <netdb.h> 41 #include <unistd.h> 42 #include <sys/stat.h> 43 #include <fcntl.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 static krb5_boolean creds_match(krb5_context, const krb5_creds *, 59 const krb5_creds *); 60 61 extern uint_t kwarn_add_warning(char *, int); 62 extern uint_t kwarn_del_warning(char *); 63 64 /* 65 * pam_sm_setcred 66 */ 67 int 68 pam_sm_setcred( 69 pam_handle_t *pamh, 70 int flags, 71 int argc, 72 const char **argv) 73 { 74 int i; 75 int err = 0; 76 int debug = 0; 77 krb5_module_data_t *kmd = NULL; 78 char *user; 79 int result; 80 krb5_repository_data_t *krb5_data = NULL; 81 pam_repository_t *rep_data = NULL; 82 83 for (i = 0; i < argc; i++) { 84 if (strcasecmp(argv[i], "debug") == 0) 85 debug = 1; 86 else if (strcasecmp(argv[i], "nowarn") == 0) 87 flags = flags | PAM_SILENT; 88 } 89 90 if (debug) 91 syslog(LOG_DEBUG, 92 "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x", 93 flags & PAM_SILENT ? 1 : 0, flags); 94 95 /* make sure flags are valid */ 96 if (flags && 97 !(flags & PAM_ESTABLISH_CRED) && 98 !(flags & PAM_REINITIALIZE_CRED) && 99 !(flags & PAM_REFRESH_CRED) && 100 !(flags & PAM_DELETE_CRED) && 101 !(flags & PAM_SILENT)) { 102 syslog(LOG_ERR, 103 dgettext(TEXT_DOMAIN, 104 "PAM-KRB5 (setcred): illegal flag %d"), flags); 105 err = PAM_SYSTEM_ERR; 106 goto out; 107 } 108 109 err = pam_get_item(pamh, PAM_USER, (void**) &user); 110 if (err != PAM_SUCCESS) 111 return (err); 112 113 if (user == NULL || !user[0]) 114 return (PAM_AUTH_ERR); 115 116 if (pam_get_data(pamh, KRB5_DATA, (const void**)&kmd) != PAM_SUCCESS) { 117 if (debug) { 118 syslog(LOG_DEBUG, 119 "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p", 120 kmd); 121 } 122 123 /* 124 * User doesn't need to authenticate for PAM_REFRESH_CRED 125 * or for PAM_DELETE_CRED 126 */ 127 if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) { 128 syslog(LOG_DEBUG, 129 "PAM-KRB5 (setcred): inst kmd structure"); 130 131 kmd = calloc(1, sizeof (krb5_module_data_t)); 132 133 if (kmd == NULL) { 134 result = PAM_BUF_ERR; 135 return (result); 136 } 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 err = PAM_CRED_UNAVAIL; 145 goto out; 146 } 147 148 } else { /* pam_get_data success */ 149 if (kmd == NULL) { 150 if (debug) { 151 syslog(LOG_DEBUG, 152 "PAM-KRB5 (setcred): kmd structure" 153 " gotten but is NULL for user %s", user); 154 } 155 err = PAM_CRED_UNAVAIL; 156 goto out; 157 } 158 159 if (debug) 160 syslog(LOG_DEBUG, 161 "PAM-KRB5 (setcred): kmd auth_status: %s", 162 pam_strerror(pamh, kmd->auth_status)); 163 164 /* 165 * pam_auth has set status to ignore, so we also return ignore 166 */ 167 if (kmd->auth_status == PAM_IGNORE) { 168 err = PAM_IGNORE; 169 goto out; 170 } 171 } 172 173 kmd->debug = debug; 174 175 176 /* 177 * User must have passed pam_authenticate() 178 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED 179 */ 180 if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) && 181 (kmd->auth_status != PAM_SUCCESS)) { 182 if (kmd->debug) 183 syslog(LOG_DEBUG, 184 "PAM-KRB5 (setcred): unable to " 185 "setcreds, not authenticated!"); 186 return (PAM_CRED_UNAVAIL); 187 } 188 189 /* 190 * We cannot assume that kmd->kcontext being non-NULL 191 * means it is valid. Other pam_krb5 mods may have 192 * freed it but not reset it to NULL. 193 * Log a message when debugging to track down memory 194 * leaks. 195 */ 196 if (kmd->kcontext != NULL && kmd->debug) 197 syslog(LOG_DEBUG, 198 "PAM-KRB5 (setcred): kcontext != NULL, " 199 "possible memory leak."); 200 201 /* 202 * If auth was short-circuited we will not have anything to 203 * renew, so just return here. 204 */ 205 err = pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 206 if (rep_data != NULL) { 207 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 208 if (debug) 209 syslog(LOG_DEBUG, "PAM-KRB5 (setcred): wrong" 210 "repository found (%s), returning " 211 "PAM_IGNORE", rep_data->type); 212 return (PAM_IGNORE); 213 } 214 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) { 215 krb5_data = (krb5_repository_data_t *)rep_data->scope; 216 217 if (krb5_data->flags == 218 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED && 219 krb5_data->principal != NULL && 220 strlen(krb5_data->principal)) { 221 if (debug) 222 syslog(LOG_DEBUG, 223 "PAM-KRB5 (setcred): " 224 "Principal %s already " 225 "authenticated, " 226 "cannot setcred", 227 krb5_data->principal); 228 return (PAM_SUCCESS); 229 } 230 } 231 } 232 233 if (flags & PAM_REINITIALIZE_CRED) 234 err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED); 235 else if (flags & PAM_REFRESH_CRED) 236 err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED); 237 else if (flags & PAM_DELETE_CRED) 238 err = attempt_delete_initcred(kmd); 239 else { 240 /* 241 * Default case: PAM_ESTABLISH_CRED 242 */ 243 err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED); 244 } 245 246 if (err) 247 syslog(LOG_ERR, 248 "PAM-KRB5 (setcred): pam_setcred failed " 249 "for %s (%s).", user, pam_strerror(pamh, err)); 250 251 out: 252 if (kmd && kmd->kcontext) { 253 /* 254 * free 'kcontext' field if it is allocated, 255 * kcontext is local to the operation being performed 256 * not considered global to the entire pam module. 257 */ 258 krb5_free_context(kmd->kcontext); 259 kmd->kcontext = NULL; 260 } 261 262 /* 263 * 'kmd' is not freed here, it is handled in krb5_cleanup 264 */ 265 266 267 if (debug) 268 syslog(LOG_DEBUG, 269 "PAM-KRB5 (setcred): end: %s", 270 pam_strerror(pamh, err)); 271 return (err); 272 } 273 274 static int 275 attempt_refresh_cred( 276 krb5_module_data_t *kmd, 277 char *user, 278 int flag) 279 { 280 krb5_principal me; 281 krb5_principal server; 282 krb5_error_code code; 283 char kuser[2*MAXHOSTNAMELEN]; 284 krb5_data tgtname = { 285 0, 286 KRB5_TGS_NAME_SIZE, 287 KRB5_TGS_NAME 288 }; 289 290 /* User must have passed pam_authenticate() */ 291 if (kmd->auth_status != PAM_SUCCESS) { 292 if (kmd->debug) 293 syslog(LOG_DEBUG, 294 "PAM-KRB5 (setcred): unable to " 295 "setcreds, not authenticated!"); 296 return (PAM_CRED_UNAVAIL); 297 } 298 299 /* Create a new context here. */ 300 if (krb5_init_context(&kmd->kcontext) != 0) { 301 if (kmd->debug) 302 syslog(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_CRED_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_CRED_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 code = PAM_CRED_ERR; 328 goto out; 329 } 330 331 code = krb5_renew_tgt(kmd, me, server, flag); 332 333 out: 334 if (server) 335 krb5_free_principal(kmd->kcontext, server); 336 if (me) 337 krb5_free_principal(kmd->kcontext, me); 338 339 if (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_ESTABLISH_CRED - Create a new cred cache if one doesnt exist, 350 * else refresh the existing one. 351 * PAM_REINITIALIZE_CRED - destroy current cred cache and create a new one 352 * PAM_REFRESH_CRED - update the existing cred cache (default action) 353 */ 354 static krb5_error_code 355 krb5_renew_tgt( 356 krb5_module_data_t *kmd, 357 krb5_principal me, 358 krb5_principal server, 359 int flag) 360 { 361 krb5_error_code retval; 362 krb5_creds creds; 363 krb5_creds *credsp = &creds; 364 char *client_name = NULL; 365 typedef struct _cred_node { 366 krb5_creds *creds; 367 struct _cred_node *next; 368 } cred_node; 369 cred_node *cred_list_head = NULL; 370 cred_node *fetched = 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 (PAM_SYSTEM_ERR); 378 379 /* this is needed only for the ktkt_warnd */ 380 if (krb5_unparse_name(kmd->kcontext, me, &client_name) != 0) { 381 krb5_free_principal(kmd->kcontext, me); 382 return (PAM_CRED_ERR); 383 } 384 385 (void) memset((char *)credsp, 0, sizeof (krb5_creds)); 386 if ((retval = krb5_copy_principal(kmd->kcontext, 387 server, &credsp->server))) { 388 if (kmd->debug) 389 syslog(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, &credsp->client); 399 if (retval && (kmd->debug)) 400 syslog(LOG_DEBUG, 401 dgettext(TEXT_DOMAIN, 402 "PAM-KRB5 (setcred): User not in cred " 403 "cache (%s)"), error_message((errcode_t)retval)); 404 405 if ((retval == KRB5_FCC_NOFILE) && 406 (flag & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED))) { 407 /* 408 * Create a fresh ccache, and store the credentials 409 * we got from pam_authenticate() 410 */ 411 if ((retval = krb5_cc_initialize(kmd->kcontext, 412 kmd->ccache, me)) != 0) { 413 syslog(LOG_DEBUG, 414 "PAM-KRB5 (setcred): krb5_cc_initialize " 415 "failed: %s", 416 error_message((errcode_t)retval)); 417 goto cleanup_creds; 418 } else if ((retval = krb5_cc_store_cred(kmd->kcontext, 419 kmd->ccache, &my_creds)) != 0) { 420 syslog(LOG_DEBUG, 421 "PAM-KRB5 (setcred): krb5_cc_store_cred " 422 "failed: %s", 423 error_message((errcode_t)retval)); 424 goto cleanup_creds; 425 } 426 } else if (retval) { 427 /* 428 * We failed to get the user's credentials. 429 * This might be due to permission error on the cache, 430 * or maybe we are looking in the wrong cache file! 431 */ 432 syslog(LOG_ERR, 433 dgettext(TEXT_DOMAIN, 434 "PAM-KRB5 (setcred): Cannot find creds" 435 " for %s (%s)"), 436 client_name ? client_name : "(unknown)", 437 error_message((errcode_t)retval)); 438 439 } else if (flag & PAM_REINITIALIZE_CRED) { 440 /* 441 * This destroys the credential cache, and stores a new 442 * krbtgt with updated startime, endtime and renewable 443 * lifetime. 444 */ 445 creds.times.starttime = my_creds.times.starttime; 446 creds.times.endtime = my_creds.times.endtime; 447 creds.times.renew_till = my_creds.times.renew_till; 448 if ((retval = krb5_get_credentials_renew(kmd->kcontext, 0, 449 kmd->ccache, &creds, &credsp))) { 450 if (kmd->debug) 451 syslog(LOG_DEBUG, 452 "PAM-KRB5 (setcred): krb5_get_credentials", 453 "_renew(reinitialize) failed: %s", 454 error_message((errcode_t)retval)); 455 /* perhaps the tgt lifetime has expired */ 456 if ((retval = krb5_cc_initialize(kmd->kcontext, 457 kmd->ccache, me)) != 0) { 458 goto cleanup_creds; 459 } else if ((retval = krb5_cc_store_cred(kmd->kcontext, 460 kmd->ccache, &my_creds)) != 0) { 461 goto cleanup_creds; 462 } 463 } 464 } else { 465 /* 466 * Creds already exist, update them if possible. 467 * We got here either with the ESTABLISH or REFRESH flag. 468 * 469 * The credential cache does exist, and we are going to 470 * read in each cred, looking for our own. When we find 471 * a matching credential, we will update it, and store it. 472 * Any nonmatching credentials are stored as is. 473 * 474 * Rules: 475 * TGT must exist in cache to get to this point. 476 * if flag == ESTABLISH 477 * refresh it if possible, else overwrite 478 * with new TGT, other tickets in cache remain 479 * unchanged. 480 * else if flag == REFRESH 481 * refresh it if possible, else return error. 482 * - Will not work if "R" flag is not set in 483 * original cred, we dont want to 2nd guess the 484 * intention of the person who created the 485 * existing TGT. 486 * 487 */ 488 krb5_cc_cursor cursor; 489 krb5_creds nextcred; 490 boolean_t found = 0; 491 492 if ((retval = krb5_cc_start_seq_get(kmd->kcontext, 493 kmd->ccache, &cursor)) != 0) 494 goto cleanup_creds; 495 496 while ((krb5_cc_next_cred(kmd->kcontext, kmd->ccache, 497 &cursor, &nextcred) == 0)) { 498 /* if two creds match, we just update the first */ 499 if ((!found) && (creds_match(kmd->kcontext, 500 &nextcred, &creds))) { 501 /* 502 * Mark it as found, don't store it 503 * in the list or else it will be 504 * stored twice later. 505 */ 506 found = 1; 507 } else { 508 /* 509 * Add a new node to the list 510 * of creds that must be replaced 511 * in the cache later. 512 */ 513 cred_node *newnode = (cred_node *)malloc( 514 sizeof (cred_node)); 515 if (newnode == NULL) { 516 retval = ENOMEM; 517 goto cleanup_creds; 518 } 519 newnode->creds = NULL; 520 newnode->next = NULL; 521 522 if (cred_list_head == NULL) { 523 cred_list_head = newnode; 524 fetched = cred_list_head; 525 } else { 526 fetched->next = newnode; 527 fetched = fetched->next; 528 } 529 retval = krb5_copy_creds(kmd->kcontext, 530 &nextcred, &fetched->creds); 531 if (retval) 532 goto cleanup_creds; 533 } 534 } 535 536 if ((retval = krb5_cc_end_seq_get(kmd->kcontext, 537 kmd->ccache, &cursor)) != 0) 538 goto cleanup_creds; 539 540 /* 541 * If we found a matching cred, renew it. 542 * This destroys the credential cache, if and only 543 * if it passes. 544 */ 545 if (found && 546 (retval = krb5_get_credentials_renew(kmd->kcontext, 547 0, kmd->ccache, &creds, &credsp))) { 548 if (kmd->debug) 549 syslog(LOG_DEBUG, 550 "PAM-KRB5 (setcred): krb5_get_credentials" 551 "_renew(update) failed: %s", 552 error_message((errcode_t)retval)); 553 /* 554 * If we only wanted to refresh the creds but failed 555 * due to expiration, lack of "R" flag, or other 556 * problems, return an error. If we were trying to 557 * establish new creds, add them to the cache. 558 */ 559 if ((retval = krb5_cc_initialize(kmd->kcontext, 560 kmd->ccache, me)) != 0) { 561 goto cleanup_creds; 562 } else if ((retval = krb5_cc_store_cred(kmd->kcontext, 563 kmd->ccache, &my_creds)) != 0) { 564 goto cleanup_creds; 565 } 566 } 567 /* 568 * If no matching creds were found, we must 569 * initialize the cache before we can store stuff 570 * in it. 571 */ 572 if (!found) { 573 if ((retval = krb5_cc_initialize(kmd->kcontext, 574 kmd->ccache, me)) != 0) { 575 goto cleanup_creds; 576 } 577 } 578 579 /* now store all the other tickets */ 580 fetched = cred_list_head; 581 while (fetched != NULL) { 582 retval = krb5_cc_store_cred(kmd->kcontext, 583 kmd->ccache, fetched->creds); 584 fetched = fetched->next; 585 if (retval) { 586 if (kmd->debug) 587 syslog(LOG_DEBUG, 588 "PAM-KRB5(setcred): krb5_cc_store_cred() " 589 "failed: %s", 590 error_message((errcode_t)retval)); 591 goto cleanup_creds; 592 } 593 } 594 } 595 596 cleanup_creds: 597 /* Cleanup the list of creds read from the cache if necessary */ 598 fetched = cred_list_head; 599 while (fetched != NULL) { 600 cred_node *old = fetched; 601 /* Free the contents and the cred structure itself */ 602 krb5_free_creds(kmd->kcontext, fetched->creds); 603 fetched = fetched->next; 604 free(old); 605 } 606 607 if ((retval == 0) && (client_name != NULL)) { 608 /* 609 * Credential update was successful! 610 * 611 * We now chown the ccache to the appropriate uid/gid 612 * combination, if its a FILE based ccache. 613 */ 614 if (strstr(kmd->env, "FILE:")) { 615 uid_t uuid; 616 gid_t ugid; 617 char *username = NULL, *tmpname = NULL; 618 char *filepath = NULL; 619 620 username = strdup(client_name); 621 if ((tmpname = strchr(username, '@'))) 622 *tmpname = '\0'; 623 624 if (get_pw_uid(username, &uuid) == 0 || 625 get_pw_gid(username, &ugid) == 0) { 626 syslog(LOG_ERR, "PAM-KRB5 (setcred): Unable to " 627 "find matching uid/gid pair for user `%s'", 628 username); 629 return (PAM_SYSTEM_ERR); 630 } 631 if (!(filepath = strchr(kmd->env, ':')) || 632 !(filepath+1)) { 633 syslog(LOG_ERR, 634 "PAM-KRB5 (setcred): Invalid pathname " 635 "for credential cache of user `%s'", 636 username); 637 return (PAM_SYSTEM_ERR); 638 } 639 if (chown(filepath+1, uuid, ugid)) { 640 if (kmd->debug) 641 syslog(LOG_DEBUG, 642 "PAM-KRB5 (setcred): chown to user " 643 "`%s' failed for FILE=%s", 644 username, filepath); 645 } 646 647 free(username); 648 } 649 650 if (creds.times.endtime != 0) { 651 kwarn_del_warning(client_name); 652 if (kwarn_add_warning(client_name, 653 creds.times.endtime) != 0) { 654 syslog(LOG_NOTICE, dgettext(TEXT_DOMAIN, 655 "PAM-KRB5 (auth): kwarn_add_warning" 656 " failed: ktkt_warnd(1M) down?")); 657 } 658 } 659 } 660 if (client_name != NULL) 661 free(client_name); 662 663 krb5_free_cred_contents(kmd->kcontext, &creds); 664 665 return (retval); 666 } 667 668 static krb5_boolean 669 creds_match(krb5_context ctx, const krb5_creds *mcreds, 670 const krb5_creds *creds) 671 { 672 char *s1, *s2, *c1, *c2; 673 krb5_unparse_name(ctx, mcreds->client, &c1); 674 krb5_unparse_name(ctx, mcreds->server, &s1); 675 krb5_unparse_name(ctx, creds->client, &c2); 676 krb5_unparse_name(ctx, creds->server, &s2); 677 678 return (krb5_principal_compare(ctx, mcreds->client, creds->client) && 679 krb5_principal_compare(ctx, mcreds->server, creds->server)); 680 } 681 682 /* 683 * Delete the user's credentials for this session 684 */ 685 static int 686 attempt_delete_initcred(krb5_module_data_t *kmd) 687 { 688 if (kmd == NULL) 689 return (0); 690 691 if (kmd->debug) { 692 syslog(LOG_DEBUG, 693 "PAM-KRB5 (setcred): deleting user's " 694 "credentials (initcreds)"); 695 } 696 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds); 697 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds)); 698 kmd->auth_status = PAM_AUTHINFO_UNAVAIL; 699 return (0); 700 } 701