1 /*- 2 * This pam_krb5 module contains code that is: 3 * Copyright (c) Derrick J. Brashear, 1996. All rights reserved. 4 * Copyright (c) Frank Cusack, 1999-2001. All rights reserved. 5 * Copyright (c) Jacques A. Vidrine, 2000-2001. All rights reserved. 6 * Copyright (c) Nicolas Williams, 2001. All rights reserved. 7 * Copyright (c) Perot Systems Corporation, 2001. All rights reserved. 8 * Copyright (c) Mark R V Murray, 2001. All rights reserved. 9 * Copyright (c) Networks Associates Technology, Inc., 2002-2005. 10 * All rights reserved. 11 * 12 * Portions of this software were developed for the FreeBSD Project by 13 * ThinkSec AS and NAI Labs, the Security Research Division of Network 14 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 15 * ("CBOSS"), as part of the DARPA CHATS research program. 16 * 17 * Redistribution and use in source and binary forms, with or without 18 * modification, are permitted provided that the following conditions 19 * are met: 20 * 1. Redistributions of source code must retain the above copyright 21 * notices, and the entire permission notice in its entirety, 22 * including the disclaimer of warranties. 23 * 2. Redistributions in binary form must reproduce the above copyright 24 * notice, this list of conditions and the following disclaimer in the 25 * documentation and/or other materials provided with the distribution. 26 * 3. The name of the author may not be used to endorse or promote 27 * products derived from this software without specific prior 28 * written permission. 29 * 30 * ALTERNATIVELY, this product may be distributed under the terms of 31 * the GNU Public License, in which case the provisions of the GPL are 32 * required INSTEAD OF the above restrictions. (This clause is 33 * necessary due to a potential bad interaction between the GPL and 34 * the restrictions contained in a BSD-style copyright.) 35 * 36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 40 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 41 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 42 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 46 * OF THE POSSIBILITY OF SUCH DAMAGE. 47 * 48 */ 49 50 #include <sys/types.h> 51 #include <sys/stat.h> 52 #include <errno.h> 53 #include <limits.h> 54 #include <pwd.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <syslog.h> 59 #include <unistd.h> 60 61 #include <krb5.h> 62 #include <com_err.h> 63 #ifdef MK_MITKRB5 64 /* For MIT KRB5 only. */ 65 #include <k5-int.h> 66 #endif 67 68 #define PAM_SM_AUTH 69 #define PAM_SM_ACCOUNT 70 #define PAM_SM_PASSWORD 71 72 #include <security/pam_appl.h> 73 #include <security/pam_modules.h> 74 #include <security/pam_mod_misc.h> 75 #include <security/openpam.h> 76 77 #define COMPAT_HEIMDAL 78 /* #define COMPAT_MIT */ 79 80 static int verify_krb_v5_tgt_begin(krb5_context, char *, int, 81 const char **, krb5_principal *, char[static BUFSIZ]); 82 static int verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int, 83 const char *, krb5_principal, char[static BUFSIZ]); 84 static void verify_krb_v5_tgt_cleanup(krb5_context, int, 85 const char *, krb5_principal, char[static BUFSIZ]); 86 static void cleanup_cache(pam_handle_t *, void *, int); 87 static const char *compat_princ_component(krb5_context, krb5_principal, int); 88 static void compat_free_data_contents(krb5_context, krb5_data *); 89 90 #define USER_PROMPT "Username: " 91 #define PASSWORD_PROMPT "Password:" 92 #define NEW_PASSWORD_PROMPT "New Password:" 93 94 #define PAM_OPT_CCACHE "ccache" 95 #define PAM_OPT_DEBUG "debug" 96 #define PAM_OPT_FORWARDABLE "forwardable" 97 #define PAM_OPT_NO_CCACHE "no_ccache" 98 #define PAM_OPT_NO_USER_CHECK "no_user_check" 99 #define PAM_OPT_REUSE_CCACHE "reuse_ccache" 100 #define PAM_OPT_NO_USER_CHECK "no_user_check" 101 #define PAM_OPT_ALLOW_KDC_SPOOF "allow_kdc_spoof" 102 103 #define PAM_LOG_KRB5_ERR(ctx, rv, fmt, ...) \ 104 do { \ 105 const char *krb5msg = krb5_get_error_message(ctx, rv); \ 106 PAM_LOG(fmt ": %s", ##__VA_ARGS__, krb5msg); \ 107 krb5_free_error_message(ctx, krb5msg); \ 108 } while (0) 109 110 /* 111 * authentication management 112 */ 113 PAM_EXTERN int 114 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 115 int argc __unused, const char *argv[] __unused) 116 { 117 krb5_error_code krbret; 118 krb5_context krbctx; 119 int debug; 120 const char *auth_service; 121 krb5_principal auth_princ; 122 char auth_phost[BUFSIZ]; 123 krb5_creds creds; 124 krb5_principal princ; 125 krb5_ccache ccache; 126 krb5_get_init_creds_opt *opts; 127 struct passwd *pwd; 128 int retval; 129 const void *ccache_data; 130 const char *user, *pass; 131 const void *sourceuser, *service; 132 char *principal, *princ_name, *ccache_name, luser[32], *srvdup; 133 134 retval = pam_get_user(pamh, &user, USER_PROMPT); 135 if (retval != PAM_SUCCESS) 136 return (retval); 137 138 PAM_LOG("Got user: %s", user); 139 140 retval = pam_get_item(pamh, PAM_RUSER, &sourceuser); 141 if (retval != PAM_SUCCESS) 142 return (retval); 143 144 PAM_LOG("Got ruser: %s", (const char *)sourceuser); 145 146 service = NULL; 147 pam_get_item(pamh, PAM_SERVICE, &service); 148 if (service == NULL) 149 service = "unknown"; 150 151 PAM_LOG("Got service: %s", (const char *)service); 152 153 if ((srvdup = strdup(service)) == NULL) { 154 retval = PAM_BUF_ERR; 155 goto cleanup6; 156 } 157 158 krbret = krb5_init_context(&krbctx); 159 if (krbret != 0) { 160 PAM_VERBOSE_ERROR("Kerberos 5 error"); 161 retval = PAM_SERVICE_ERR; 162 goto cleanup5; 163 } 164 165 PAM_LOG("Context initialised"); 166 167 debug = openpam_get_option(pamh, PAM_OPT_DEBUG) ? 1 : 0; 168 krbret = verify_krb_v5_tgt_begin(krbctx, srvdup, debug, 169 &auth_service, &auth_princ, auth_phost); 170 if (krbret != 0) { /* failed to find key */ 171 /* Keytab or service key does not exist */ 172 /* 173 * Give up now because we can't authenticate the KDC 174 * with a keytab, unless the administrator asked to 175 * have the traditional behaviour of being vulnerable 176 * to spoofed KDCs. 177 */ 178 if (!openpam_get_option(pamh, PAM_OPT_ALLOW_KDC_SPOOF)) { 179 retval = PAM_SERVICE_ERR; 180 goto cleanup4; 181 } 182 } 183 184 krbret = krb5_cc_register(krbctx, &krb5_mcc_ops, FALSE); 185 if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) { 186 PAM_VERBOSE_ERROR("Kerberos 5 error"); 187 retval = PAM_SERVICE_ERR; 188 goto cleanup3; 189 } 190 191 PAM_LOG("Done krb5_cc_register()"); 192 193 /* Get principal name */ 194 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) 195 asprintf(&principal, "%s/%s", (const char *)sourceuser, user); 196 else 197 principal = strdup(user); 198 199 PAM_LOG("Created principal: %s", principal); 200 201 krbret = krb5_parse_name(krbctx, principal, &princ); 202 free(principal); 203 if (krbret != 0) { 204 PAM_LOG_KRB5_ERR(krbctx, krbret, "Error krb5_parse_name()"); 205 PAM_VERBOSE_ERROR("Kerberos 5 error"); 206 retval = PAM_SERVICE_ERR; 207 goto cleanup3; 208 } 209 210 PAM_LOG("Done krb5_parse_name()"); 211 212 /* Now convert the principal name into something human readable */ 213 princ_name = NULL; 214 krbret = krb5_unparse_name(krbctx, princ, &princ_name); 215 if (krbret != 0) { 216 PAM_LOG_KRB5_ERR(krbctx, krbret, 217 "Error krb5_unparse_name()"); 218 PAM_VERBOSE_ERROR("Kerberos 5 error"); 219 retval = PAM_SERVICE_ERR; 220 goto cleanup2; 221 } 222 223 PAM_LOG("Got principal: %s", princ_name); 224 225 /* Get password */ 226 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT); 227 if (retval != PAM_SUCCESS) 228 goto cleanup2; 229 230 PAM_LOG("Got password"); 231 232 if (openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK)) 233 PAM_LOG("Skipping local user check"); 234 else { 235 236 /* Verify the local user exists (AFTER getting the password) */ 237 if (strchr(user, '@')) { 238 /* get a local account name for this principal */ 239 krbret = krb5_aname_to_localname(krbctx, princ, 240 sizeof(luser), luser); 241 if (krbret != 0) { 242 PAM_VERBOSE_ERROR("Kerberos 5 error"); 243 PAM_LOG_KRB5_ERR(krbctx, krbret, 244 "Error krb5_aname_to_localname()"); 245 retval = PAM_USER_UNKNOWN; 246 goto cleanup2; 247 } 248 249 retval = pam_set_item(pamh, PAM_USER, luser); 250 if (retval != PAM_SUCCESS) 251 goto cleanup2; 252 253 PAM_LOG("PAM_USER Redone"); 254 } 255 256 if (!openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK)) { 257 pwd = getpwnam(user); 258 if (pwd == NULL) { 259 retval = PAM_USER_UNKNOWN; 260 goto cleanup2; 261 } 262 } 263 264 PAM_LOG("Done getpwnam()"); 265 } 266 267 /* Initialize credentials request options. */ 268 krbret = krb5_get_init_creds_opt_alloc(krbctx, &opts); 269 if (krbret != 0) { 270 PAM_LOG_KRB5_ERR(krbctx, krbret, 271 "Error krb5_get_init_creds_opt_alloc()"); 272 PAM_VERBOSE_ERROR("Kerberos 5 error"); 273 retval = PAM_SERVICE_ERR; 274 goto cleanup2; 275 } 276 krb5_get_init_creds_opt_set_default_flags(krbctx, 277 service, NULL, opts); 278 279 if (openpam_get_option(pamh, PAM_OPT_FORWARDABLE)) 280 krb5_get_init_creds_opt_set_forwardable(opts, 1); 281 282 PAM_LOG("Credential options initialised"); 283 284 /* Get a TGT */ 285 memset(&creds, 0, sizeof(krb5_creds)); 286 krbret = krb5_get_init_creds_password(krbctx, &creds, princ, 287 pass, NULL, pamh, 0, NULL, opts); 288 krb5_get_init_creds_opt_free(krbctx, opts); 289 if (krbret != 0) { 290 PAM_VERBOSE_ERROR("Kerberos 5 error"); 291 PAM_LOG_KRB5_ERR(krbctx, krbret, 292 "Error krb5_get_init_creds_password()"); 293 retval = PAM_AUTH_ERR; 294 goto cleanup2; 295 } 296 297 PAM_LOG("Got TGT"); 298 299 /* Generate a temporary cache */ 300 krbret = krb5_cc_new_unique(krbctx, krb5_cc_type_memory, NULL, &ccache); 301 if (krbret != 0) { 302 PAM_VERBOSE_ERROR("Kerberos 5 error"); 303 PAM_LOG_KRB5_ERR(krbctx, krbret, 304 "Error krb5_cc_new_unique()"); 305 retval = PAM_SERVICE_ERR; 306 goto cleanup; 307 } 308 krbret = krb5_cc_initialize(krbctx, ccache, princ); 309 if (krbret != 0) { 310 PAM_VERBOSE_ERROR("Kerberos 5 error"); 311 PAM_LOG_KRB5_ERR(krbctx, krbret, 312 "Error krb5_cc_initialize()"); 313 retval = PAM_SERVICE_ERR; 314 goto cleanup; 315 } 316 krbret = krb5_cc_store_cred(krbctx, ccache, &creds); 317 if (krbret != 0) { 318 PAM_VERBOSE_ERROR("Kerberos 5 error"); 319 PAM_LOG_KRB5_ERR(krbctx, krbret, 320 "Error krb5_cc_store_cred()"); 321 krb5_cc_destroy(krbctx, ccache); 322 retval = PAM_SERVICE_ERR; 323 goto cleanup; 324 } 325 326 PAM_LOG("Credentials stashed"); 327 328 /* Verify them */ 329 krbret = verify_krb_v5_tgt(krbctx, ccache, srvdup, 330 debug, 331 auth_service, auth_princ, auth_phost); 332 free(srvdup); 333 srvdup = NULL; 334 if (krbret == -1) { 335 PAM_VERBOSE_ERROR("Kerberos 5 error"); 336 krb5_cc_destroy(krbctx, ccache); 337 retval = PAM_AUTH_ERR; 338 goto cleanup; 339 } 340 341 PAM_LOG("Credentials stash verified"); 342 343 retval = pam_get_data(pamh, "ccache", &ccache_data); 344 if (retval == PAM_SUCCESS) { 345 krb5_cc_destroy(krbctx, ccache); 346 PAM_VERBOSE_ERROR("Kerberos 5 error"); 347 retval = PAM_AUTH_ERR; 348 goto cleanup; 349 } 350 351 PAM_LOG("Credentials stash not pre-existing"); 352 353 asprintf(&ccache_name, "%s:%s", krb5_cc_get_type(krbctx, 354 ccache), krb5_cc_get_name(krbctx, ccache)); 355 if (ccache_name == NULL) { 356 PAM_VERBOSE_ERROR("Kerberos 5 error"); 357 retval = PAM_BUF_ERR; 358 goto cleanup; 359 } 360 retval = pam_set_data(pamh, "ccache", ccache_name, cleanup_cache); 361 if (retval != 0) { 362 krb5_cc_destroy(krbctx, ccache); 363 PAM_VERBOSE_ERROR("Kerberos 5 error"); 364 retval = PAM_SERVICE_ERR; 365 goto cleanup; 366 } 367 368 PAM_LOG("Credentials stash saved"); 369 370 cleanup: 371 krb5_free_cred_contents(krbctx, &creds); 372 PAM_LOG("Done cleanup"); 373 cleanup2: 374 krb5_free_principal(krbctx, princ); 375 if (princ_name) 376 free(princ_name); 377 PAM_LOG("Done cleanup2"); 378 379 cleanup3: 380 krb5_free_context(krbctx); 381 382 PAM_LOG("Done cleanup3"); 383 384 cleanup4: 385 verify_krb_v5_tgt_cleanup(krbctx, debug, 386 auth_service, auth_princ, auth_phost); 387 PAM_LOG("Done cleanup4"); 388 389 cleanup5: 390 if (srvdup != NULL) 391 free(srvdup); 392 PAM_LOG("Done cleanup5"); 393 394 cleanup6: 395 if (retval != PAM_SUCCESS) 396 PAM_VERBOSE_ERROR("Kerberos 5 refuses you"); 397 PAM_LOG("Done cleanup6"); 398 399 return (retval); 400 } 401 402 PAM_EXTERN int 403 pam_sm_setcred(pam_handle_t *pamh, int flags, 404 int argc __unused, const char *argv[] __unused) 405 { 406 #ifdef _FREEFALL_CONFIG 407 return (PAM_SUCCESS); 408 #else 409 410 krb5_error_code krbret; 411 krb5_context krbctx; 412 krb5_principal princ; 413 krb5_creds creds; 414 krb5_ccache ccache_temp, ccache_perm; 415 krb5_cc_cursor cursor; 416 struct passwd *pwd = NULL; 417 int retval; 418 const char *cache_name, *q; 419 const void *user; 420 const void *cache_data; 421 char *cache_name_buf = NULL, *p; 422 423 uid_t euid; 424 gid_t egid; 425 426 if (flags & PAM_DELETE_CRED) 427 return (PAM_SUCCESS); 428 429 if (flags & PAM_REFRESH_CRED) 430 return (PAM_SUCCESS); 431 432 if (flags & PAM_REINITIALIZE_CRED) 433 return (PAM_SUCCESS); 434 435 if (!(flags & PAM_ESTABLISH_CRED)) 436 return (PAM_SERVICE_ERR); 437 438 /* If a persistent cache isn't desired, stop now. */ 439 if (openpam_get_option(pamh, PAM_OPT_NO_CCACHE) || 440 openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK)) 441 return (PAM_SUCCESS); 442 443 PAM_LOG("Establishing credentials"); 444 445 /* Get username */ 446 retval = pam_get_item(pamh, PAM_USER, &user); 447 if (retval != PAM_SUCCESS) 448 return (retval); 449 450 PAM_LOG("Got user: %s", (const char *)user); 451 452 krbret = krb5_init_context(&krbctx); 453 if (krbret != 0) { 454 PAM_LOG("Error krb5_init_context() failed"); 455 return (PAM_SERVICE_ERR); 456 } 457 458 PAM_LOG("Context initialised"); 459 460 euid = geteuid(); /* Usually 0 */ 461 egid = getegid(); 462 463 PAM_LOG("Got euid, egid: %d %d", euid, egid); 464 465 /* Retrieve the temporary cache */ 466 retval = pam_get_data(pamh, "ccache", &cache_data); 467 if (retval != PAM_SUCCESS) { 468 retval = PAM_CRED_UNAVAIL; 469 goto cleanup3; 470 } 471 krbret = krb5_cc_resolve(krbctx, cache_data, &ccache_temp); 472 if (krbret != 0) { 473 PAM_LOG_KRB5_ERR(krbctx, krbret, 474 "Error krb5_cc_resolve(\"%s\")", (const char *)cache_data); 475 retval = PAM_SERVICE_ERR; 476 goto cleanup3; 477 } 478 479 /* Get the uid. This should exist. */ 480 pwd = getpwnam(user); 481 if (pwd == NULL) { 482 retval = PAM_USER_UNKNOWN; 483 goto cleanup3; 484 } 485 486 PAM_LOG("Done getpwnam()"); 487 488 /* Avoid following a symlink as root */ 489 if (setegid(pwd->pw_gid)) { 490 retval = PAM_SERVICE_ERR; 491 goto cleanup3; 492 } 493 if (seteuid(pwd->pw_uid)) { 494 retval = PAM_SERVICE_ERR; 495 goto cleanup3; 496 } 497 498 PAM_LOG("Done setegid() & seteuid()"); 499 500 /* Get the cache name */ 501 cache_name = openpam_get_option(pamh, PAM_OPT_CCACHE); 502 if (cache_name == NULL) { 503 asprintf(&cache_name_buf, "FILE:/tmp/krb5cc_%d", pwd->pw_uid); 504 cache_name = cache_name_buf; 505 } 506 507 p = calloc(PATH_MAX + 16, sizeof(char)); 508 q = cache_name; 509 510 if (p == NULL) { 511 PAM_LOG("Error malloc(): failure"); 512 retval = PAM_BUF_ERR; 513 goto cleanup3; 514 } 515 cache_name = p; 516 517 /* convert %u and %p */ 518 while (*q) { 519 if (*q == '%') { 520 q++; 521 if (*q == 'u') { 522 sprintf(p, "%d", pwd->pw_uid); 523 p += strlen(p); 524 } 525 else if (*q == 'p') { 526 sprintf(p, "%d", getpid()); 527 p += strlen(p); 528 } 529 else { 530 /* Not a special token */ 531 *p++ = '%'; 532 q--; 533 } 534 q++; 535 } 536 else { 537 *p++ = *q++; 538 } 539 } 540 541 PAM_LOG("Got cache_name: %s", cache_name); 542 543 /* Initialize the new ccache */ 544 krbret = krb5_cc_get_principal(krbctx, ccache_temp, &princ); 545 if (krbret != 0) { 546 PAM_LOG_KRB5_ERR(krbctx, krbret, 547 "Error krb5_cc_get_principal()"); 548 retval = PAM_SERVICE_ERR; 549 goto cleanup3; 550 } 551 krbret = krb5_cc_resolve(krbctx, cache_name, &ccache_perm); 552 if (krbret != 0) { 553 PAM_LOG_KRB5_ERR(krbctx, krbret, "Error krb5_cc_resolve()"); 554 retval = PAM_SERVICE_ERR; 555 goto cleanup2; 556 } 557 krbret = krb5_cc_initialize(krbctx, ccache_perm, princ); 558 if (krbret != 0) { 559 PAM_LOG_KRB5_ERR(krbctx, krbret, 560 "Error krb5_cc_initialize()"); 561 retval = PAM_SERVICE_ERR; 562 goto cleanup2; 563 } 564 565 PAM_LOG("Cache initialised"); 566 567 /* Prepare for iteration over creds */ 568 krbret = krb5_cc_start_seq_get(krbctx, ccache_temp, &cursor); 569 if (krbret != 0) { 570 PAM_LOG_KRB5_ERR(krbctx, krbret, 571 "Error krb5_cc_start_seq_get()"); 572 krb5_cc_destroy(krbctx, ccache_perm); 573 retval = PAM_SERVICE_ERR; 574 goto cleanup2; 575 } 576 577 PAM_LOG("Prepared for iteration"); 578 579 /* Copy the creds (should be two of them) */ 580 while (krb5_cc_next_cred(krbctx, ccache_temp, &cursor, &creds) == 0) { 581 krbret = krb5_cc_store_cred(krbctx, ccache_perm, &creds); 582 if (krbret != 0) { 583 PAM_LOG_KRB5_ERR(krbctx, krbret, 584 "Error krb5_cc_store_cred()"); 585 krb5_cc_destroy(krbctx, ccache_perm); 586 krb5_free_cred_contents(krbctx, &creds); 587 retval = PAM_SERVICE_ERR; 588 goto cleanup2; 589 } 590 krb5_free_cred_contents(krbctx, &creds); 591 PAM_LOG("Iteration"); 592 } 593 krb5_cc_end_seq_get(krbctx, ccache_temp, &cursor); 594 595 PAM_LOG("Done iterating"); 596 597 if (strstr(cache_name, "FILE:") == cache_name) { 598 if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) { 599 PAM_LOG("Error chown(): %s", strerror(errno)); 600 krb5_cc_destroy(krbctx, ccache_perm); 601 retval = PAM_SERVICE_ERR; 602 goto cleanup2; 603 } 604 PAM_LOG("Done chown()"); 605 606 if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) { 607 PAM_LOG("Error chmod(): %s", strerror(errno)); 608 krb5_cc_destroy(krbctx, ccache_perm); 609 retval = PAM_SERVICE_ERR; 610 goto cleanup2; 611 } 612 PAM_LOG("Done chmod()"); 613 } 614 615 krb5_cc_close(krbctx, ccache_perm); 616 617 PAM_LOG("Cache closed"); 618 619 retval = pam_setenv(pamh, "KRB5CCNAME", cache_name, 1); 620 if (retval != PAM_SUCCESS) { 621 PAM_LOG("Error pam_setenv(): %s", pam_strerror(pamh, retval)); 622 krb5_cc_destroy(krbctx, ccache_perm); 623 retval = PAM_SERVICE_ERR; 624 goto cleanup2; 625 } 626 627 PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name); 628 629 cleanup2: 630 krb5_free_principal(krbctx, princ); 631 PAM_LOG("Done cleanup2"); 632 cleanup3: 633 krb5_free_context(krbctx); 634 PAM_LOG("Done cleanup3"); 635 636 seteuid(euid); 637 setegid(egid); 638 639 PAM_LOG("Done seteuid() & setegid()"); 640 641 if (cache_name_buf != NULL) 642 free(cache_name_buf); 643 644 return (retval); 645 #endif 646 } 647 648 /* 649 * account management 650 */ 651 PAM_EXTERN int 652 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, 653 int argc __unused, const char *argv[] __unused) 654 { 655 krb5_error_code krbret; 656 krb5_context krbctx; 657 krb5_ccache ccache; 658 krb5_principal princ; 659 int retval; 660 const void *user; 661 const void *ccache_name; 662 663 retval = pam_get_item(pamh, PAM_USER, &user); 664 if (retval != PAM_SUCCESS) 665 return (retval); 666 667 PAM_LOG("Got user: %s", (const char *)user); 668 669 retval = pam_get_data(pamh, "ccache", &ccache_name); 670 if (retval != PAM_SUCCESS) 671 return (PAM_SUCCESS); 672 673 PAM_LOG("Got credentials"); 674 675 krbret = krb5_init_context(&krbctx); 676 if (krbret != 0) { 677 PAM_LOG("Error krb5_init_context() failed"); 678 return (PAM_PERM_DENIED); 679 } 680 681 PAM_LOG("Context initialised"); 682 683 krbret = krb5_cc_resolve(krbctx, (const char *)ccache_name, &ccache); 684 if (krbret != 0) { 685 PAM_LOG_KRB5_ERR(krbctx, krbret, 686 "Error krb5_cc_resolve(\"%s\")", (const char *)ccache_name); 687 krb5_free_context(krbctx); 688 return (PAM_PERM_DENIED); 689 } 690 691 PAM_LOG("Got ccache %s", (const char *)ccache_name); 692 693 694 krbret = krb5_cc_get_principal(krbctx, ccache, &princ); 695 if (krbret != 0) { 696 PAM_LOG_KRB5_ERR(krbctx, krbret, 697 "Error krb5_cc_get_principal()"); 698 retval = PAM_PERM_DENIED; 699 goto cleanup; 700 } 701 702 PAM_LOG("Got principal"); 703 704 if (krb5_kuserok(krbctx, princ, (const char *)user)) 705 retval = PAM_SUCCESS; 706 else 707 retval = PAM_PERM_DENIED; 708 krb5_free_principal(krbctx, princ); 709 710 PAM_LOG("Done kuserok()"); 711 712 cleanup: 713 krb5_free_context(krbctx); 714 PAM_LOG("Done cleanup"); 715 716 return (retval); 717 718 } 719 720 /* 721 * password management 722 */ 723 PAM_EXTERN int 724 pam_sm_chauthtok(pam_handle_t *pamh, int flags, 725 int argc __unused, const char *argv[] __unused) 726 { 727 krb5_error_code krbret; 728 krb5_context krbctx; 729 krb5_creds creds; 730 krb5_principal princ; 731 krb5_get_init_creds_opt *opts; 732 krb5_data result_code_string, result_string; 733 int result_code, retval; 734 const char *pass; 735 const void *user; 736 char *princ_name, *passdup; 737 738 if (!(flags & PAM_UPDATE_AUTHTOK)) 739 return (PAM_AUTHTOK_ERR); 740 741 retval = pam_get_item(pamh, PAM_USER, &user); 742 if (retval != PAM_SUCCESS) 743 return (retval); 744 745 PAM_LOG("Got user: %s", (const char *)user); 746 747 krbret = krb5_init_context(&krbctx); 748 if (krbret != 0) { 749 PAM_LOG("Error krb5_init_context() failed"); 750 return (PAM_SERVICE_ERR); 751 } 752 753 PAM_LOG("Context initialised"); 754 755 /* Get principal name */ 756 krbret = krb5_parse_name(krbctx, (const char *)user, &princ); 757 if (krbret != 0) { 758 PAM_LOG_KRB5_ERR(krbctx, krbret, 759 "Error krb5_parse_name()"); 760 retval = PAM_USER_UNKNOWN; 761 goto cleanup3; 762 } 763 764 /* Now convert the principal name into something human readable */ 765 princ_name = NULL; 766 krbret = krb5_unparse_name(krbctx, princ, &princ_name); 767 if (krbret != 0) { 768 PAM_LOG_KRB5_ERR(krbctx, krbret, 769 "Error krb5_unparse_name()"); 770 retval = PAM_SERVICE_ERR; 771 goto cleanup2; 772 } 773 774 PAM_LOG("Got principal: %s", princ_name); 775 776 /* Get password */ 777 retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT); 778 if (retval != PAM_SUCCESS) 779 goto cleanup2; 780 781 PAM_LOG("Got password"); 782 783 /* Initialize credentials request options. */ 784 krbret = krb5_get_init_creds_opt_alloc(krbctx, &opts); 785 if (krbret != 0) { 786 PAM_LOG_KRB5_ERR(krbctx, krbret, 787 "Error krb5_get_init_creds_opt_alloc()"); 788 PAM_VERBOSE_ERROR("Kerberos 5 error"); 789 retval = PAM_SERVICE_ERR; 790 goto cleanup2; 791 } 792 793 PAM_LOG("Credentials options initialised"); 794 795 memset(&creds, 0, sizeof(krb5_creds)); 796 krbret = krb5_get_init_creds_password(krbctx, &creds, princ, 797 pass, NULL, pamh, 0, "kadmin/changepw", opts); 798 krb5_get_init_creds_opt_free(krbctx, opts); 799 if (krbret != 0) { 800 PAM_LOG_KRB5_ERR(krbctx, krbret, 801 "Error krb5_get_init_creds_password()"); 802 retval = PAM_AUTH_ERR; 803 goto cleanup2; 804 } 805 806 PAM_LOG("Credentials established"); 807 808 /* Now get the new password */ 809 for (;;) { 810 retval = pam_get_authtok(pamh, 811 PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT); 812 if (retval != PAM_TRY_AGAIN) 813 break; 814 pam_error(pamh, "Mismatch; try again, EOF to quit."); 815 } 816 if (retval != PAM_SUCCESS) 817 goto cleanup; 818 819 PAM_LOG("Got new password"); 820 821 /* Change it */ 822 if ((passdup = strdup(pass)) == NULL) { 823 retval = PAM_BUF_ERR; 824 goto cleanup; 825 } 826 krbret = krb5_set_password(krbctx, &creds, passdup, NULL, 827 &result_code, &result_code_string, &result_string); 828 free(passdup); 829 if (krbret != 0) { 830 PAM_LOG_KRB5_ERR(krbctx, krbret, 831 "Error krb5_change_password()"); 832 retval = PAM_AUTHTOK_ERR; 833 goto cleanup; 834 } 835 if (result_code) { 836 PAM_LOG("Error krb5_change_password(): (result_code)"); 837 retval = PAM_AUTHTOK_ERR; 838 goto cleanup; 839 } 840 841 PAM_LOG("Password changed"); 842 843 if (result_string.data) 844 free(result_string.data); 845 if (result_code_string.data) 846 free(result_code_string.data); 847 848 cleanup: 849 krb5_free_cred_contents(krbctx, &creds); 850 PAM_LOG("Done cleanup"); 851 cleanup2: 852 krb5_free_principal(krbctx, princ); 853 if (princ_name) 854 free(princ_name); 855 PAM_LOG("Done cleanup2"); 856 857 cleanup3: 858 krb5_free_context(krbctx); 859 860 PAM_LOG("Done cleanup3"); 861 862 return (retval); 863 } 864 865 PAM_MODULE_ENTRY("pam_krb5"); 866 867 /* 868 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c 869 * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services 870 * for Debian. 871 * 872 * Verify the Kerberos ticket-granting ticket just retrieved for the 873 * user. If the Kerberos server doesn't respond, assume the user is 874 * trying to fake us out (since we DID just get a TGT from what is 875 * supposedly our KDC). If the host/<host> service is unknown (i.e., 876 * the local keytab doesn't have it), and we cannot find another 877 * service we do have, let her in. 878 * 879 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty. 880 */ 881 /* ARGSUSED */ 882 static int 883 verify_krb_v5_tgt_begin(krb5_context context, char *pam_service, int debug, 884 const char **servicep, krb5_principal *princp __unused, char phost[static BUFSIZ]) 885 { 886 krb5_error_code retval; 887 krb5_principal princ; 888 krb5_keyblock *keyblock; 889 const char *services[3], **service; 890 891 *servicep = NULL; 892 893 if (debug) 894 openlog("pam_krb5", LOG_PID, LOG_AUTHPRIV); 895 896 /* If possible we want to try and verify the ticket we have 897 * received against a keytab. We will try multiple service 898 * principals, including at least the host principal and the PAM 899 * service principal. The host principal is preferred because access 900 * to that key is generally sufficient to compromise root, while the 901 * service key for this PAM service may be less carefully guarded. 902 * It is important to check the keytab first before the KDC so we do 903 * not get spoofed by a fake KDC. 904 */ 905 services[0] = "host"; 906 services[1] = pam_service; 907 services[2] = NULL; 908 keyblock = NULL; 909 retval = -1; 910 for (service = &services[0]; *service != NULL; service++) { 911 retval = krb5_sname_to_principal(context, NULL, *service, 912 KRB5_NT_SRV_HST, &princ); 913 if (retval != 0) { 914 if (debug) { 915 const char *msg = krb5_get_error_message( 916 context, retval); 917 syslog(LOG_DEBUG, 918 "pam_krb5: verify_krb_v5_tgt(): %s: %s", 919 "krb5_sname_to_principal()", msg); 920 krb5_free_error_message(context, msg); 921 } 922 return -1; 923 } 924 925 /* Extract the name directly. */ 926 strncpy(phost, compat_princ_component(context, princ, 1), 927 BUFSIZ); 928 phost[BUFSIZ - 1] = '\0'; 929 930 /* 931 * Do we have service/<host> keys? 932 * (use default/configured keytab, kvno IGNORE_VNO to get the 933 * first match, and ignore enctype.) 934 */ 935 retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0, 936 &keyblock); 937 if (retval != 0) 938 continue; 939 break; 940 } 941 if (keyblock) 942 krb5_free_keyblock(context, keyblock); 943 944 return (retval); 945 } 946 947 static int 948 verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache, 949 char *pam_service __unused, int debug, 950 const char *service, krb5_principal princ, char phost[static BUFSIZ]) 951 { 952 krb5_error_code retval; 953 krb5_auth_context auth_context = NULL; 954 krb5_data packet; 955 956 if (service == NULL) 957 return (0); /* uncertain, can't authenticate KDC */ 958 959 packet.data = 0; 960 961 /* Talk to the kdc and construct the ticket. */ 962 auth_context = NULL; 963 retval = krb5_mk_req(context, &auth_context, 0, service, phost, 964 NULL, ccache, &packet); 965 if (auth_context) { 966 krb5_auth_con_free(context, auth_context); 967 auth_context = NULL; /* setup for rd_req */ 968 } 969 if (retval) { 970 if (debug) { 971 const char *msg = krb5_get_error_message(context, 972 retval); 973 syslog(LOG_DEBUG, 974 "pam_krb5: verify_krb_v5_tgt(): %s: %s", 975 "krb5_mk_req()", msg); 976 krb5_free_error_message(context, msg); 977 } 978 retval = -1; 979 goto cleanup; 980 } 981 982 /* Try to use the ticket. */ 983 retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL, 984 NULL, NULL); 985 if (retval) { 986 if (debug) { 987 const char *msg = krb5_get_error_message(context, 988 retval); 989 syslog(LOG_DEBUG, 990 "pam_krb5: verify_krb_v5_tgt(): %s: %s", 991 "krb5_rd_req()", msg); 992 krb5_free_error_message(context, msg); 993 } 994 retval = -1; 995 } 996 else 997 retval = 1; 998 999 cleanup: 1000 if (packet.data) 1001 compat_free_data_contents(context, &packet); 1002 return (retval); 1003 } 1004 1005 static void 1006 verify_krb_v5_tgt_cleanup(krb5_context context, int debug, 1007 const char *service, krb5_principal princ, char phost[static BUFSIZ] __unused) 1008 { 1009 1010 if (service) 1011 krb5_free_principal(context, princ); 1012 if (debug) 1013 closelog(); 1014 1015 } 1016 1017 /* Free the memory for cache_name. Called by pam_end() */ 1018 /* ARGSUSED */ 1019 static void 1020 cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused) 1021 { 1022 krb5_context krbctx; 1023 krb5_ccache ccache; 1024 krb5_error_code krbret; 1025 1026 if (krb5_init_context(&krbctx)) 1027 return; 1028 1029 krbret = krb5_cc_resolve(krbctx, data, &ccache); 1030 if (krbret == 0) 1031 krb5_cc_destroy(krbctx, ccache); 1032 krb5_free_context(krbctx); 1033 free(data); 1034 } 1035 1036 #ifdef COMPAT_HEIMDAL 1037 #ifdef COMPAT_MIT 1038 #error This cannot be MIT and Heimdal compatible! 1039 #endif 1040 #endif 1041 1042 #ifndef COMPAT_HEIMDAL 1043 #ifndef COMPAT_MIT 1044 #error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified! 1045 #endif 1046 #endif 1047 1048 #ifdef COMPAT_HEIMDAL 1049 /* ARGSUSED */ 1050 static const char * 1051 compat_princ_component(krb5_context context __unused, krb5_principal princ, int n) 1052 { 1053 return princ->name.name_string.val[n]; 1054 } 1055 1056 /* ARGSUSED */ 1057 static void 1058 compat_free_data_contents(krb5_context context __unused, krb5_data * data) 1059 { 1060 krb5_xfree(data->data); 1061 } 1062 #endif 1063 1064 #ifdef COMPAT_MIT 1065 static const char * 1066 compat_princ_component(krb5_context context, krb5_principal princ, int n) 1067 { 1068 return krb5_princ_component(context, princ, n)->data; 1069 } 1070 1071 static void 1072 compat_free_data_contents(krb5_context context, krb5_data * data) 1073 { 1074 krb5_free_data_contents(context, data); 1075 } 1076 #endif 1077