krb5_setcred.c revision 6488:b47361eaf8f2
1255722Semaste/*
2255722Semaste * CDDL HEADER START
3255722Semaste *
4255722Semaste * The contents of this file are subject to the terms of the
5255722Semaste * Common Development and Distribution License (the "License").
6255722Semaste * You may not use this file except in compliance with the License.
7255722Semaste *
8255722Semaste * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9255722Semaste * or http://www.opensolaris.org/os/licensing.
10255722Semaste * See the License for the specific language governing permissions
11255722Semaste * and limitations under the License.
12255722Semaste *
13255722Semaste * When distributing Covered Code, include this CDDL HEADER in each
14255722Semaste * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15255722Semaste * If applicable, add the following below this CDDL HEADER, with the
16255722Semaste * fields enclosed by brackets "[]" replaced with your own identifying
17255722Semaste * information: Portions Copyright [yyyy] [name of copyright owner]
18255722Semaste *
19255722Semaste * CDDL HEADER END
20255722Semaste */
21255722Semaste/*
22255722Semaste * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23255722Semaste * Use is subject to license terms.
24255722Semaste */
25255722Semaste
26255722Semaste#pragma ident	"%Z%%M%	%I%	%E% SMI"
27255722Semaste
28255722Semaste#include <libintl.h>
29255722Semaste#include <security/pam_appl.h>
30255722Semaste#include <security/pam_modules.h>
31255722Semaste#include <string.h>
32255722Semaste#include <stdio.h>
33255722Semaste#include <stdlib.h>
34255722Semaste#include <sys/types.h>
35255722Semaste#include <pwd.h>
36255722Semaste#include <syslog.h>
37255722Semaste#include <libintl.h>
38255722Semaste#include <krb5.h>
39255722Semaste#include <netdb.h>
40255722Semaste#include <unistd.h>
41255722Semaste#include <sys/stat.h>
42255722Semaste#include <fcntl.h>
43255722Semaste#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
52extern void krb5_cleanup(pam_handle_t *, void *, int);
53
54static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
55static int attempt_delete_initcred(krb5_module_data_t *);
56static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
57		krb5_principal, int);
58
59extern uint_t kwarn_add_warning(char *, int);
60extern uint_t kwarn_del_warning(char *);
61
62/*
63 * pam_sm_setcred
64 */
65int
66pam_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
264out:
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
285static int
286attempt_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 */
362static krb5_error_code
363krb5_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
509cleanup_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
564error:
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 */
599static int
600attempt_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