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