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