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