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