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