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