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