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