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