1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <security/pam_appl.h>
28 #include <security/pam_modules.h>
29 #include <security/pam_impl.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <pwd.h>
36 #include <syslog.h>
37 #include <libintl.h>
38 #include <k5-int.h>
39 #include "profile/prof_int.h"
40 #include <netdb.h>
41 #include <ctype.h>
42 #include "utils.h"
43 #include "krb5_repository.h"
44 
45 #define	KRB5_DEFAULT_OPTIONS 0
46 
47 int forwardable_flag = 0;
48 int renewable_flag = 0;
49 int proxiable_flag = 0;
50 int no_address_flag = 0;
51 profile_options_boolean config_option[] = {
52 	{ "forwardable", &forwardable_flag, 0 },
53 	{ "renewable",  &renewable_flag, 0 },
54 	{ "proxiable", &proxiable_flag, 0 },
55 	{ "no_addresses", &no_address_flag, 0 },
56 	{ NULL, NULL, 0 }
57 };
58 char *renew_timeval;
59 char *life_timeval;
60 profile_option_strings config_times[] = {
61 	{ "max_life", &life_timeval, 0 },
62 	{ "max_renewable_life",  &renew_timeval, 0 },
63 	{ NULL, NULL, 0 }
64 };
65 char *realmdef[] = { "realms", NULL, NULL, NULL };
66 char *appdef[] = { "appdefaults", "kinit", NULL };
67 
68 #define	krb_realm (*(realmdef + 1))
69 
70 int	attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *,
71 	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 	boolean_t		do_pkinit = FALSE;
110 
111 	for (i = 0; i < argc; i++) {
112 		if (strcmp(argv[i], "debug") == 0) {
113 			debug = 1;
114 		} else if (strcmp(argv[i], "nowarn") == 0) {
115 			warn = 0;
116 		} else if (strcmp(argv[i], "err_on_exp") == 0) {
117 			err_on_exp = 1;
118 		} else if (strcmp(argv[i], "pkinit") == 0) {
119 			do_pkinit = TRUE;
120 		} else {
121 			__pam_log(LOG_AUTH | LOG_ERR,
122 			    "PAM-KRB5 (auth) unrecognized option %s", argv[i]);
123 		}
124 	}
125 	if (flags & PAM_SILENT) warn = 0;
126 
127 	if (debug)
128 		__pam_log(LOG_AUTH | LOG_DEBUG,
129 		    "PAM-KRB5 (auth): pam_sm_authenticate flags=%d",
130 		    flags);
131 
132 	/*
133 	 * pam_get_data could fail if we are being called for the first time
134 	 * or if the module is not found, PAM_NO_MODULE_DATA is not an error
135 	 */
136 	err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd);
137 	if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA))
138 		return (PAM_SYSTEM_ERR);
139 
140 	/*
141 	 * If pam_krb5 was stacked higher in the auth stack and did PKINIT
142 	 * preauth sucessfully then this instance is a fallback to password
143 	 * based preauth and should just return PAM_IGNORE.
144 	 *
145 	 * The else clause is handled further down.
146 	 */
147 	if (kmd != NULL) {
148 		if (++(kmd->auth_calls) > 2) {
149 			/*
150 			 * pam_krb5 has been stacked > 2 times in the auth
151 			 * stack.  Clear out the current kmd and proceed as if
152 			 * this is the first time pam_krb5 auth has been called.
153 			 */
154 			if (debug) {
155 				__pam_log(LOG_AUTH | LOG_DEBUG,
156 				    "PAM-KRB5 (auth): stacked more than"
157 				    " two times, clearing kmd");
158 			}
159 			/* clear out/free current kmd */
160 			err = pam_set_data(pamh, KRB5_DATA, NULL, NULL);
161 			if (err != PAM_SUCCESS) {
162 				krb5_cleanup(pamh, kmd, err);
163 				result = err;
164 				goto out;
165 			}
166 			kmd = NULL;
167 		} else if (kmd->auth_calls == 2 &&
168 		    kmd->auth_status == PAM_SUCCESS) {
169 			/*
170 			 * The previous instance of pam_krb5 succeeded and this
171 			 * instance was a fall back in case it didn't succeed so
172 			 * return ignore.
173 			 */
174 			if (debug) {
175 				__pam_log(LOG_AUTH | LOG_DEBUG,
176 				    "PAM-KRB5 (auth): PKINIT succeeded "
177 				    "earlier so returning PAM_IGNORE");
178 			}
179 			return (PAM_IGNORE);
180 		}
181 	}
182 
183 	(void) pam_get_item(pamh, PAM_USER, (void**) &user);
184 
185 	if (user == NULL || *user == '\0') {
186 		if (do_pkinit) {
187 			/*
188 			 * If doing PKINIT it is okay to prompt for the user
189 			 * name.
190 			 */
191 			if ((err = pam_get_user(pamh, &user, NULL)) !=
192 			    PAM_SUCCESS) {
193 				if (debug) {
194 					__pam_log(LOG_AUTH | LOG_DEBUG,
195 					    "PAM-KRB5 (auth): get user failed: "
196 					    "%s", pam_strerror(pamh, err));
197 				}
198 				return (err);
199 			}
200 		} else {
201 			if (debug)
202 				__pam_log(LOG_AUTH | LOG_DEBUG,
203 				    "PAM-KRB5 (auth): user empty or null");
204 			return (PAM_USER_UNKNOWN);
205 		}
206 	}
207 
208 	/* make sure a password entry exists for this user */
209 	if (!get_pw_uid(user, &pw_uid))
210 		return (PAM_USER_UNKNOWN);
211 
212 	if (kmd == NULL) {
213 		kmd = calloc(1, sizeof (krb5_module_data_t));
214 		if (kmd == NULL) {
215 			result = PAM_BUF_ERR;
216 			goto out;
217 		}
218 
219 		err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup);
220 		if (err != PAM_SUCCESS) {
221 			free(kmd);
222 			result = err;
223 			goto out;
224 		}
225 	}
226 
227 	if (!kmd->env) {
228 		char buffer[512];
229 
230 		if (snprintf(buffer, sizeof (buffer),
231 			    "%s=FILE:/tmp/krb5cc_%d",
232 			    KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) {
233 			result = PAM_SYSTEM_ERR;
234 			goto out;
235 		}
236 
237 		/* we MUST copy this to the heap for the putenv to work! */
238 		kmd->env = strdup(buffer);
239 		if (!kmd->env) {
240 			result = PAM_BUF_ERR;
241 			goto out;
242 		} else {
243 			if (putenv(kmd->env)) {
244 				result = PAM_SYSTEM_ERR;
245 				goto out;
246 			}
247 		}
248 	}
249 
250 	if (kmd->user != NULL)
251 		free(kmd->user);
252 	if ((kmd->user = strdup(user)) == NULL) {
253 		result = PAM_BUF_ERR;
254 		goto out;
255 	}
256 
257 	kmd->auth_status = PAM_AUTH_ERR;
258 	kmd->debug = debug;
259 	kmd->warn = warn;
260 	kmd->err_on_exp = err_on_exp;
261 	kmd->ccache = NULL;
262 	kmd->kcontext = NULL;
263 	kmd->password = NULL;
264 	kmd->age_status = PAM_SUCCESS;
265 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
266 	kmd->auth_calls = 1;
267 	kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD;
268 
269 	/*
270 	 * For apps that already did krb5 auth exchange...
271 	 * Now that we've created the kmd structure, we can
272 	 * return SUCCESS.  'kmd' may be needed later by other
273 	 * PAM functions, thats why we wait until this point to
274 	 * return.
275 	 */
276 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
277 
278 	if (rep_data != NULL) {
279 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
280 			if (debug)
281 				__pam_log(LOG_AUTH | LOG_DEBUG,
282 					"PAM-KRB5 (auth): wrong"
283 					"repository found (%s), returning "
284 					"PAM_IGNORE", rep_data->type);
285 			return (PAM_IGNORE);
286 		}
287 		if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
288 			krb5_data = (krb5_repository_data_t *)rep_data->scope;
289 
290 			if (krb5_data->flags ==
291 				SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
292 				krb5_data->principal != NULL &&
293 				strlen(krb5_data->principal)) {
294 				if (debug)
295 					__pam_log(LOG_AUTH | LOG_DEBUG,
296 						"PAM-KRB5 (auth): Principal "
297 						"%s already authenticated",
298 						krb5_data->principal);
299 				kmd->auth_status = PAM_SUCCESS;
300 				return (PAM_SUCCESS);
301 			}
302 		}
303 	}
304 
305 	/*
306 	 * if root key exists in the keytab, it's a random key so no
307 	 * need to prompt for pw and we just return IGNORE.
308 	 *
309 	 * note we don't need to force a prompt for pw as authtok_get
310 	 * is required to be stacked above this module.
311 	 */
312 	if ((strcmp(user, ROOT_UNAME) == 0) &&
313 	    key_in_keytab(user, debug)) {
314 		if (debug)
315 			__pam_log(LOG_AUTH | LOG_DEBUG,
316 			    "PAM-KRB5 (auth): "
317 			    "key for '%s' in keytab, returning IGNORE", user);
318 		result = PAM_IGNORE;
319 		goto out;
320 	}
321 
322 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
323 
324 	result = attempt_krb5_auth(pamh, kmd, user, &password, 1);
325 
326 out:
327 	if (kmd) {
328 		if (debug)
329 			__pam_log(LOG_AUTH | LOG_DEBUG,
330 			    "PAM-KRB5 (auth): pam_sm_auth finalize"
331 			    " ccname env, result =%d, env ='%s',"
332 			    " age = %d, status = %d",
333 			    result, kmd->env ? kmd->env : "<null>",
334 			    kmd->age_status, kmd->auth_status);
335 
336 		if (kmd->env &&
337 		    !(kmd->age_status == PAM_NEW_AUTHTOK_REQD &&
338 			    kmd->auth_status == PAM_SUCCESS)) {
339 
340 
341 			if (result == PAM_SUCCESS) {
342 				/*
343 				 * Put ccname into the pamh so that login
344 				 * apps can pick this up when they run
345 				 * pam_getenvlist().
346 				 */
347 				if ((result = pam_putenv(pamh, kmd->env))
348 				    != PAM_SUCCESS) {
349 					/* should not happen but... */
350 					__pam_log(LOG_AUTH | LOG_ERR,
351 					    "PAM-KRB5 (auth):"
352 					    " pam_putenv failed: result: %d",
353 					    result);
354 					goto cleanupccname;
355 				}
356 			} else {
357 			cleanupccname:
358 				/* for lack of a Solaris unputenv() */
359 				krb5_unsetenv(KRB5_ENV_CCNAME);
360 				free(kmd->env);
361 				kmd->env = NULL;
362 			}
363 		}
364 		kmd->auth_status = result;
365 	}
366 
367 	if (debug)
368 		__pam_log(LOG_AUTH | LOG_DEBUG,
369 		    "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result));
370 
371 	return (result);
372 }
373 
374 static krb5_error_code
375 pam_krb5_prompter(
376 	krb5_context ctx,
377 	void *data,
378 	/* ARGSUSED1 */
379 	const char *name,
380 	const char *banner,
381 	int num_prompts,
382 	krb5_prompt prompts[])
383 {
384 	krb5_error_code rc;
385 	pam_handle_t *pamh = (pam_handle_t *)data;
386 	struct pam_conv	*pam_convp;
387 	struct pam_message *msgs;
388 	struct pam_response *ret_respp;
389 	int i;
390 	krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx);
391 	char tmpbuf[PAM_MAX_MSG_SIZE];
392 
393 	/*
394 	 * Because this function should never be used for password prompts,
395 	 * disallow password prompts.
396 	 */
397 	for (i = 0; i < num_prompts; i++) {
398 		if (prompt_type[i] == KRB5_PROMPT_TYPE_PASSWORD)
399 			return (KRB5_LIBOS_CANTREADPWD);
400 	}
401 
402 	if (num_prompts == 0) {
403 		if (prompts) {
404 			/* This shouldn't happen */
405 			return (PAM_SYSTEM_ERR);
406 		} else {
407 			/* no prompts so return */
408 			return (0);
409 		}
410 	}
411 	if ((rc = pam_get_item(pamh, PAM_CONV, (void **)&pam_convp))
412 	    != PAM_SUCCESS) {
413 		return (rc);
414 	}
415 	if (pam_convp == NULL) {
416 		return (PAM_SYSTEM_ERR);
417 	}
418 
419 	msgs = (struct pam_message *)calloc(num_prompts,
420 	    sizeof (struct pam_message));
421 	if (msgs == NULL) {
422 		return (PAM_BUF_ERR);
423 	}
424 	(void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts);
425 
426 	for (i = 0; i < num_prompts; i++) {
427 		/* convert krb prompt style to PAM style */
428 		if (prompts[i].hidden) {
429 			msgs[i].msg_style = PAM_PROMPT_ECHO_OFF;
430 		} else {
431 			msgs[i].msg_style = PAM_PROMPT_ECHO_ON;
432 		}
433 		/*
434 		 * krb expects the prompting function to append ": " to the
435 		 * prompt string.
436 		 */
437 		if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ",
438 		    prompts[i].prompt) < 0) {
439 			rc = PAM_BUF_ERR;
440 			goto cleanup;
441 		}
442 		msgs[i].msg = strdup(tmpbuf);
443 		if (msgs[i].msg == NULL) {
444 			rc = PAM_BUF_ERR;
445 			goto cleanup;
446 		}
447 	}
448 
449 	/*
450 	 * Call PAM conv function to display the prompt.
451 	 */
452 	rc = (pam_convp->conv)(num_prompts, &msgs, &ret_respp,
453 	    pam_convp->appdata_ptr);
454 
455 	if (rc == PAM_SUCCESS) {
456 		for (i = 0; i < num_prompts; i++) {
457 			/* convert PAM response to krb prompt reply format */
458 			prompts[i].reply->length = strlen(ret_respp[i].resp) +
459 			    1; /* adding 1 for NULL terminator */
460 			prompts[i].reply->data = ret_respp[i].resp;
461 		}
462 		/*
463 		 * Note, just free ret_respp, not the resp data since that is
464 		 * being referenced by the krb prompt reply data pointer.
465 		 */
466 		free(ret_respp);
467 	}
468 
469 cleanup:
470 	for (i = 0; i < num_prompts; i++) {
471 		if (msgs[i].msg) {
472 			free(msgs[i].msg);
473 		}
474 	}
475 	free(msgs);
476 	return (rc);
477 }
478 
479 int
480 attempt_krb5_auth(
481 	pam_handle_t *pamh,
482 	krb5_module_data_t	*kmd,
483 	char		*user,
484 	char		**krb5_pass,
485 	boolean_t	verify_tik)
486 {
487 	krb5_principal	me = NULL, clientp = NULL;
488 	krb5_principal	server = NULL, serverp = NULL;
489 	krb5_creds	*my_creds;
490 	krb5_timestamp	now;
491 	krb5_error_code	code = 0;
492 	char		kuser[2*MAXHOSTNAMELEN];
493 	krb5_deltat	lifetime;
494 	krb5_deltat	rlife;
495 	krb5_deltat	krb5_max_duration;
496 	int		options = KRB5_DEFAULT_OPTIONS;
497 	krb5_data tgtname = {
498 		0,
499 		KRB5_TGS_NAME_SIZE,
500 		KRB5_TGS_NAME
501 	};
502 	krb5_get_init_creds_opt opts;
503 	krb5_kdc_rep *as_reply = NULL;
504 	/*
505 	 * "result" should not be assigned PAM_SUCCESS unless
506 	 * authentication has succeeded and there are no other errors.
507 	 *
508 	 * "code" is sometimes used for PAM codes, sometimes for krb5
509 	 * codes.  Be careful.
510 	 */
511 	int result = PAM_AUTH_ERR;
512 
513 	if (kmd->debug)
514 		__pam_log(LOG_AUTH | LOG_DEBUG,
515 		    "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
516 		    user ? user : "<null>");
517 
518 	krb5_get_init_creds_opt_init(&opts);
519 
520 	/* need to free context with krb5_free_context */
521 	if (code = krb5_init_secure_context(&kmd->kcontext)) {
522 		__pam_log(LOG_AUTH | LOG_ERR,
523 			"PAM-KRB5 (auth): Error initializing "
524 			"krb5: %s",
525 			error_message(code));
526 		return (PAM_SYSTEM_ERR);
527 	}
528 
529 	if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
530 		2*MAXHOSTNAMELEN)) != 0) {
531 		/* get_kmd_kuser returns proper PAM error statuses */
532 		return (code);
533 	}
534 
535 	if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) {
536 		krb5_free_context(kmd->kcontext);
537 		kmd->kcontext = NULL;
538 		return (PAM_SYSTEM_ERR);
539 	}
540 
541 	/* call krb5_free_cred_contents() on error */
542 	my_creds = &kmd->initcreds;
543 
544 	if ((code =
545 	    krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) {
546 		result = PAM_SYSTEM_ERR;
547 		goto out_err;
548 	}
549 	clientp = my_creds->client;
550 
551 	if (code = krb5_build_principal_ext(kmd->kcontext, &server,
552 			    krb5_princ_realm(kmd->kcontext, me)->length,
553 			    krb5_princ_realm(kmd->kcontext, me)->data,
554 			    tgtname.length, tgtname.data,
555 			    krb5_princ_realm(kmd->kcontext, me)->length,
556 			    krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
557 		__pam_log(LOG_AUTH | LOG_ERR,
558 			"PAM-KRB5 (auth): attempt_krb5_auth: "
559 			"krb5_build_princ_ext failed: %s",
560 			error_message(code));
561 		result = PAM_SYSTEM_ERR;
562 		goto out;
563 	}
564 
565 	if (code = krb5_copy_principal(kmd->kcontext, server,
566 				&my_creds->server)) {
567 		result = PAM_SYSTEM_ERR;
568 		goto out_err;
569 	}
570 	serverp = my_creds->server;
571 
572 	if (code = krb5_timeofday(kmd->kcontext, &now)) {
573 		__pam_log(LOG_AUTH | LOG_ERR,
574 			"PAM-KRB5 (auth): attempt_krb5_auth: "
575 			"krb5_timeofday failed: %s",
576 			error_message(code));
577 		result = PAM_SYSTEM_ERR;
578 		goto out;
579 	}
580 
581 	/*
582 	 * set the values for lifetime and rlife to be the maximum
583 	 * possible
584 	 */
585 	krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
586 	lifetime = krb5_max_duration;
587 	rlife = krb5_max_duration;
588 
589 	/*
590 	 * Let us get the values for various options
591 	 * from Kerberos configuration file
592 	 */
593 
594 	krb_realm = krb5_princ_realm(kmd->kcontext, me)->data;
595 	profile_get_options_boolean(kmd->kcontext->profile,
596 				    realmdef, config_option);
597 	profile_get_options_boolean(kmd->kcontext->profile,
598 				    appdef, config_option);
599 	profile_get_options_string(kmd->kcontext->profile,
600 				realmdef, config_times);
601 	profile_get_options_string(kmd->kcontext->profile,
602 				appdef, config_times);
603 
604 	if (renew_timeval) {
605 		code = krb5_string_to_deltat(renew_timeval, &rlife);
606 		if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
607 			__pam_log(LOG_AUTH | LOG_ERR,
608 				    "PAM-KRB5 (auth): Bad max_renewable_life "
609 				    " value '%s' in Kerberos config file",
610 			    renew_timeval);
611 			result = PAM_SYSTEM_ERR;
612 			goto out;
613 		}
614 	}
615 	if (life_timeval) {
616 		code = krb5_string_to_deltat(life_timeval, &lifetime);
617 		if (code != 0 || lifetime == 0 ||
618 		    lifetime > krb5_max_duration) {
619 			__pam_log(LOG_AUTH | LOG_ERR,
620 				"lifetime value '%s' in Kerberos config file",
621 			    life_timeval);
622 			result = PAM_SYSTEM_ERR;
623 			goto out;
624 		}
625 	}
626 	/*  start timer when request gets to KDC */
627 	my_creds->times.starttime = 0;
628 	my_creds->times.endtime = now + lifetime;
629 
630 	if (options & KDC_OPT_RENEWABLE) {
631 		my_creds->times.renew_till = now + rlife;
632 	} else
633 		my_creds->times.renew_till = 0;
634 
635 	krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime);
636 
637 	if (proxiable_flag) { 		/* Set in config file */
638 		if (kmd->debug)
639 			__pam_log(LOG_AUTH | LOG_DEBUG,
640 				"PAM-KRB5 (auth): Proxiable tickets "
641 				"requested");
642 		krb5_get_init_creds_opt_set_proxiable(&opts, TRUE);
643 	}
644 	if (forwardable_flag) {
645 		if (kmd->debug)
646 			__pam_log(LOG_AUTH | LOG_DEBUG,
647 				"PAM-KRB5 (auth): Forwardable tickets "
648 				"requested");
649 		krb5_get_init_creds_opt_set_forwardable(&opts, TRUE);
650 	}
651 	if (renewable_flag) {
652 		if (kmd->debug)
653 			__pam_log(LOG_AUTH | LOG_DEBUG,
654 				"PAM-KRB5 (auth): Renewable tickets "
655 				"requested");
656 		krb5_get_init_creds_opt_set_renew_life(&opts, rlife);
657 	}
658 	if (no_address_flag) {
659 		if (kmd->debug)
660 			__pam_log(LOG_AUTH | LOG_DEBUG,
661 				"PAM-KRB5 (auth): Addressless tickets "
662 				"requested");
663 		krb5_get_init_creds_opt_set_address_list(&opts, NULL);
664 	}
665 
666 	/*
667 	 * mech_krb5 interprets empty passwords as NULL passwords and tries to
668 	 * read a password from stdin. Since we are in pam this is bad and
669 	 * should not be allowed.
670 	 *
671 	 * Note, the logic now is that if the preauth_type is PKINIT then
672 	 * provide a proper PAMcentric prompt function that the underlying
673 	 * PKINIT preauth plugin will use to prompt for the PIN.
674 	 */
675 	if (kmd->preauth_type == KRB_PKINIT) {
676 		/*
677 		 * Do PKINIT preauth
678 		 *
679 		 * Note: we want to limit preauth types to just those for PKINIT
680 		 * but krb5_get_init_creds() doesn't support that at this point.
681 		 * Instead we rely on pam_krb5_prompter() to limit prompts to
682 		 * non-password types.  So all we can do here is set the preauth
683 		 * list so krb5_get_init_creds() will try that first.
684 		 */
685 		krb5_preauthtype pk_pa_list[] = {
686 			KRB5_PADATA_PK_AS_REQ,
687 			KRB5_PADATA_PK_AS_REQ_OLD
688 		};
689 		krb5_get_init_creds_opt_set_preauth_list(&opts, pk_pa_list, 2);
690 
691 		if (*krb5_pass == NULL) {
692 			/* let preauth plugin prompt for PIN */
693 			code = __krb5_get_init_creds_password(kmd->kcontext,
694 			    my_creds,
695 			    me,
696 			    NULL, /* clear text passwd */
697 			    pam_krb5_prompter, /* prompter */
698 			    pamh, /* prompter data */
699 			    0, /* start time */
700 			    NULL, /* defaults to krbtgt@REALM */
701 			    &opts,
702 			    &as_reply);
703 		} else {
704 			/*
705 			 * krb pkinit does not support setting the PIN so we
706 			 * punt on trying to use krb5_pass as the PIN for now.
707 			 * Note that once this is supported by pkinit the code
708 			 * should make sure krb5_pass isn't empty and if it is
709 			 * then that's an error.
710 			 */
711 			code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
712 		}
713 	} else {
714 		/*
715 		 * Do password based preauths
716 		 *
717 		 * See earlier PKINIT comment.  We are doing something similar
718 		 * here but we do not pass in a prompter (we assume
719 		 * pam_authtok_get has already prompted for that).
720 		 */
721 		if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) {
722 			code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
723 		} else {
724 			krb5_preauthtype pk_pa_list[] = {
725 				KRB5_PADATA_ENC_TIMESTAMP
726 			};
727 
728 			krb5_get_init_creds_opt_set_preauth_list(&opts,
729 			    pk_pa_list, 1);
730 
731 			/*
732 			 * We call our own private version of gic_pwd, because
733 			 * we need more information, such as password/account
734 			 * expiration, that is found in the as_reply.  The
735 			 * "prompter" interface is not granular enough for PAM
736 			 * to make use of.
737 			 */
738 			code = __krb5_get_init_creds_password(kmd->kcontext,
739 			    my_creds,
740 			    me,
741 			    *krb5_pass,	/* clear text passwd */
742 			    NULL,	/* prompter */
743 			    NULL,	/* data */
744 			    0,		/* start time */
745 			    NULL,	/* defaults to krbtgt@REALM */
746 			    &opts,
747 			    &as_reply);
748 		}
749 	}
750 
751 	if (kmd->debug)
752 		__pam_log(LOG_AUTH | LOG_DEBUG,
753 		    "PAM-KRB5 (auth): attempt_krb5_auth: "
754 		    "krb5_get_init_creds_password returns: %s",
755 		    code == 0 ? "SUCCESS" : error_message(code));
756 
757 	switch (code) {
758 	case 0:
759 		/* got a tgt, let's verify it */
760 		if (verify_tik) {
761 			krb5_verify_init_creds_opt vopts;
762 
763 			krb5_principal sp = NULL;
764 			char kt_name[MAX_KEYTAB_NAME_LEN];
765 			char *fqdn;
766 
767 			krb5_verify_init_creds_opt_init(&vopts);
768 
769 			code = krb5_verify_init_creds(kmd->kcontext,
770 				my_creds,
771 				NULL,	/* defaults to host/localhost@REALM */
772 				NULL,
773 				NULL,
774 				&vopts);
775 
776 			if (code) {
777 				result = PAM_SYSTEM_ERR;
778 
779 				/*
780 				 * Give a better error message when the
781 				 * keytable entry isn't found or the keytab
782 				 * file cannot be found.
783 				 */
784 				if (krb5_sname_to_principal(kmd->kcontext, NULL,
785 						NULL, KRB5_NT_SRV_HST, &sp))
786 					fqdn = "<fqdn>";
787 				else
788 					fqdn = sp->data[1].data;
789 
790 				if (krb5_kt_default_name(kmd->kcontext, kt_name,
791 							sizeof (kt_name)))
792 					(void) strncpy(kt_name,
793 						"default keytab",
794 						sizeof (kt_name));
795 
796 				switch (code) {
797 				case KRB5_KT_NOTFOUND:
798 					__pam_log(LOG_AUTH | LOG_ERR,
799 						"PAM-KRB5 (auth): "
800 						"krb5_verify_init_creds failed:"
801 						" Key table entry \"host/%s\""
802 						" not found in %s",
803 						fqdn, kt_name);
804 					break;
805 				case ENOENT:
806 					__pam_log(LOG_AUTH | LOG_ERR,
807 						"PAM-KRB5 (auth): "
808 						"krb5_verify_init_creds failed:"
809 						" Keytab file \"%s\""
810 						" does not exist.\n",
811 						kt_name);
812 					break;
813 				default:
814 					__pam_log(LOG_AUTH | LOG_ERR,
815 						"PAM-KRB5 (auth): "
816 						"krb5_verify_init_creds failed:"
817 						" %s",
818 						error_message(code));
819 					break;
820 				}
821 
822 				if (sp)
823 					krb5_free_principal(kmd->kcontext, sp);
824 			}
825 		}
826 
827 		if (code == 0)
828 			kmd->expiration = as_reply->enc_part2->key_exp;
829 
830 		break;
831 
832 	case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
833 		/*
834 		 * Since this principal is not part of the local
835 		 * Kerberos realm, we just return PAM_USER_UNKNOWN.
836 		 */
837 		result = PAM_USER_UNKNOWN;
838 
839 		if (kmd->debug)
840 			__pam_log(LOG_AUTH | LOG_DEBUG,
841 				"PAM-KRB5 (auth): attempt_krb5_auth:"
842 				" User is not part of the local Kerberos"
843 				" realm: %s", error_message(code));
844 		break;
845 
846 	case KRB5KDC_ERR_PREAUTH_FAILED:
847 	case KRB5KRB_AP_ERR_BAD_INTEGRITY:
848 		/*
849 		 * We could be trying the password from a previous
850 		 * pam authentication module, but we don't want to
851 		 * generate an error if the unix password is different
852 		 * than the Kerberos password...
853 		 */
854 		break;
855 
856 	case KRB5KDC_ERR_KEY_EXP:
857 		if (!kmd->err_on_exp) {
858 			/*
859 			 * Request a tik for changepw service and it will tell
860 			 * us if pw is good or not. If PKINIT is being done it
861 			 * is possible that *krb5_pass may be NULL so check for
862 			 * that.  If that is the case this function will return
863 			 * an error.
864 			 */
865 			if (*krb5_pass != NULL) {
866 				code = krb5_verifypw(kuser, *krb5_pass,
867 				    kmd->debug);
868 				if (kmd->debug) {
869 					__pam_log(LOG_AUTH | LOG_DEBUG,
870 					    "PAM-KRB5 (auth): "
871 					    "attempt_krb5_auth: "
872 					    "verifypw %d", code);
873 				}
874 				if (code == 0) {
875 					/*
876 					 * pw is good, set age status for
877 					 * acct_mgmt.
878 					 */
879 					kmd->age_status = PAM_NEW_AUTHTOK_REQD;
880 				}
881 			}
882 
883 		}
884 		break;
885 
886 	default:
887 		result = PAM_SYSTEM_ERR;
888 		if (kmd->debug)
889 			__pam_log(LOG_AUTH | LOG_DEBUG,
890 				"PAM-KRB5 (auth): error %d - %s",
891 				code, error_message(code));
892 		break;
893 	}
894 
895 	if (code == 0) {
896 		/*
897 		 * success for the entered pw or PKINIT succeeded.
898 		 *
899 		 * we can't rely on the pw in PAM_AUTHTOK
900 		 * to be the (correct) krb5 one so
901 		 * store krb5 pw in module data for
902 		 * use in acct_mgmt.  Note that *krb5_pass may be NULL if we're
903 		 * doing PKINIT.
904 		 */
905 		if (*krb5_pass != NULL &&
906 		    !(kmd->password = strdup(*krb5_pass))) {
907 			__pam_log(LOG_AUTH | LOG_ERR,
908 				  "Cannot strdup password");
909 			result = PAM_BUF_ERR;
910 			goto out_err;
911 		}
912 
913 		result = PAM_SUCCESS;
914 		goto out;
915 	}
916 
917 out_err:
918 	/* jump (or reach) here if error and cred cache has been init */
919 
920 	if (kmd->debug)
921 		__pam_log(LOG_AUTH | LOG_DEBUG,
922 		    "PAM-KRB5 (auth): clearing initcreds in "
923 		    "pam_authenticate()");
924 
925 	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
926 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
927 
928 out:
929 	if (server)
930 		krb5_free_principal(kmd->kcontext, server);
931 	if (me)
932 		krb5_free_principal(kmd->kcontext, me);
933 	if (as_reply)
934 		krb5_free_kdc_rep(kmd->kcontext, as_reply);
935 
936 	/*
937 	 * clientp or serverp could be NULL in certain error cases in this
938 	 * function.  mycreds->[client|server] could also be NULL in case
939 	 * of error in this function, see out_err above.  The pointers clientp
940 	 * and serverp reference the input argument in my_creds for
941 	 * get_init_creds and must be freed if the input argument does not
942 	 * match the output argument, which occurs during a successful call
943 	 * to get_init_creds.
944 	 */
945 	if (clientp && my_creds->client && clientp != my_creds->client)
946 		krb5_free_principal(kmd->kcontext, clientp);
947 	if (serverp && my_creds->server && serverp != my_creds->server)
948 		krb5_free_principal(kmd->kcontext, serverp);
949 
950 	if (kmd->kcontext) {
951 		krb5_free_context(kmd->kcontext);
952 		kmd->kcontext = NULL;
953 	}
954 
955 	if (kmd->debug)
956 		__pam_log(LOG_AUTH | LOG_DEBUG,
957 		    "PAM-KRB5 (auth): attempt_krb5_auth returning %d",
958 		    result);
959 
960 	return (kmd->auth_status = result);
961 }
962 
963 /*ARGSUSED*/
964 void
965 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status)
966 {
967 	krb5_module_data_t *kmd = (krb5_module_data_t *)data;
968 
969 	if (kmd == NULL)
970 		return;
971 
972 	if (kmd->debug) {
973 		__pam_log(LOG_AUTH | LOG_DEBUG,
974 			    "PAM-KRB5 (auth): krb5_cleanup auth_status = %d",
975 		    kmd->auth_status);
976 	}
977 
978 	/*
979 	 * Apps could be calling pam_end here, so we should always clean
980 	 * up regardless of success or failure here.
981 	 */
982 	if (kmd->ccache)
983 		(void) krb5_cc_close(kmd->kcontext, kmd->ccache);
984 
985 	if (kmd->password) {
986 		(void) memset(kmd->password, 0, strlen(kmd->password));
987 		free(kmd->password);
988 	}
989 
990 	if (kmd->user)
991 		free(kmd->user);
992 
993 	if (kmd->env)
994 		free(kmd->env);
995 
996 	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
997 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
998 
999 	free(kmd);
1000 }
1001