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