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