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