krb5_setcred.c revision 8991:5ab80299f68b
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 2009 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
50extern void krb5_cleanup(pam_handle_t *, void *, int);
51
52static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
53static int attempt_delete_initcred(krb5_module_data_t *);
54static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
55		krb5_principal, int);
56
57extern uint_t kwarn_add_warning(char *, int);
58extern uint_t kwarn_del_warning(char *);
59
60/*
61 * pam_sm_setcred
62 */
63int
64pam_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
262out:
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
283static int
284attempt_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_secure_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 */
359static krb5_error_code
360krb5_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
464cleanup_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
554error:
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 */
592static int
593attempt_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