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