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