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