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