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