Home | History | Annotate | Download | only in krb5
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #include <security/pam_appl.h>
     27 #include <security/pam_modules.h>
     28 #include <security/pam_impl.h>
     29 #include <string.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <sys/types.h>
     33 #include <sys/stat.h>
     34 #include <pwd.h>
     35 #include <syslog.h>
     36 #include <libintl.h>
     37 #include <k5-int.h>
     38 #include "profile/prof_int.h"
     39 #include <netdb.h>
     40 #include <ctype.h>
     41 #include "utils.h"
     42 #include "krb5_repository.h"
     43 
     44 #define	KRB5_DEFAULT_OPTIONS 0
     45 
     46 int forwardable_flag = 0;
     47 int renewable_flag = 0;
     48 int proxiable_flag = 0;
     49 int no_address_flag = 0;
     50 profile_options_boolean config_option[] = {
     51 	{ "forwardable", &forwardable_flag, 0 },
     52 	{ "renewable",  &renewable_flag, 0 },
     53 	{ "proxiable", &proxiable_flag, 0 },
     54 	{ "no_addresses", &no_address_flag, 0 },
     55 	{ NULL, NULL, 0 }
     56 };
     57 char *renew_timeval;
     58 char *life_timeval;
     59 profile_option_strings config_times[] = {
     60 	{ "max_life", &life_timeval, 0 },
     61 	{ "max_renewable_life",  &renew_timeval, 0 },
     62 	{ NULL, NULL, 0 }
     63 };
     64 char *realmdef[] = { "realms", NULL, NULL, NULL };
     65 char *appdef[] = { "appdefaults", "kinit", NULL };
     66 
     67 #define	krb_realm (*(realmdef + 1))
     68 
     69 int	attempt_krb5_auth(krb5_module_data_t *, char *, char **, boolean_t);
     70 void	krb5_cleanup(pam_handle_t *, void *, int);
     71 
     72 extern errcode_t profile_get_options_boolean();
     73 extern errcode_t profile_get_options_string();
     74 extern int krb5_verifypw(char *, char *, int);
     75 extern krb5_error_code krb5_verify_init_creds(krb5_context,
     76 		krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *,
     77 		krb5_verify_init_creds_opt *);
     78 extern krb5_error_code __krb5_get_init_creds_password(krb5_context,
     79 		krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
     80 		krb5_deltat, char *, krb5_get_init_creds_opt *,
     81 		krb5_kdc_rep **);
     82 
     83 /*
     84  * pam_sm_authenticate		- Authenticate user
     85  */
     86 int
     87 pam_sm_authenticate(
     88 	pam_handle_t		*pamh,
     89 	int 			flags,
     90 	int			argc,
     91 	const char		**argv)
     92 {
     93 	char			*user = NULL;
     94 	int			err;
     95 	int			result = PAM_AUTH_ERR;
     96 	/* pam.conf options */
     97 	int			debug = 0;
     98 	int			warn = 1;
     99 	/* return an error on password expire */
    100 	int			err_on_exp = 0;
    101 	int			i;
    102 	char			*password = NULL;
    103 	uid_t			pw_uid;
    104 	krb5_module_data_t	*kmd = NULL;
    105 	krb5_repository_data_t  *krb5_data = NULL;
    106 	pam_repository_t	*rep_data = NULL;
    107 
    108 	for (i = 0; i < argc; i++) {
    109 		if (strcmp(argv[i], "debug") == 0) {
    110 			debug = 1;
    111 		} else if (strcmp(argv[i], "nowarn") == 0) {
    112 			warn = 0;
    113 		} else if (strcmp(argv[i], "err_on_exp") == 0) {
    114 			err_on_exp = 1;
    115 		} else {
    116 			__pam_log(LOG_AUTH | LOG_ERR,
    117 				"PAM-KRB5 (auth) unrecognized option %s",
    118 				argv[i]);
    119 		}
    120 	}
    121 	if (flags & PAM_SILENT) warn = 0;
    122 
    123 	if (debug)
    124 		__pam_log(LOG_AUTH | LOG_DEBUG,
    125 		    "PAM-KRB5 (auth): pam_sm_authenticate flags=%d",
    126 		    flags);
    127 
    128 	(void) pam_get_item(pamh, PAM_USER, (void**) &user);
    129 
    130 	if (user == NULL || *user == '\0') {
    131 		if (debug)
    132 			__pam_log(LOG_AUTH | LOG_DEBUG,
    133 				"PAM-KRB5 (auth): user empty or null");
    134 		return (PAM_USER_UNKNOWN);
    135 	}
    136 
    137 	/* make sure a password entry exists for this user */
    138 	if (!get_pw_uid(user, &pw_uid))
    139 		return (PAM_USER_UNKNOWN);
    140 
    141 	/*
    142 	 * pam_get_data could fail if we are being called for the first time
    143 	 * or if the module is not found, PAM_NO_MODULE_DATA is not an error
    144 	 */
    145 	err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd);
    146 	if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA))
    147 		return (PAM_SYSTEM_ERR);
    148 
    149 	if (kmd == NULL) {
    150 		kmd = calloc(1, sizeof (krb5_module_data_t));
    151 		if (kmd == NULL) {
    152 			result = PAM_BUF_ERR;
    153 			goto out;
    154 		}
    155 
    156 		err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup);
    157 		if (err != PAM_SUCCESS) {
    158 			free(kmd);
    159 			result = err;
    160 			goto out;
    161 		}
    162 	}
    163 
    164 	if (!kmd->env) {
    165 		char buffer[512];
    166 
    167 		if (snprintf(buffer, sizeof (buffer),
    168 			    "%s=FILE:/tmp/krb5cc_%d",
    169 			    KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) {
    170 			result = PAM_SYSTEM_ERR;
    171 			goto out;
    172 		}
    173 
    174 		/* we MUST copy this to the heap for the putenv to work! */
    175 		kmd->env = strdup(buffer);
    176 		if (!kmd->env) {
    177 			result = PAM_BUF_ERR;
    178 			goto out;
    179 		} else {
    180 			if (putenv(kmd->env)) {
    181 				result = PAM_SYSTEM_ERR;
    182 				goto out;
    183 			}
    184 		}
    185 	}
    186 
    187 	if (kmd->user != NULL)
    188 		free(kmd->user);
    189 	if ((kmd->user = strdup(user)) == NULL) {
    190 		result = PAM_BUF_ERR;
    191 		goto out;
    192 	}
    193 
    194 	kmd->auth_status = PAM_AUTH_ERR;
    195 	kmd->debug = debug;
    196 	kmd->warn = warn;
    197 	kmd->err_on_exp = err_on_exp;
    198 	kmd->ccache = NULL;
    199 	kmd->kcontext = NULL;
    200 	kmd->password = NULL;
    201 	kmd->age_status = PAM_SUCCESS;
    202 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
    203 
    204 	/*
    205 	 * For apps that already did krb5 auth exchange...
    206 	 * Now that we've created the kmd structure, we can
    207 	 * return SUCCESS.  'kmd' may be needed later by other
    208 	 * PAM functions, thats why we wait until this point to
    209 	 * return.
    210 	 */
    211 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
    212 
    213 	if (rep_data != NULL) {
    214 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
    215 			if (debug)
    216 				__pam_log(LOG_AUTH | LOG_DEBUG,
    217 					"PAM-KRB5 (auth): wrong"
    218 					"repository found (%s), returning "
    219 					"PAM_IGNORE", rep_data->type);
    220 			return (PAM_IGNORE);
    221 		}
    222 		if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
    223 			krb5_data = (krb5_repository_data_t *)rep_data->scope;
    224 
    225 			if (krb5_data->flags ==
    226 				SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
    227 				krb5_data->principal != NULL &&
    228 				strlen(krb5_data->principal)) {
    229 				if (debug)
    230 					__pam_log(LOG_AUTH | LOG_DEBUG,
    231 						"PAM-KRB5 (auth): Principal "
    232 						"%s already authenticated",
    233 						krb5_data->principal);
    234 				kmd->auth_status = PAM_SUCCESS;
    235 				return (PAM_SUCCESS);
    236 			}
    237 		}
    238 	}
    239 
    240 	/*
    241 	 * if root key exists in the keytab, it's a random key so no
    242 	 * need to prompt for pw and we just return IGNORE.
    243 	 *
    244 	 * note we don't need to force a prompt for pw as authtok_get
    245 	 * is required to be stacked above this module.
    246 	 */
    247 	if ((strcmp(user, ROOT_UNAME) == 0) &&
    248 	    key_in_keytab(user, debug)) {
    249 		if (debug)
    250 			__pam_log(LOG_AUTH | LOG_DEBUG,
    251 			    "PAM-KRB5 (auth): "
    252 			    "key for '%s' in keytab, returning IGNORE", user);
    253 		result = PAM_IGNORE;
    254 		goto out;
    255 	}
    256 
    257 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
    258 
    259 	result = attempt_krb5_auth(kmd, user, &password, 1);
    260 
    261 out:
    262 	if (kmd) {
    263 		if (debug)
    264 			__pam_log(LOG_AUTH | LOG_DEBUG,
    265 			    "PAM-KRB5 (auth): pam_sm_auth finalize"
    266 			    " ccname env, result =%d, env ='%s',"
    267 			    " age = %d, status = %d",
    268 			    result, kmd->env ? kmd->env : "<null>",
    269 			    kmd->age_status, kmd->auth_status);
    270 
    271 		if (kmd->env &&
    272 		    !(kmd->age_status == PAM_NEW_AUTHTOK_REQD &&
    273 			    kmd->auth_status == PAM_SUCCESS)) {
    274 
    275 
    276 			if (result == PAM_SUCCESS) {
    277 				/*
    278 				 * Put ccname into the pamh so that login
    279 				 * apps can pick this up when they run
    280 				 * pam_getenvlist().
    281 				 */
    282 				if ((result = pam_putenv(pamh, kmd->env))
    283 				    != PAM_SUCCESS) {
    284 					/* should not happen but... */
    285 					__pam_log(LOG_AUTH | LOG_ERR,
    286 					    "PAM-KRB5 (auth):"
    287 					    " pam_putenv failed: result: %d",
    288 					    result);
    289 					goto cleanupccname;
    290 				}
    291 			} else {
    292 			cleanupccname:
    293 				/* for lack of a Solaris unputenv() */
    294 				krb5_unsetenv(KRB5_ENV_CCNAME);
    295 				free(kmd->env);
    296 				kmd->env = NULL;
    297 			}
    298 		}
    299 		kmd->auth_status = result;
    300 	}
    301 
    302 	if (debug)
    303 		__pam_log(LOG_AUTH | LOG_DEBUG,
    304 		    "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result));
    305 
    306 	return (result);
    307 }
    308 
    309 int
    310 attempt_krb5_auth(
    311 	krb5_module_data_t	*kmd,
    312 	char		*user,
    313 	char		**krb5_pass,
    314 	boolean_t	verify_tik)
    315 {
    316 	krb5_principal	me = NULL, clientp = NULL;
    317 	krb5_principal	server = NULL, serverp = NULL;
    318 	krb5_creds	*my_creds;
    319 	krb5_timestamp	now;
    320 	krb5_error_code	code = 0;
    321 	char		kuser[2*MAXHOSTNAMELEN];
    322 	krb5_deltat	lifetime;
    323 	krb5_deltat	rlife;
    324 	krb5_deltat	krb5_max_duration;
    325 	int		options = KRB5_DEFAULT_OPTIONS;
    326 	krb5_data tgtname = {
    327 		0,
    328 		KRB5_TGS_NAME_SIZE,
    329 		KRB5_TGS_NAME
    330 	};
    331 	krb5_get_init_creds_opt opts;
    332 	krb5_kdc_rep *as_reply = NULL;
    333 	/*
    334 	 * "result" should not be assigned PAM_SUCCESS unless
    335 	 * authentication has succeeded and there are no other errors.
    336 	 *
    337 	 * "code" is sometimes used for PAM codes, sometimes for krb5
    338 	 * codes.  Be careful.
    339 	 */
    340 	int result = PAM_AUTH_ERR;
    341 
    342 	if (kmd->debug)
    343 		__pam_log(LOG_AUTH | LOG_DEBUG,
    344 		    "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
    345 		    user ? user : "<null>");
    346 
    347 	krb5_get_init_creds_opt_init(&opts);
    348 
    349 	/* need to free context with krb5_free_context */
    350 	if (code = krb5_init_secure_context(&kmd->kcontext)) {
    351 		__pam_log(LOG_AUTH | LOG_ERR,
    352 			"PAM-KRB5 (auth): Error initializing "
    353 			"krb5: %s",
    354 			error_message(code));
    355 		return (PAM_SYSTEM_ERR);
    356 	}
    357 
    358 	if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
    359 		2*MAXHOSTNAMELEN)) != 0) {
    360 		/* get_kmd_kuser returns proper PAM error statuses */
    361 		return (code);
    362 	}
    363 
    364 	if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) {
    365 		krb5_free_context(kmd->kcontext);
    366 		kmd->kcontext = NULL;
    367 		return (PAM_SYSTEM_ERR);
    368 	}
    369 
    370 	/* call krb5_free_cred_contents() on error */
    371 	my_creds = &kmd->initcreds;
    372 
    373 	if ((code =
    374 	    krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) {
    375 		result = PAM_SYSTEM_ERR;
    376 		goto out_err;
    377 	}
    378 	clientp = my_creds->client;
    379 
    380 	if (code = krb5_build_principal_ext(kmd->kcontext, &server,
    381 			    krb5_princ_realm(kmd->kcontext, me)->length,
    382 			    krb5_princ_realm(kmd->kcontext, me)->data,
    383 			    tgtname.length, tgtname.data,
    384 			    krb5_princ_realm(kmd->kcontext, me)->length,
    385 			    krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
    386 		__pam_log(LOG_AUTH | LOG_ERR,
    387 			"PAM-KRB5 (auth): attempt_krb5_auth: "
    388 			"krb5_build_princ_ext failed: %s",
    389 			error_message(code));
    390 		result = PAM_SYSTEM_ERR;
    391 		goto out;
    392 	}
    393 
    394 	if (code = krb5_copy_principal(kmd->kcontext, server,
    395 				&my_creds->server)) {
    396 		result = PAM_SYSTEM_ERR;
    397 		goto out_err;
    398 	}
    399 	serverp = my_creds->server;
    400 
    401 	if (code = krb5_timeofday(kmd->kcontext, &now)) {
    402 		__pam_log(LOG_AUTH | LOG_ERR,
    403 			"PAM-KRB5 (auth): attempt_krb5_auth: "
    404 			"krb5_timeofday failed: %s",
    405 			error_message(code));
    406 		result = PAM_SYSTEM_ERR;
    407 		goto out;
    408 	}
    409 
    410 	/*
    411 	 * set the values for lifetime and rlife to be the maximum
    412 	 * possible
    413 	 */
    414 	krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
    415 	lifetime = krb5_max_duration;
    416 	rlife = krb5_max_duration;
    417 
    418 	/*
    419 	 * Let us get the values for various options
    420 	 * from Kerberos configuration file
    421 	 */
    422 
    423 	krb_realm = krb5_princ_realm(kmd->kcontext, me)->data;
    424 	profile_get_options_boolean(kmd->kcontext->profile,
    425 				    realmdef, config_option);
    426 	profile_get_options_boolean(kmd->kcontext->profile,
    427 				    appdef, config_option);
    428 	profile_get_options_string(kmd->kcontext->profile,
    429 				realmdef, config_times);
    430 	profile_get_options_string(kmd->kcontext->profile,
    431 				appdef, config_times);
    432 
    433 	if (renew_timeval) {
    434 		code = krb5_string_to_deltat(renew_timeval, &rlife);
    435 		if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
    436 			__pam_log(LOG_AUTH | LOG_ERR,
    437 				    "PAM-KRB5 (auth): Bad max_renewable_life "
    438 				    " value '%s' in Kerberos config file",
    439 			    renew_timeval);
    440 			result = PAM_SYSTEM_ERR;
    441 			goto out;
    442 		}
    443 	}
    444 	if (life_timeval) {
    445 		code = krb5_string_to_deltat(life_timeval, &lifetime);
    446 		if (code != 0 || lifetime == 0 ||
    447 		    lifetime > krb5_max_duration) {
    448 			__pam_log(LOG_AUTH | LOG_ERR,
    449 				"lifetime value '%s' in Kerberos config file",
    450 			    life_timeval);
    451 			result = PAM_SYSTEM_ERR;
    452 			goto out;
    453 		}
    454 	}
    455 	/*  start timer when request gets to KDC */
    456 	my_creds->times.starttime = 0;
    457 	my_creds->times.endtime = now + lifetime;
    458 
    459 	if (options & KDC_OPT_RENEWABLE) {
    460 		my_creds->times.renew_till = now + rlife;
    461 	} else
    462 		my_creds->times.renew_till = 0;
    463 
    464 	krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime);
    465 
    466 	if (proxiable_flag) { 		/* Set in config file */
    467 		if (kmd->debug)
    468 			__pam_log(LOG_AUTH | LOG_DEBUG,
    469 				"PAM-KRB5 (auth): Proxiable tickets "
    470 				"requested");
    471 		krb5_get_init_creds_opt_set_proxiable(&opts, TRUE);
    472 	}
    473 	if (forwardable_flag) {
    474 		if (kmd->debug)
    475 			__pam_log(LOG_AUTH | LOG_DEBUG,
    476 				"PAM-KRB5 (auth): Forwardable tickets "
    477 				"requested");
    478 		krb5_get_init_creds_opt_set_forwardable(&opts, TRUE);
    479 	}
    480 	if (renewable_flag) {
    481 		if (kmd->debug)
    482 			__pam_log(LOG_AUTH | LOG_DEBUG,
    483 				"PAM-KRB5 (auth): Renewable tickets "
    484 				"requested");
    485 		krb5_get_init_creds_opt_set_renew_life(&opts, rlife);
    486 	}
    487 	if (no_address_flag) {
    488 		if (kmd->debug)
    489 			__pam_log(LOG_AUTH | LOG_DEBUG,
    490 				"PAM-KRB5 (auth): Addressless tickets "
    491 				"requested");
    492 		krb5_get_init_creds_opt_set_address_list(&opts, NULL);
    493 	}
    494 
    495 	/*
    496 	 * mech_krb5 interprets empty passwords as NULL passwords
    497 	 * and tries to read a password from stdin. Since we are in
    498 	 * pam this is bad and should not be allowed.
    499 	 */
    500 	if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) {
    501 		code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
    502 	} else {
    503 
    504 		/*
    505 		 * We call our own private version of gic_pwd, because we need
    506 		 * more information, such as password/account expiration, that
    507 		 * is found in the as_reply.  The "prompter" interface is not
    508 		 * granular enough for PAM to make use of.
    509 		 */
    510 		code = __krb5_get_init_creds_password(kmd->kcontext,
    511 				my_creds,
    512 				me,
    513 				*krb5_pass,	/* clear text passwd */
    514 				NULL,		/* prompter */
    515 				NULL,		/* data */
    516 				0,		/* start time */
    517 				NULL,		/* defaults to krbtgt@REALM */
    518 				&opts,
    519 				&as_reply);
    520 	}
    521 
    522 	if (kmd->debug)
    523 		__pam_log(LOG_AUTH | LOG_DEBUG,
    524 		    "PAM-KRB5 (auth): attempt_krb5_auth: "
    525 		    "krb5_get_init_creds_password returns: %s",
    526 		    code == 0 ? "SUCCESS" : error_message(code));
    527 
    528 	switch (code) {
    529 	case 0:
    530 		/* got a tgt, let's verify it */
    531 		if (verify_tik) {
    532 			krb5_verify_init_creds_opt vopts;
    533 
    534 			krb5_principal sp = NULL;
    535 			char kt_name[MAX_KEYTAB_NAME_LEN];
    536 			char *fqdn;
    537 
    538 			krb5_verify_init_creds_opt_init(&vopts);
    539 
    540 			code = krb5_verify_init_creds(kmd->kcontext,
    541 				my_creds,
    542 				NULL,	/* defaults to host/localhost@REALM */
    543 				NULL,
    544 				NULL,
    545 				&vopts);
    546 
    547 			if (code) {
    548 				result = PAM_SYSTEM_ERR;
    549 
    550 				/*
    551 				 * Give a better error message when the
    552 				 * keytable entry isn't found or the keytab
    553 				 * file cannot be found.
    554 				 */
    555 				if (krb5_sname_to_principal(kmd->kcontext, NULL,
    556 						NULL, KRB5_NT_SRV_HST, &sp))
    557 					fqdn = "<fqdn>";
    558 				else
    559 					fqdn = sp->data[1].data;
    560 
    561 				if (krb5_kt_default_name(kmd->kcontext, kt_name,
    562 							sizeof (kt_name)))
    563 					(void) strncpy(kt_name,
    564 						"default keytab",
    565 						sizeof (kt_name));
    566 
    567 				switch (code) {
    568 				case KRB5_KT_NOTFOUND:
    569 					__pam_log(LOG_AUTH | LOG_ERR,
    570 						"PAM-KRB5 (auth): "
    571 						"krb5_verify_init_creds failed:"
    572 						" Key table entry \"host/%s\""
    573 						" not found in %s",
    574 						fqdn, kt_name);
    575 					break;
    576 				case ENOENT:
    577 					__pam_log(LOG_AUTH | LOG_ERR,
    578 						"PAM-KRB5 (auth): "
    579 						"krb5_verify_init_creds failed:"
    580 						" Keytab file \"%s\""
    581 						" does not exist.\n",
    582 						kt_name);
    583 					break;
    584 				default:
    585 					__pam_log(LOG_AUTH | LOG_ERR,
    586 						"PAM-KRB5 (auth): "
    587 						"krb5_verify_init_creds failed:"
    588 						" %s",
    589 						error_message(code));
    590 					break;
    591 				}
    592 
    593 				if (sp)
    594 					krb5_free_principal(kmd->kcontext, sp);
    595 			}
    596 		}
    597 
    598 		if (code == 0)
    599 			kmd->expiration = as_reply->enc_part2->key_exp;
    600 
    601 		break;
    602 
    603 	case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
    604 		/*
    605 		 * Since this principal is not part of the local
    606 		 * Kerberos realm, we just return PAM_USER_UNKNOWN.
    607 		 */
    608 		result = PAM_USER_UNKNOWN;
    609 
    610 		if (kmd->debug)
    611 			__pam_log(LOG_AUTH | LOG_DEBUG,
    612 				"PAM-KRB5 (auth): attempt_krb5_auth:"
    613 				" User is not part of the local Kerberos"
    614 				" realm: %s", error_message(code));
    615 		break;
    616 
    617 	case KRB5KDC_ERR_PREAUTH_FAILED:
    618 	case KRB5KRB_AP_ERR_BAD_INTEGRITY:
    619 		/*
    620 		 * We could be trying the password from a previous
    621 		 * pam authentication module, but we don't want to
    622 		 * generate an error if the unix password is different
    623 		 * than the Kerberos password...
    624 		 */
    625 		break;
    626 
    627 	case KRB5KDC_ERR_KEY_EXP:
    628 		if (!kmd->err_on_exp) {
    629 			/*
    630 			 * Request a tik for changepw service
    631 			 * and it will tell us if pw is good or not.
    632 			 */
    633 			code = krb5_verifypw(kuser, *krb5_pass, kmd->debug);
    634 
    635 			if (kmd->debug)
    636 				__pam_log(LOG_AUTH | LOG_DEBUG,
    637 				    "PAM-KRB5 (auth): attempt_krb5_auth: "
    638 				    "verifypw %d", code);
    639 
    640 			if (code == 0) {
    641 				/* pw is good, set age status for acct_mgmt */
    642 				kmd->age_status = PAM_NEW_AUTHTOK_REQD;
    643 			}
    644 		}
    645 		break;
    646 
    647 	default:
    648 		result = PAM_SYSTEM_ERR;
    649 		if (kmd->debug)
    650 			__pam_log(LOG_AUTH | LOG_DEBUG,
    651 				"PAM-KRB5 (auth): error %d - %s",
    652 				code, error_message(code));
    653 		break;
    654 	}
    655 
    656 	if (code == 0) {
    657 		/*
    658 		 * success for the entered pw
    659 		 *
    660 		 * we can't rely on the pw in PAM_AUTHTOK
    661 		 * to be the (correct) krb5 one so
    662 		 * store krb5 pw in module data for
    663 		 * use in acct_mgmt
    664 		 */
    665 		if (!(kmd->password = strdup(*krb5_pass))) {
    666 			__pam_log(LOG_AUTH | LOG_ERR, "Cannot strdup password");
    667 			result = PAM_BUF_ERR;
    668 			goto out_err;
    669 		}
    670 		result = PAM_SUCCESS;
    671 		goto out;
    672 	}
    673 
    674 out_err:
    675 	/* jump (or reach) here if error and cred cache has been init */
    676 
    677 	if (kmd->debug)
    678 		__pam_log(LOG_AUTH | LOG_DEBUG,
    679 		    "PAM-KRB5 (auth): clearing initcreds in "
    680 		    "pam_authenticate()");
    681 
    682 	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
    683 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
    684 
    685 out:
    686 	if (server)
    687 		krb5_free_principal(kmd->kcontext, server);
    688 	if (me)
    689 		krb5_free_principal(kmd->kcontext, me);
    690 	if (as_reply)
    691 		krb5_free_kdc_rep(kmd->kcontext, as_reply);
    692 
    693 	/*
    694 	 * clientp or serverp could be NULL in certain error cases in this
    695 	 * function.  mycreds->[client|server] could also be NULL in case
    696 	 * of error in this function, see out_err above.  The pointers clientp
    697 	 * and serverp reference the input argument in my_creds for
    698 	 * get_init_creds and must be freed if the input argument does not
    699 	 * match the output argument, which occurs during a successful call
    700 	 * to get_init_creds.
    701 	 */
    702 	if (clientp && my_creds->client && clientp != my_creds->client)
    703 		krb5_free_principal(kmd->kcontext, clientp);
    704 	if (serverp && my_creds->server && serverp != my_creds->server)
    705 		krb5_free_principal(kmd->kcontext, serverp);
    706 
    707 	if (kmd->kcontext) {
    708 		krb5_free_context(kmd->kcontext);
    709 		kmd->kcontext = NULL;
    710 	}
    711 
    712 	if (kmd->debug)
    713 		__pam_log(LOG_AUTH | LOG_DEBUG,
    714 		    "PAM-KRB5 (auth): attempt_krb5_auth returning %d",
    715 		    result);
    716 
    717 	return (kmd->auth_status = result);
    718 }
    719 
    720 /*ARGSUSED*/
    721 void
    722 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status)
    723 {
    724 	krb5_module_data_t *kmd = (krb5_module_data_t *)data;
    725 
    726 	if (kmd == NULL)
    727 		return;
    728 
    729 	if (kmd->debug) {
    730 		__pam_log(LOG_AUTH | LOG_DEBUG,
    731 			    "PAM-KRB5 (auth): krb5_cleanup auth_status = %d",
    732 		    kmd->auth_status);
    733 	}
    734 
    735 	/*
    736 	 * Apps could be calling pam_end here, so we should always clean
    737 	 * up regardless of success or failure here.
    738 	 */
    739 	if (kmd->ccache)
    740 		krb5_cc_close(kmd->kcontext, kmd->ccache);
    741 
    742 	if (kmd->password) {
    743 		(void) memset(kmd->password, 0, strlen(kmd->password));
    744 		free(kmd->password);
    745 	}
    746 
    747 	if (kmd->user)
    748 		free(kmd->user);
    749 
    750 	if (kmd->env)
    751 		free(kmd->env);
    752 
    753 	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
    754 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
    755 
    756 	free(kmd);
    757 }
    758