gssd.c revision 250689
1/*-
2 * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
3 * Authors: Doug Rabson <dfr@rabson.org>
4 * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: stable/9/usr.sbin/gssd/gssd.c 250689 2013-05-16 00:52:08Z rmacklem $");
30
31#include <sys/param.h>
32#include <sys/stat.h>
33#include <sys/linker.h>
34#include <sys/module.h>
35#include <sys/queue.h>
36#include <sys/syslog.h>
37#include <ctype.h>
38#include <dirent.h>
39#include <err.h>
40#include <errno.h>
41#ifndef WITHOUT_KERBEROS
42#include <krb5.h>
43#endif
44#include <pwd.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include <gssapi/gssapi.h>
50#include <rpc/rpc.h>
51#include <rpc/rpc_com.h>
52
53#include "gssd.h"
54
55#ifndef _PATH_GSS_MECH
56#define _PATH_GSS_MECH	"/etc/gss/mech"
57#endif
58#ifndef _PATH_GSSDSOCK
59#define _PATH_GSSDSOCK	"/var/run/gssd.sock"
60#endif
61
62struct gss_resource {
63	LIST_ENTRY(gss_resource) gr_link;
64	uint64_t	gr_id;	/* indentifier exported to kernel */
65	void*		gr_res;	/* GSS-API resource pointer */
66};
67LIST_HEAD(gss_resource_list, gss_resource) gss_resources;
68int gss_resource_count;
69uint32_t gss_next_id;
70uint32_t gss_start_time;
71int debug_level;
72static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1];
73static char pref_realm[1024];
74
75static void gssd_load_mech(void);
76static int find_ccache_file(const char *, uid_t, char *);
77static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *);
78
79extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp);
80extern int gssd_syscall(char *path);
81
82int
83main(int argc, char **argv)
84{
85	/*
86	 * We provide an RPC service on a local-domain socket. The
87	 * kernel's GSS-API code will pass what it can't handle
88	 * directly to us.
89	 */
90	struct sockaddr_un sun;
91	int fd, oldmask, ch, debug;
92	SVCXPRT *xprt;
93
94	/*
95	 * Initialize the credential cache file name substring and the
96	 * search directory list.
97	 */
98	strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring));
99	ccfile_dirlist[0] = '\0';
100	pref_realm[0] = '\0';
101	debug = 0;
102	while ((ch = getopt(argc, argv, "ds:c:r:")) != -1) {
103		switch (ch) {
104		case 'd':
105			debug_level++;
106			break;
107		case 's':
108#ifndef WITHOUT_KERBEROS
109			/*
110			 * Set the directory search list. This enables use of
111			 * find_ccache_file() to search the directories for a
112			 * suitable credentials cache file.
113			 */
114			strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist));
115#else
116			errx(1, "This option not available when built"
117			    " without MK_KERBEROS\n");
118#endif
119			break;
120		case 'c':
121			/*
122			 * Specify a non-default credential cache file
123			 * substring.
124			 */
125			strlcpy(ccfile_substring, optarg,
126			    sizeof(ccfile_substring));
127			break;
128		case 'r':
129			/*
130			 * Set the preferred realm for the credential cache tgt.
131			 */
132			strlcpy(pref_realm, optarg, sizeof(pref_realm));
133			break;
134		default:
135			fprintf(stderr,
136			    "usage: %s [-d] [-s dir-list] [-c file-substring]"
137			    " [-r preferred-realm]\n", argv[0]);
138			exit(1);
139			break;
140		}
141	}
142
143	gssd_load_mech();
144
145	if (!debug_level)
146		daemon(0, 0);
147
148	memset(&sun, 0, sizeof sun);
149	sun.sun_family = AF_LOCAL;
150	unlink(_PATH_GSSDSOCK);
151	strcpy(sun.sun_path, _PATH_GSSDSOCK);
152	sun.sun_len = SUN_LEN(&sun);
153	fd = socket(AF_LOCAL, SOCK_STREAM, 0);
154	if (!fd) {
155		if (debug_level == 0) {
156			syslog(LOG_ERR, "Can't create local gssd socket");
157			exit(1);
158		}
159		err(1, "Can't create local gssd socket");
160	}
161	oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
162	if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) {
163		if (debug_level == 0) {
164			syslog(LOG_ERR, "Can't bind local gssd socket");
165			exit(1);
166		}
167		err(1, "Can't bind local gssd socket");
168	}
169	umask(oldmask);
170	if (listen(fd, SOMAXCONN) < 0) {
171		if (debug_level == 0) {
172			syslog(LOG_ERR, "Can't listen on local gssd socket");
173			exit(1);
174		}
175		err(1, "Can't listen on local gssd socket");
176	}
177	xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
178	if (!xprt) {
179		if (debug_level == 0) {
180			syslog(LOG_ERR,
181			    "Can't create transport for local gssd socket");
182			exit(1);
183		}
184		err(1, "Can't create transport for local gssd socket");
185	}
186	if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) {
187		if (debug_level == 0) {
188			syslog(LOG_ERR,
189			    "Can't register service for local gssd socket");
190			exit(1);
191		}
192		err(1, "Can't register service for local gssd socket");
193	}
194
195	LIST_INIT(&gss_resources);
196	gss_next_id = 1;
197	gss_start_time = time(0);
198
199	gssd_syscall(_PATH_GSSDSOCK);
200	svc_run();
201
202	return (0);
203}
204
205static void
206gssd_load_mech(void)
207{
208	FILE		*fp;
209	char		buf[256];
210	char		*p;
211	char		*name, *oid, *lib, *kobj;
212
213	fp = fopen(_PATH_GSS_MECH, "r");
214	if (!fp)
215		return;
216
217	while (fgets(buf, sizeof(buf), fp)) {
218		if (*buf == '#')
219			continue;
220		p = buf;
221		name = strsep(&p, "\t\n ");
222		if (p) while (isspace(*p)) p++;
223		oid = strsep(&p, "\t\n ");
224		if (p) while (isspace(*p)) p++;
225		lib = strsep(&p, "\t\n ");
226		if (p) while (isspace(*p)) p++;
227		kobj = strsep(&p, "\t\n ");
228		if (!name || !oid || !lib || !kobj)
229			continue;
230
231		if (strcmp(kobj, "-")) {
232			/*
233			 * Attempt to load the kernel module if its
234			 * not already present.
235			 */
236			if (modfind(kobj) < 0) {
237				if (kldload(kobj) < 0) {
238					fprintf(stderr,
239			"%s: can't find or load kernel module %s for %s\n",
240					    getprogname(), kobj, name);
241				}
242			}
243		}
244	}
245	fclose(fp);
246}
247
248static void *
249gssd_find_resource(uint64_t id)
250{
251	struct gss_resource *gr;
252
253	if (!id)
254		return (NULL);
255
256	LIST_FOREACH(gr, &gss_resources, gr_link)
257		if (gr->gr_id == id)
258			return (gr->gr_res);
259
260	return (NULL);
261}
262
263static uint64_t
264gssd_make_resource(void *res)
265{
266	struct gss_resource *gr;
267
268	if (!res)
269		return (0);
270
271	gr = malloc(sizeof(struct gss_resource));
272	if (!gr)
273		return (0);
274	gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32);
275	gr->gr_res = res;
276	LIST_INSERT_HEAD(&gss_resources, gr, gr_link);
277	gss_resource_count++;
278	if (debug_level > 1)
279		printf("%d resources allocated\n", gss_resource_count);
280
281	return (gr->gr_id);
282}
283
284static void
285gssd_delete_resource(uint64_t id)
286{
287	struct gss_resource *gr;
288
289	LIST_FOREACH(gr, &gss_resources, gr_link) {
290		if (gr->gr_id == id) {
291			LIST_REMOVE(gr, gr_link);
292			free(gr);
293			gss_resource_count--;
294			if (debug_level > 1)
295				printf("%d resources allocated\n",
296				    gss_resource_count);
297			return;
298		}
299	}
300}
301
302bool_t
303gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp)
304{
305
306	return (TRUE);
307}
308
309bool_t
310gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp)
311{
312	gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
313	gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
314	gss_name_t name = GSS_C_NO_NAME;
315	char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
316	int gotone;
317
318	memset(result, 0, sizeof(*result));
319	if (ccfile_dirlist[0] != '\0' && argp->cred == 0) {
320		/*
321		 * For the "-s" case and no credentials provided as an
322		 * argument, search the directory list for an appropriate
323		 * credential cache file. If the search fails, return failure.
324		 */
325		gotone = 0;
326		cp = ccfile_dirlist;
327		do {
328			cp2 = strchr(cp, ':');
329			if (cp2 != NULL)
330				*cp2 = '\0';
331			gotone = find_ccache_file(cp, argp->uid, ccname);
332			if (gotone != 0)
333				break;
334			if (cp2 != NULL)
335				*cp2++ = ':';
336			cp = cp2;
337		} while (cp != NULL && *cp != '\0');
338		if (gotone == 0) {
339			result->major_status = GSS_S_CREDENTIALS_EXPIRED;
340			return (TRUE);
341		}
342	} else {
343		/*
344		 * If there wasn't a "-s" option or the credentials have
345		 * been provided as an argument, do it the old way.
346		 * When credentials are provided, the uid should be root.
347		 */
348		if (argp->cred != 0 && argp->uid != 0) {
349			if (debug_level == 0)
350				syslog(LOG_ERR, "gss_init_sec_context:"
351				    " cred for non-root");
352			else
353				fprintf(stderr, "gss_init_sec_context:"
354				    " cred for non-root\n");
355		}
356		snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
357		    (int) argp->uid);
358	}
359	setenv("KRB5CCNAME", ccname, TRUE);
360
361	if (argp->cred) {
362		cred = gssd_find_resource(argp->cred);
363		if (!cred) {
364			result->major_status = GSS_S_CREDENTIALS_EXPIRED;
365			return (TRUE);
366		}
367	}
368	if (argp->ctx) {
369		ctx = gssd_find_resource(argp->ctx);
370		if (!ctx) {
371			result->major_status = GSS_S_CONTEXT_EXPIRED;
372			return (TRUE);
373		}
374	}
375	if (argp->name) {
376		name = gssd_find_resource(argp->name);
377		if (!name) {
378			result->major_status = GSS_S_BAD_NAME;
379			return (TRUE);
380		}
381	}
382
383	result->major_status = gss_init_sec_context(&result->minor_status,
384	    cred, &ctx, name, argp->mech_type,
385	    argp->req_flags, argp->time_req, argp->input_chan_bindings,
386	    &argp->input_token, &result->actual_mech_type,
387	    &result->output_token, &result->ret_flags, &result->time_rec);
388
389	if (result->major_status == GSS_S_COMPLETE
390	    || result->major_status == GSS_S_CONTINUE_NEEDED) {
391		if (argp->ctx)
392			result->ctx = argp->ctx;
393		else
394			result->ctx = gssd_make_resource(ctx);
395	}
396
397	return (TRUE);
398}
399
400bool_t
401gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp)
402{
403	gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
404	gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
405	gss_name_t src_name;
406	gss_cred_id_t delegated_cred_handle;
407
408	memset(result, 0, sizeof(*result));
409	if (argp->ctx) {
410		ctx = gssd_find_resource(argp->ctx);
411		if (!ctx) {
412			result->major_status = GSS_S_CONTEXT_EXPIRED;
413			return (TRUE);
414		}
415	}
416	if (argp->cred) {
417		cred = gssd_find_resource(argp->cred);
418		if (!cred) {
419			result->major_status = GSS_S_CREDENTIALS_EXPIRED;
420			return (TRUE);
421		}
422	}
423
424	memset(result, 0, sizeof(*result));
425	result->major_status = gss_accept_sec_context(&result->minor_status,
426	    &ctx, cred, &argp->input_token, argp->input_chan_bindings,
427	    &src_name, &result->mech_type, &result->output_token,
428	    &result->ret_flags, &result->time_rec,
429	    &delegated_cred_handle);
430
431	if (result->major_status == GSS_S_COMPLETE
432	    || result->major_status == GSS_S_CONTINUE_NEEDED) {
433		if (argp->ctx)
434			result->ctx = argp->ctx;
435		else
436			result->ctx = gssd_make_resource(ctx);
437		result->src_name = gssd_make_resource(src_name);
438		result->delegated_cred_handle =
439			gssd_make_resource(delegated_cred_handle);
440	}
441
442	return (TRUE);
443}
444
445bool_t
446gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp)
447{
448	gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);
449
450	if (ctx) {
451		result->major_status = gss_delete_sec_context(
452			&result->minor_status, &ctx, &result->output_token);
453		gssd_delete_resource(argp->ctx);
454	} else {
455		result->major_status = GSS_S_COMPLETE;
456		result->minor_status = 0;
457	}
458
459	return (TRUE);
460}
461
462bool_t
463gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp)
464{
465	gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);
466
467	if (ctx) {
468		result->major_status = gss_export_sec_context(
469			&result->minor_status, &ctx,
470			&result->interprocess_token);
471		result->format = KGSS_HEIMDAL_1_1;
472		gssd_delete_resource(argp->ctx);
473	} else {
474		result->major_status = GSS_S_FAILURE;
475		result->minor_status = 0;
476		result->interprocess_token.length = 0;
477		result->interprocess_token.value = NULL;
478	}
479
480	return (TRUE);
481}
482
483bool_t
484gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp)
485{
486	gss_name_t name;
487
488	result->major_status = gss_import_name(&result->minor_status,
489	    &argp->input_name_buffer, argp->input_name_type, &name);
490
491	if (result->major_status == GSS_S_COMPLETE)
492		result->output_name = gssd_make_resource(name);
493	else
494		result->output_name = 0;
495
496	return (TRUE);
497}
498
499bool_t
500gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp)
501{
502	gss_name_t name = gssd_find_resource(argp->input_name);
503	gss_name_t output_name;
504
505	memset(result, 0, sizeof(*result));
506	if (!name) {
507		result->major_status = GSS_S_BAD_NAME;
508		return (TRUE);
509	}
510
511	result->major_status = gss_canonicalize_name(&result->minor_status,
512	    name, argp->mech_type, &output_name);
513
514	if (result->major_status == GSS_S_COMPLETE)
515		result->output_name = gssd_make_resource(output_name);
516	else
517		result->output_name = 0;
518
519	return (TRUE);
520}
521
522bool_t
523gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp)
524{
525	gss_name_t name = gssd_find_resource(argp->input_name);
526
527	memset(result, 0, sizeof(*result));
528	if (!name) {
529		result->major_status = GSS_S_BAD_NAME;
530		return (TRUE);
531	}
532
533	result->major_status = gss_export_name(&result->minor_status,
534	    name, &result->exported_name);
535
536	return (TRUE);
537}
538
539bool_t
540gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp)
541{
542	gss_name_t name = gssd_find_resource(argp->input_name);
543
544	if (name) {
545		result->major_status = gss_release_name(&result->minor_status,
546		    &name);
547		gssd_delete_resource(argp->input_name);
548	} else {
549		result->major_status = GSS_S_COMPLETE;
550		result->minor_status = 0;
551	}
552
553	return (TRUE);
554}
555
556bool_t
557gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp)
558{
559	gss_name_t name = gssd_find_resource(argp->pname);
560	uid_t uid;
561	char buf[1024], *bufp;
562	struct passwd pwd, *pw;
563	size_t buflen;
564	int error;
565	static size_t buflen_hint = 1024;
566
567	memset(result, 0, sizeof(*result));
568	if (name) {
569		result->major_status =
570			gss_pname_to_uid(&result->minor_status,
571			    name, argp->mech, &uid);
572		if (result->major_status == GSS_S_COMPLETE) {
573			result->uid = uid;
574			buflen = buflen_hint;
575			for (;;) {
576				pw = NULL;
577				bufp = buf;
578				if (buflen > sizeof(buf))
579					bufp = malloc(buflen);
580				if (bufp == NULL)
581					break;
582				error = getpwuid_r(uid, &pwd, bufp, buflen,
583				    &pw);
584				if (error != ERANGE)
585					break;
586				if (buflen > sizeof(buf))
587					free(bufp);
588				buflen += 1024;
589				if (buflen > buflen_hint)
590					buflen_hint = buflen;
591			}
592			if (pw) {
593				int len = NGRPS;
594				int groups[NGRPS];
595				result->gid = pw->pw_gid;
596				getgrouplist(pw->pw_name, pw->pw_gid,
597				    groups, &len);
598				result->gidlist.gidlist_len = len;
599				result->gidlist.gidlist_val =
600					mem_alloc(len * sizeof(int));
601				memcpy(result->gidlist.gidlist_val, groups,
602				    len * sizeof(int));
603			} else {
604				result->gid = 65534;
605				result->gidlist.gidlist_len = 0;
606				result->gidlist.gidlist_val = NULL;
607			}
608			if (bufp != NULL && buflen > sizeof(buf))
609				free(bufp);
610		}
611	} else {
612		result->major_status = GSS_S_BAD_NAME;
613		result->minor_status = 0;
614	}
615
616	return (TRUE);
617}
618
619bool_t
620gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp)
621{
622	gss_name_t desired_name = GSS_C_NO_NAME;
623	gss_cred_id_t cred;
624	char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
625	int gotone;
626
627	memset(result, 0, sizeof(*result));
628	if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) {
629		/*
630		 * For the "-s" case and no name provided as an
631		 * argument, search the directory list for an appropriate
632		 * credential cache file. If the search fails, return failure.
633		 */
634		gotone = 0;
635		cp = ccfile_dirlist;
636		do {
637			cp2 = strchr(cp, ':');
638			if (cp2 != NULL)
639				*cp2 = '\0';
640			gotone = find_ccache_file(cp, argp->uid, ccname);
641			if (gotone != 0)
642				break;
643			if (cp2 != NULL)
644				*cp2++ = ':';
645			cp = cp2;
646		} while (cp != NULL && *cp != '\0');
647		if (gotone == 0) {
648			result->major_status = GSS_S_CREDENTIALS_EXPIRED;
649			return (TRUE);
650		}
651	} else {
652		/*
653		 * If there wasn't a "-s" option or the name has
654		 * been provided as an argument, do it the old way.
655		 * When a name is provided, it will normally exist in the
656		 * default keytab file and the uid will be root.
657		 */
658		if (argp->desired_name != 0 && argp->uid != 0) {
659			if (debug_level == 0)
660				syslog(LOG_ERR, "gss_acquire_cred:"
661				    " principal_name for non-root");
662			else
663				fprintf(stderr, "gss_acquire_cred:"
664				    " principal_name for non-root\n");
665		}
666		snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
667		    (int) argp->uid);
668	}
669	setenv("KRB5CCNAME", ccname, TRUE);
670
671	if (argp->desired_name) {
672		desired_name = gssd_find_resource(argp->desired_name);
673		if (!desired_name) {
674			result->major_status = GSS_S_BAD_NAME;
675			return (TRUE);
676		}
677	}
678
679	result->major_status = gss_acquire_cred(&result->minor_status,
680	    desired_name, argp->time_req, argp->desired_mechs,
681	    argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec);
682
683	if (result->major_status == GSS_S_COMPLETE)
684		result->output_cred = gssd_make_resource(cred);
685	else
686		result->output_cred = 0;
687
688	return (TRUE);
689}
690
691bool_t
692gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp)
693{
694	gss_cred_id_t cred = gssd_find_resource(argp->cred);
695
696	memset(result, 0, sizeof(*result));
697	if (!cred) {
698		result->major_status = GSS_S_CREDENTIALS_EXPIRED;
699		return (TRUE);
700	}
701
702	result->major_status = gss_set_cred_option(&result->minor_status,
703	    &cred, argp->option_name, &argp->option_value);
704
705	return (TRUE);
706}
707
708bool_t
709gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp)
710{
711	gss_cred_id_t cred = gssd_find_resource(argp->cred);
712
713	if (cred) {
714		result->major_status = gss_release_cred(&result->minor_status,
715		    &cred);
716		gssd_delete_resource(argp->cred);
717	} else {
718		result->major_status = GSS_S_COMPLETE;
719		result->minor_status = 0;
720	}
721
722	return (TRUE);
723}
724
725bool_t
726gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp)
727{
728
729	result->message_context = argp->message_context;
730	result->major_status = gss_display_status(&result->minor_status,
731	    argp->status_value, argp->status_type, argp->mech_type,
732	    &result->message_context, &result->status_string);
733
734	return (TRUE);
735}
736
737int
738gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
739{
740	/*
741	 * We don't use XDR to free the results - anything which was
742	 * allocated came from GSS-API. We use xdr_result to figure
743	 * out what to do.
744	 */
745	OM_uint32 junk;
746
747	if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) {
748		init_sec_context_res *p = (init_sec_context_res *) result;
749		gss_release_buffer(&junk, &p->output_token);
750	} else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) {
751		accept_sec_context_res *p = (accept_sec_context_res *) result;
752		gss_release_buffer(&junk, &p->output_token);
753	} else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) {
754		delete_sec_context_res *p = (delete_sec_context_res *) result;
755		gss_release_buffer(&junk, &p->output_token);
756	} else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) {
757		export_sec_context_res *p = (export_sec_context_res *) result;
758		if (p->interprocess_token.length)
759			memset(p->interprocess_token.value, 0,
760			    p->interprocess_token.length);
761		gss_release_buffer(&junk, &p->interprocess_token);
762	} else if (xdr_result == (xdrproc_t) xdr_export_name_res) {
763		export_name_res *p = (export_name_res *) result;
764		gss_release_buffer(&junk, &p->exported_name);
765	} else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) {
766		acquire_cred_res *p = (acquire_cred_res *) result;
767		gss_release_oid_set(&junk, &p->actual_mechs);
768	} else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) {
769		pname_to_uid_res *p = (pname_to_uid_res *) result;
770		if (p->gidlist.gidlist_val)
771			free(p->gidlist.gidlist_val);
772	} else if (xdr_result == (xdrproc_t) xdr_display_status_res) {
773		display_status_res *p = (display_status_res *) result;
774		gss_release_buffer(&junk, &p->status_string);
775	}
776
777	return (TRUE);
778}
779
780/*
781 * Search a directory for the most likely candidate to be used as the
782 * credential cache for a uid. If successful, return 1 and fill the
783 * file's path id into "rpath". Otherwise, return 0.
784 */
785static int
786find_ccache_file(const char *dirpath, uid_t uid, char *rpath)
787{
788	DIR *dirp;
789	struct dirent *dp;
790	struct stat sb;
791	time_t exptime, oexptime;
792	int gotone, len, rating, orating;
793	char namepath[PATH_MAX + 5 + 1];
794	char retpath[PATH_MAX + 5 + 1];
795
796	dirp = opendir(dirpath);
797	if (dirp == NULL)
798		return (0);
799	gotone = 0;
800	orating = 0;
801	oexptime = 0;
802	while ((dp = readdir(dirp)) != NULL) {
803		len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath,
804		    dp->d_name);
805		if (len < sizeof(namepath) &&
806		    strstr(dp->d_name, ccfile_substring) != NULL &&
807		    lstat(namepath, &sb) >= 0 &&
808		    sb.st_uid == uid &&
809		    S_ISREG(sb.st_mode)) {
810			len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s",
811			    dirpath, dp->d_name);
812			if (len < sizeof(namepath) &&
813			    is_a_valid_tgt_cache(namepath, uid, &rating,
814			    &exptime) != 0) {
815				if (gotone == 0 || rating > orating ||
816				    (rating == orating && exptime > oexptime)) {
817					orating = rating;
818					oexptime = exptime;
819					strcpy(retpath, namepath);
820					gotone = 1;
821				}
822			}
823		}
824	}
825	closedir(dirp);
826	if (gotone != 0) {
827		strcpy(rpath, retpath);
828		return (1);
829	}
830	return (0);
831}
832
833/*
834 * Try to determine if the file is a valid tgt cache file.
835 * Check that the file has a valid tgt for a principal.
836 * If it does, return 1, otherwise return 0.
837 * It also returns a "rating" and the expiry time for the TGT, when found.
838 * This "rating" is higher based on heuristics that make it more
839 * likely to be the correct credential cache file to use. It can
840 * be used by the caller, along with expiry time, to select from
841 * multiple credential cache files.
842 */
843static int
844is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating,
845    time_t *retexptime)
846{
847#ifndef WITHOUT_KERBEROS
848	krb5_context context;
849	krb5_principal princ;
850	krb5_ccache ccache;
851	krb5_error_code retval;
852	krb5_cc_cursor curse;
853	krb5_creds krbcred;
854	int gotone, orating, rating, ret;
855	struct passwd *pw;
856	char *cp, *cp2, *pname;
857	time_t exptime;
858
859	/* Find a likely name for the uid principal. */
860	pw = getpwuid(uid);
861
862	/*
863	 * Do a bunch of krb5 library stuff to try and determine if
864	 * this file is a credentials cache with an appropriate TGT
865	 * in it.
866	 */
867	retval = krb5_init_context(&context);
868	if (retval != 0)
869		return (0);
870	retval = krb5_cc_resolve(context, filepath, &ccache);
871	if (retval != 0) {
872		krb5_free_context(context);
873		return (0);
874	}
875	ret = 0;
876	orating = 0;
877	exptime = 0;
878	retval = krb5_cc_start_seq_get(context, ccache, &curse);
879	if (retval == 0) {
880		while ((retval = krb5_cc_next_cred(context, ccache, &curse,
881		    &krbcred)) == 0) {
882			gotone = 0;
883			rating = 0;
884			retval = krb5_unparse_name(context, krbcred.server,
885			    &pname);
886			if (retval == 0) {
887				cp = strchr(pname, '/');
888				if (cp != NULL) {
889					*cp++ = '\0';
890					if (strcmp(pname, "krbtgt") == 0 &&
891					    krbcred.times.endtime > time(NULL)
892					    ) {
893						gotone = 1;
894						/*
895						 * Test to see if this is a
896						 * tgt for cross-realm auth.
897						 * Rate it higher, if it is not.
898						 */
899						cp2 = strchr(cp, '@');
900						if (cp2 != NULL) {
901							*cp2++ = '\0';
902							if (strcmp(cp, cp2) ==
903							    0)
904								rating++;
905						}
906					}
907				}
908				free(pname);
909			}
910			if (gotone != 0) {
911				retval = krb5_unparse_name(context,
912				    krbcred.client, &pname);
913				if (retval == 0) {
914					cp = strchr(pname, '@');
915					if (cp != NULL) {
916						*cp++ = '\0';
917						if (pw != NULL && strcmp(pname,
918						    pw->pw_name) == 0)
919							rating++;
920						if (strchr(pname, '/') == NULL)
921							rating++;
922						if (pref_realm[0] != '\0' &&
923						    strcmp(cp, pref_realm) == 0)
924							rating++;
925					}
926				}
927				free(pname);
928				if (rating > orating) {
929					orating = rating;
930					exptime = krbcred.times.endtime;
931				} else if (rating == orating &&
932				    krbcred.times.endtime > exptime)
933					exptime = krbcred.times.endtime;
934				ret = 1;
935			}
936			krb5_free_cred_contents(context, &krbcred);
937		}
938		krb5_cc_end_seq_get(context, ccache, &curse);
939	}
940	krb5_cc_close(context, ccache);
941	krb5_free_context(context);
942	if (ret != 0) {
943		*retrating = orating;
944		*retexptime = exptime;
945	}
946	return (ret);
947#else /* WITHOUT_KERBEROS */
948	return (0);
949#endif /* !WITHOUT_KERBEROS */
950}
951
952