xref: /illumos-gate/usr/src/lib/pam_modules/krb5/krb5_setcred.c (revision c9eab9d4e096bb9b983e9b007577edfa73c32eff)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <libintl.h>
27 #include <security/pam_appl.h>
28 #include <security/pam_modules.h>
29 #include <string.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <pwd.h>
34 #include <syslog.h>
35 #include <libintl.h>
36 #include <k5-int.h>
37 #include <netdb.h>
38 #include <unistd.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <errno.h>
42 #include <com_err.h>
43 
44 #include "utils.h"
45 #include "krb5_repository.h"
46 
47 #define	PAMTXD			"SUNW_OST_SYSOSPAM"
48 #define	KRB5_DEFAULT_LIFE	60*60*10  /* 10 hours */
49 
50 extern void krb5_cleanup(pam_handle_t *, void *, int);
51 
52 static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
53 static int attempt_delete_initcred(krb5_module_data_t *);
54 static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
55 		krb5_principal, int);
56 
57 extern uint_t kwarn_add_warning(char *, int);
58 extern uint_t kwarn_del_warning(char *);
59 
60 /*
61  * pam_sm_setcred
62  */
63 int
64 pam_sm_setcred(
65 	pam_handle_t *pamh,
66 	int	flags,
67 	int	argc,
68 	const char **argv)
69 {
70 	int	i;
71 	int	err = 0;
72 	int	debug = 0;
73 	krb5_module_data_t	*kmd = NULL;
74 	char			*user = NULL;
75 	krb5_repository_data_t	*krb5_data = NULL;
76 	pam_repository_t	*rep_data = NULL;
77 
78 	for (i = 0; i < argc; i++) {
79 		if (strcasecmp(argv[i], "debug") == 0)
80 			debug = 1;
81 		else if (strcasecmp(argv[i], "nowarn") == 0)
82 			flags = flags | PAM_SILENT;
83 	}
84 
85 	if (debug)
86 		__pam_log(LOG_AUTH | LOG_DEBUG,
87 		    "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x",
88 		    flags & PAM_SILENT ? 1 : 0, flags);
89 
90 	/* make sure flags are valid */
91 	if (flags &&
92 	    !(flags & PAM_ESTABLISH_CRED) &&
93 	    !(flags & PAM_REINITIALIZE_CRED) &&
94 	    !(flags & PAM_REFRESH_CRED) &&
95 	    !(flags & PAM_DELETE_CRED) &&
96 	    !(flags & PAM_SILENT)) {
97 		__pam_log(LOG_AUTH | LOG_ERR,
98 		    "PAM-KRB5 (setcred): illegal flag %d", flags);
99 		err = PAM_SYSTEM_ERR;
100 		goto out;
101 	}
102 
103 	(void) pam_get_item(pamh, PAM_USER, (void**) &user);
104 
105 	if (user == NULL || *user == '\0')
106 		return (PAM_USER_UNKNOWN);
107 
108 	if (pam_get_data(pamh, KRB5_DATA, (const void**)&kmd) != PAM_SUCCESS) {
109 		if (debug) {
110 			__pam_log(LOG_AUTH | LOG_DEBUG,
111 			    "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p",
112 			    kmd);
113 		}
114 
115 		/*
116 		 * User  doesn't need to authenticate for PAM_REFRESH_CRED
117 		 * or for PAM_DELETE_CRED
118 		 */
119 		if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) {
120 			__pam_log(LOG_AUTH | LOG_DEBUG,
121 			    "PAM-KRB5 (setcred): inst kmd structure");
122 
123 			kmd = calloc(1, sizeof (krb5_module_data_t));
124 
125 			if (kmd == NULL)
126 				return (PAM_BUF_ERR);
127 
128 
129 			/*
130 			 * Need to initialize auth_status here to
131 			 * PAM_AUTHINFO_UNAVAIL else there is a false positive
132 			 * of PAM_SUCCESS.
133 			 */
134 			kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
135 
136 			if ((err = pam_set_data(pamh, KRB5_DATA,
137 			    kmd, &krb5_cleanup)) != PAM_SUCCESS) {
138 				free(kmd);
139 				return (PAM_SYSTEM_ERR);
140 			}
141 		} else {
142 			/*
143 			 * This could mean that we are not the account authority
144 			 * for the authenticated user.  Therefore we should
145 			 * return PAM_IGNORE in order to not affect the
146 			 * login process of said user.
147 			 */
148 			err = PAM_IGNORE;
149 			goto out;
150 		}
151 
152 	} else {  /* pam_get_data success */
153 		if (kmd == NULL) {
154 			if (debug) {
155 				__pam_log(LOG_AUTH | LOG_DEBUG,
156 				    "PAM-KRB5 (setcred): kmd structure"
157 				    " gotten but is NULL for user %s", user);
158 			}
159 			err = PAM_SYSTEM_ERR;
160 			goto out;
161 		}
162 
163 		if (debug)
164 			__pam_log(LOG_AUTH | LOG_DEBUG,
165 			    "PAM-KRB5 (setcred): kmd auth_status: %s",
166 			    pam_strerror(pamh, kmd->auth_status));
167 
168 		/*
169 		 * pam_auth has set status to ignore, so we also return ignore
170 		 */
171 		if (kmd->auth_status == PAM_IGNORE) {
172 			err = PAM_IGNORE;
173 			goto out;
174 		}
175 	}
176 
177 	kmd->debug = debug;
178 
179 	/*
180 	 * User must have passed pam_authenticate()
181 	 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED
182 	 */
183 	if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) &&
184 	    (kmd->auth_status != PAM_SUCCESS)) {
185 		if (kmd->debug)
186 			__pam_log(LOG_AUTH | LOG_DEBUG,
187 			    "PAM-KRB5 (setcred): unable to "
188 			    "setcreds, not authenticated!");
189 		return (PAM_CRED_UNAVAIL);
190 	}
191 
192 	/*
193 	 * We cannot assume that kmd->kcontext being non-NULL
194 	 * means it is valid.  Other pam_krb5 mods may have
195 	 * freed it but not reset it to NULL.
196 	 * Log a message when debugging to track down memory
197 	 * leaks.
198 	 */
199 	if (kmd->kcontext != NULL && kmd->debug)
200 		__pam_log(LOG_AUTH | LOG_DEBUG,
201 		    "PAM-KRB5 (setcred): kcontext != NULL, "
202 		    "possible memory leak.");
203 
204 	/*
205 	 * Use the authenticated and validated user, if applicable.
206 	 */
207 	if (kmd->user != NULL)
208 		user = kmd->user;
209 
210 	/*
211 	 * If auth was short-circuited we will not have anything to
212 	 * renew, so just return here.
213 	 */
214 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
215 
216 	if (rep_data != NULL) {
217 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
218 			if (debug)
219 				__pam_log(LOG_AUTH | LOG_DEBUG,
220 				    "PAM-KRB5 (setcred): wrong"
221 				    "repository found (%s), returning "
222 				    "PAM_IGNORE", rep_data->type);
223 			return (PAM_IGNORE);
224 		}
225 		if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
226 			krb5_data = (krb5_repository_data_t *)rep_data->scope;
227 
228 			if (krb5_data->flags ==
229 			    SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
230 			    krb5_data->principal != NULL &&
231 			    strlen(krb5_data->principal)) {
232 				if (debug)
233 					__pam_log(LOG_AUTH | LOG_DEBUG,
234 					    "PAM-KRB5 (setcred): "
235 					    "Principal %s already "
236 					    "authenticated, "
237 					    "cannot setcred",
238 					    krb5_data->principal);
239 				return (PAM_SUCCESS);
240 			}
241 		}
242 	}
243 
244 	if (flags & PAM_REINITIALIZE_CRED)
245 		err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED);
246 	else if (flags & PAM_REFRESH_CRED)
247 		err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED);
248 	else if (flags & PAM_DELETE_CRED)
249 		err = attempt_delete_initcred(kmd);
250 	else {
251 		/*
252 		 * Default case:  PAM_ESTABLISH_CRED
253 		 */
254 		err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED);
255 	}
256 
257 	if (err != PAM_SUCCESS)
258 		__pam_log(LOG_AUTH | LOG_ERR,
259 		    "PAM-KRB5 (setcred): pam_setcred failed "
260 		    "for %s (%s).", user, pam_strerror(pamh, err));
261 
262 out:
263 	if (kmd && kmd->kcontext) {
264 		/*
265 		 * free 'kcontext' field if it is allocated,
266 		 * kcontext is local to the operation being performed
267 		 * not considered global to the entire pam module.
268 		 */
269 		krb5_free_context(kmd->kcontext);
270 		kmd->kcontext = NULL;
271 	}
272 
273 	/*
274 	 * 'kmd' is not freed here, it is handled in krb5_cleanup
275 	 */
276 	if (debug)
277 		__pam_log(LOG_AUTH | LOG_DEBUG,
278 		    "PAM-KRB5 (setcred): end: %s",
279 		    pam_strerror(pamh, err));
280 	return (err);
281 }
282 
283 static int
284 attempt_refresh_cred(
285 	krb5_module_data_t	*kmd,
286 	char		*user,
287 	int	flag)
288 {
289 	krb5_principal	me;
290 	krb5_principal	server;
291 	krb5_error_code	code;
292 	char		kuser[2*MAXHOSTNAMELEN];
293 	krb5_data tgtname = {
294 		0,
295 		KRB5_TGS_NAME_SIZE,
296 		KRB5_TGS_NAME
297 	};
298 
299 	/* Create a new context here. */
300 	if (krb5_init_context(&kmd->kcontext) != 0) {
301 		if (kmd->debug)
302 			__pam_log(LOG_AUTH | LOG_DEBUG,
303 			    "PAM-KRB5 (setcred): unable to "
304 			    "initialize krb5 context");
305 		return (PAM_SYSTEM_ERR);
306 	}
307 
308 	if (krb5_cc_default(kmd->kcontext, &kmd->ccache) != 0) {
309 		return (PAM_SYSTEM_ERR);
310 	}
311 
312 	if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
313 	    2*MAXHOSTNAMELEN)) != 0) {
314 		return (code);
315 	}
316 
317 	if (krb5_parse_name(kmd->kcontext, kuser, &me) != 0) {
318 		return (PAM_SYSTEM_ERR);
319 	}
320 
321 	if (code = krb5_build_principal_ext(kmd->kcontext, &server,
322 	    krb5_princ_realm(kmd->kcontext, me)->length,
323 	    krb5_princ_realm(kmd->kcontext, me)->data,
324 	    tgtname.length, tgtname.data,
325 	    krb5_princ_realm(kmd->kcontext, me)->length,
326 	    krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
327 		krb5_free_principal(kmd->kcontext, me);
328 		return (PAM_SYSTEM_ERR);
329 	}
330 
331 	code = krb5_renew_tgt(kmd, me, server, flag);
332 
333 	krb5_free_principal(kmd->kcontext, server);
334 	krb5_free_principal(kmd->kcontext, me);
335 
336 	if (code) {
337 		if (kmd->debug)
338 			__pam_log(LOG_AUTH | LOG_DEBUG,
339 			    "PAM-KRB5(setcred): krb5_renew_tgt() "
340 			    "failed: %s", error_message((errcode_t)code));
341 		return (PAM_CRED_ERR);
342 	} else {
343 		return (PAM_SUCCESS);
344 	}
345 }
346 
347 /*
348  * This code will update the credential matching "server" in the user's
349  * credential cache.  The flag may be set to one of:
350  * PAM_REINITIALIZE_CRED/PAM_ESTABLISH_CRED - If we have new credentials then
351  *     create a new cred cache with these credentials else return failure.
352  * PAM_REFRESH_CRED - If we have new credentials then create a new cred cache
353  *  with these credentials else attempt to renew the credentials.
354  *
355  * Note for any of the flags that if a new credential does exist from the
356  * previous auth pass then this will overwrite any existing credentials in the
357  * credential cache.
358  */
359 static krb5_error_code
360 krb5_renew_tgt(
361 	krb5_module_data_t *kmd,
362 	krb5_principal	me,
363 	krb5_principal	server,
364 	int	flag)
365 {
366 	krb5_error_code	retval;
367 	krb5_creds	creds;
368 	krb5_creds	*renewed_cred = NULL;
369 	char		*client_name = NULL;
370 	char		*username = NULL;
371 
372 #define	my_creds	(kmd->initcreds)
373 
374 	if ((flag != PAM_REFRESH_CRED) &&
375 	    (flag != PAM_REINITIALIZE_CRED) &&
376 	    (flag != PAM_ESTABLISH_CRED))
377 		return (KRB5KRB_ERR_GENERIC);
378 
379 	/* this is needed only for the ktkt_warnd */
380 	if ((retval = krb5_unparse_name(kmd->kcontext, me, &client_name)) != 0)
381 		return (retval);
382 
383 	(void) memset(&creds, 0, sizeof (krb5_creds));
384 	if ((retval = krb5_copy_principal(kmd->kcontext,
385 	    server, &creds.server))) {
386 		if (kmd->debug)
387 			__pam_log(LOG_AUTH | LOG_DEBUG,
388 			    "PAM-KRB5 (setcred): krb5_copy_principal "
389 			    "failed: %s",
390 			    error_message((errcode_t)retval));
391 		goto cleanup_creds;
392 	}
393 
394 	/* obtain ticket & session key */
395 	retval = krb5_cc_get_principal(kmd->kcontext,
396 	    kmd->ccache, &creds.client);
397 	if (retval && (kmd->debug))
398 		__pam_log(LOG_AUTH | LOG_DEBUG,
399 		    "PAM-KRB5 (setcred): User not in cred "
400 		    "cache (%s)", error_message((errcode_t)retval));
401 
402 	/*
403 	 * We got here either with the ESTABLISH | REINIT | REFRESH flag and
404 	 * auth_status returns SUCCESS or REFRESH and auth_status failure.
405 	 *
406 	 * Rules:
407 	 * - If the prior auth pass was successful then store the new
408 	 * credentials in the cache, regardless of which flag.
409 	 *
410 	 * - Else if REFRESH flag is used and there are no new
411 	 * credentials then attempt to refresh the existing credentials.
412 	 *
413 	 * - Note, refresh will not work if "R" flag is not set in
414 	 * original credential.  We don't want to 2nd guess the
415 	 * intention of the person who created the existing credential.
416 	 */
417 	if (kmd->auth_status == PAM_SUCCESS) {
418 		/*
419 		 * Create a fresh ccache, and store the credentials
420 		 * we got from pam_authenticate()
421 		 */
422 		if ((retval = krb5_cc_initialize(kmd->kcontext,
423 		    kmd->ccache, me)) != 0) {
424 			__pam_log(LOG_AUTH | LOG_DEBUG,
425 			    "PAM-KRB5 (setcred): krb5_cc_initialize "
426 			    "failed: %s",
427 			    error_message((errcode_t)retval));
428 		} else if ((retval = krb5_cc_store_cred(kmd->kcontext,
429 		    kmd->ccache, &my_creds)) != 0) {
430 			__pam_log(LOG_AUTH | LOG_DEBUG,
431 			    "PAM-KRB5 (setcred): krb5_cc_store_cred "
432 			    "failed: %s",
433 			    error_message((errcode_t)retval));
434 		}
435 	} else if ((retval == 0) && (flag & PAM_REFRESH_CRED)) {
436 		/*
437 		 * If we only wanted to refresh the creds but failed
438 		 * due to expiration, lack of "R" flag, or other
439 		 * problems, return an error.
440 		 */
441 		if (retval = krb5_get_credentials_renew(kmd->kcontext,
442 		    0, kmd->ccache, &creds, &renewed_cred)) {
443 			if (kmd->debug) {
444 				__pam_log(LOG_AUTH | LOG_DEBUG,
445 				    "PAM-KRB5 (setcred): "
446 				    "krb5_get_credentials"
447 				    "_renew(update) failed: %s",
448 				    error_message((errcode_t)retval));
449 			}
450 		}
451 	} else {
452 		/*
453 		 * We failed to get the user's credentials.
454 		 * This might be due to permission error on the cache,
455 		 * or maybe we are looking in the wrong cache file!
456 		 */
457 		__pam_log(LOG_AUTH | LOG_ERR,
458 		    "PAM-KRB5 (setcred): Cannot find creds"
459 		    " for %s (%s)",
460 		    client_name ? client_name : "(unknown)",
461 		    error_message((errcode_t)retval));
462 	}
463 
464 cleanup_creds:
465 
466 	if ((retval == 0) && (client_name != NULL)) {
467 		/*
468 		 * Credential update was successful!
469 		 *
470 		 * We now chown the ccache to the appropriate uid/gid
471 		 * combination, if its a FILE based ccache.
472 		 */
473 		if (!kmd->env || strstr(kmd->env, "FILE:")) {
474 			uid_t uuid;
475 			gid_t ugid;
476 			char *tmpname = NULL;
477 			char *filepath = NULL;
478 
479 			username = strdup(client_name);
480 			if (username == NULL) {
481 				__pam_log(LOG_AUTH | LOG_ERR,
482 				    "PAM-KRB5 (setcred): Out of memory");
483 				retval = KRB5KRB_ERR_GENERIC;
484 				goto error;
485 			}
486 			if ((tmpname = strchr(username, '@')))
487 				*tmpname = '\0';
488 
489 			if (get_pw_uid(username, &uuid) == 0 ||
490 			    get_pw_gid(username, &ugid) == 0) {
491 				__pam_log(LOG_AUTH | LOG_ERR,
492 				    "PAM-KRB5 (setcred): Unable to "
493 				    "find matching uid/gid pair for user `%s'",
494 				    username);
495 				retval = KRB5KRB_ERR_GENERIC;
496 				goto error;
497 			}
498 
499 			if (!kmd->env) {
500 				char buffer[512];
501 
502 				if (snprintf(buffer, sizeof (buffer),
503 				    "%s=FILE:/tmp/krb5cc_%d", KRB5_ENV_CCNAME,
504 				    (int)uuid) >= sizeof (buffer)) {
505 					retval = KRB5KRB_ERR_GENERIC;
506 					goto error;
507 				}
508 
509 				/*
510 				 * We MUST copy this to the heap for the putenv
511 				 * to work!
512 				 */
513 				kmd->env = strdup(buffer);
514 				if (!kmd->env) {
515 					retval = ENOMEM;
516 					goto error;
517 				} else {
518 					if (putenv(kmd->env)) {
519 						retval = ENOMEM;
520 						goto error;
521 					}
522 				}
523 			}
524 
525 			/*
526 			 * We know at this point that kmd->env must start
527 			 * with the literal string "FILE:".  Set filepath
528 			 * character string to point to ":"
529 			 */
530 
531 			filepath = strchr(kmd->env, ':');
532 
533 			/*
534 			 * Now check if first char after ":" is null char
535 			 */
536 			if (filepath[1] == '\0') {
537 				__pam_log(LOG_AUTH | LOG_ERR,
538 				    "PAM-KRB5 (setcred): Invalid pathname "
539 				    "for credential cache of user `%s'",
540 				    username);
541 				retval = KRB5KRB_ERR_GENERIC;
542 				goto error;
543 			}
544 			if (chown(filepath+1, uuid, ugid)) {
545 				if (kmd->debug)
546 					__pam_log(LOG_AUTH | LOG_DEBUG,
547 					    "PAM-KRB5 (setcred): chown to user "
548 					    "`%s' failed for FILE=%s",
549 					    username, filepath);
550 			}
551 		}
552 	}
553 
554 error:
555 	if (retval == 0) {
556 		krb5_timestamp endtime;
557 
558 		if (renewed_cred && renewed_cred->times.endtime != 0)
559 			endtime = renewed_cred->times.endtime;
560 		else
561 			endtime = my_creds.times.endtime;
562 
563 		if (kmd->debug)
564 			__pam_log(LOG_AUTH | LOG_DEBUG,
565 			    "PAM-KRB5 (setcred): delete/add warning");
566 
567 		kwarn_del_warning(client_name);
568 		if (kwarn_add_warning(client_name, endtime) != 0) {
569 			__pam_log(LOG_AUTH | LOG_NOTICE,
570 			    "PAM-KRB5 (setcred): kwarn_add_warning"
571 			    " failed: ktkt_warnd(1M) down?");
572 		}
573 	}
574 
575 	if (renewed_cred != NULL)
576 		krb5_free_creds(kmd->kcontext, renewed_cred);
577 
578 	if (client_name != NULL)
579 		free(client_name);
580 
581 	if (username)
582 		free(username);
583 
584 	krb5_free_cred_contents(kmd->kcontext, &creds);
585 
586 	return (retval);
587 }
588 
589 /*
590  * Delete the user's credentials for this session
591  */
592 static int
593 attempt_delete_initcred(krb5_module_data_t *kmd)
594 {
595 	if (kmd == NULL)
596 		return (PAM_SUCCESS);
597 
598 	if (kmd->debug) {
599 		__pam_log(LOG_AUTH | LOG_DEBUG,
600 		    "PAM-KRB5 (setcred): deleting user's "
601 		    "credentials (initcreds)");
602 	}
603 	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
604 	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
605 	kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
606 	return (PAM_SUCCESS);
607 }
608