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