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