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