8b456b07c0f1cf51a816e41e3f45e4b2850ee412
[nfs-utils.git] / utils / gssd / gssd_proc.c
1 /*
2   gssd_proc.c
3
4   Copyright (c) 2000-2004 The Regents of the University of Michigan.
5   All rights reserved.
6
7   Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
8   Copyright (c) 2001 Andy Adamson <andros@UMICH.EDU>.
9   Copyright (c) 2002 Marius Aamodt Eriksen <marius@UMICH.EDU>.
10   Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU>
11   Copyright (c) 2004 Kevin Coffman <kwc@umich.edu>
12   All rights reserved, all wrongs reversed.
13
14   Redistribution and use in source and binary forms, with or without
15   modification, are permitted provided that the following conditions
16   are met:
17
18   1. Redistributions of source code must retain the above copyright
19      notice, this list of conditions and the following disclaimer.
20   2. Redistributions in binary form must reproduce the above copyright
21      notice, this list of conditions and the following disclaimer in the
22      documentation and/or other materials provided with the distribution.
23   3. Neither the name of the University nor the names of its
24      contributors may be used to endorse or promote products derived
25      from this software without specific prior written permission.
26
27   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
28   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
29   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39 */
40
41 #ifndef _GNU_SOURCE
42 #define _GNU_SOURCE
43 #endif
44 #include "config.h"
45 #include <sys/param.h>
46 #include <rpc/rpc.h>
47 #include <sys/stat.h>
48 #include <sys/socket.h>
49 #include <arpa/inet.h>
50
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <pwd.h>
54 #include <grp.h>
55 #include <string.h>
56 #include <dirent.h>
57 #include <poll.h>
58 #include <fcntl.h>
59 #include <signal.h>
60 #include <unistd.h>
61 #include <errno.h>
62 #include <gssapi/gssapi.h>
63 #include <netdb.h>
64
65 #include "gssd.h"
66 #include "err_util.h"
67 #include "gss_util.h"
68 #include "gss_oids.h"
69 #include "krb5_util.h"
70 #include "context.h"
71
72 /*
73  * pollarray:
74  *      array of struct pollfd suitable to pass to poll. initialized to
75  *      zero - a zero struct is ignored by poll() because the events mask is 0.
76  *
77  * clnt_list:
78  *      linked list of struct clnt_info which associates a clntXXX directory
79  *      with an index into pollarray[], and other basic data about that client.
80  *
81  * Directory structure: created by the kernel nfs client
82  *      /pipefsdir/clntXX             : one per rpc_clnt struct in the kernel
83  *      /pipefsdir/clntXX/krb5        : read uid for which kernel wants
84  *                                       a context, write the resulting context
85  *      /pipefsdir/clntXX/info        : stores info such as server name
86  *
87  * Algorithm:
88  *      Poll all /pipefsdir/clntXX/krb5 files.  When ready, data read
89  *      is a uid; performs rpcsec_gss context initialization protocol to
90  *      get a cred for that user.  Writes result to corresponding krb5 file
91  *      in a form the kernel code will understand.
92  *      In addition, we make sure we are notified whenever anything is
93  *      created or destroyed in pipefsdir/ or in an of the clntXX directories,
94  *      and rescan the whole pipefsdir when this happens.
95  */
96
97 struct pollfd * pollarray;
98
99 int pollsize;  /* the size of pollaray (in pollfd's) */
100
101 /* XXX buffer problems: */
102 static int
103 read_service_info(char *info_file_name, char **servicename, char **servername,
104                   int *prog, int *vers, char **protocol) {
105 #define INFOBUFLEN 256
106         char            buf[INFOBUFLEN];
107         static char     dummy[128];
108         int             nbytes;
109         static char     service[128];
110         static char     address[128];
111         char            program[16];
112         char            version[16];
113         char            protoname[16];
114         in_addr_t       inaddr;
115         int             fd = -1;
116         struct hostent  *ent = NULL;
117         int             numfields;
118
119         *servicename = *servername = *protocol = NULL;
120
121         if ((fd = open(info_file_name, O_RDONLY)) == -1) {
122                 printerr(0, "ERROR: can't open %s: %s\n", info_file_name,
123                          strerror(errno));
124                 goto fail;
125         }
126         if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1)
127                 goto fail;
128         close(fd);
129
130         numfields = sscanf(buf,"RPC server: %s\n"
131                    "service: %s %s version %s\n"
132                    "address: %s\n"
133                    "protocol: %s\n",
134                    dummy,
135                    service, program, version,
136                    address,
137                    protoname);
138
139         if (numfields == 5) {
140                 strcpy(protoname, "tcp");
141         } else if (numfields != 6) {
142                 goto fail;
143         }
144
145         /* check service, program, and version */
146         if(memcmp(service, "nfs", 3)) return -1;
147         *prog = atoi(program + 1); /* skip open paren */
148         *vers = atoi(version);
149         if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
150                 goto fail;
151
152         /* create service name */
153         inaddr = inet_addr(address);
154         if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) {
155                 printerr(0, "ERROR: can't resolve server %s name\n", address);
156                 goto fail;
157         }
158         if (!(*servername = calloc(strlen(ent->h_name) + 1, 1)))
159                 goto fail;
160         memcpy(*servername, ent->h_name, strlen(ent->h_name));
161         snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name);
162         if (!(*servicename = calloc(strlen(buf) + 1, 1)))
163                 goto fail;
164         memcpy(*servicename, buf, strlen(buf));
165
166         if (!(*protocol = strdup(protoname)))
167                 goto fail;
168         return 0;
169 fail:
170         printerr(0, "ERROR: failed to read service info\n");
171         if (fd != -1) close(fd);
172         if (*servername) free(*servername);
173         if (*servicename) free(*servicename);
174         if (*protocol) free(*protocol);
175         return -1;
176 }
177
178 static void
179 destroy_client(struct clnt_info *clp)
180 {
181         if (clp->krb5_poll_index != -1)
182                 memset(&pollarray[clp->krb5_poll_index], 0,
183                                         sizeof(struct pollfd));
184         if (clp->spkm3_poll_index != -1)
185                 memset(&pollarray[clp->spkm3_poll_index], 0,
186                                         sizeof(struct pollfd));
187         if (clp->dir_fd != -1) close(clp->dir_fd);
188         if (clp->krb5_fd != -1) close(clp->krb5_fd);
189         if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
190         if (clp->dirname) free(clp->dirname);
191         if (clp->servicename) free(clp->servicename);
192         if (clp->servername) free(clp->servername);
193         if (clp->protocol) free(clp->protocol);
194         free(clp);
195 }
196
197 static struct clnt_info *
198 insert_new_clnt(void)
199 {
200         struct clnt_info        *clp = NULL;
201
202         if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) {
203                 printerr(0, "ERROR: can't malloc clnt_info: %s\n",
204                          strerror(errno));
205                 goto out;
206         }
207         clp->krb5_poll_index = -1;
208         clp->spkm3_poll_index = -1;
209         clp->krb5_fd = -1;
210         clp->spkm3_fd = -1;
211         clp->dir_fd = -1;
212
213         TAILQ_INSERT_HEAD(&clnt_list, clp, list);
214 out:
215         return clp;
216 }
217
218 static int
219 process_clnt_dir_files(struct clnt_info * clp)
220 {
221         char    kname[32];
222         char    sname[32];
223         char    info_file_name[32];
224
225         if (clp->krb5_fd == -1) {
226                 snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
227                 clp->krb5_fd = open(kname, O_RDWR);
228         }
229         if (clp->spkm3_fd == -1) {
230                 snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
231                 clp->spkm3_fd = open(sname, O_RDWR);
232         }
233         if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
234                 return -1;
235         snprintf(info_file_name, sizeof(info_file_name), "%s/info",
236                         clp->dirname);
237         if ((clp->servicename == NULL) &&
238              read_service_info(info_file_name, &clp->servicename,
239                                 &clp->servername, &clp->prog, &clp->vers,
240                                 &clp->protocol))
241                 return -1;
242         return 0;
243 }
244
245 static int
246 get_poll_index(int *ind)
247 {
248         int i;
249
250         *ind = -1;
251         for (i=0; i<FD_ALLOC_BLOCK; i++) {
252                 if (pollarray[i].events == 0) {
253                         *ind = i;
254                         break;
255                 }
256         }
257         if (*ind == -1) {
258                 printerr(0, "ERROR: No pollarray slots open\n");
259                 return -1;
260         }
261         return 0;
262 }
263
264
265 static int
266 insert_clnt_poll(struct clnt_info *clp)
267 {
268         if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
269                 if (get_poll_index(&clp->krb5_poll_index)) {
270                         printerr(0, "ERROR: Too many krb5 clients\n");
271                         return -1;
272                 }
273                 pollarray[clp->krb5_poll_index].fd = clp->krb5_fd;
274                 pollarray[clp->krb5_poll_index].events |= POLLIN;
275         }
276
277         if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) {
278                 if (get_poll_index(&clp->spkm3_poll_index)) {
279                         printerr(0, "ERROR: Too many spkm3 clients\n");
280                         return -1;
281                 }
282                 pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd;
283                 pollarray[clp->spkm3_poll_index].events |= POLLIN;
284         }
285
286         return 0;
287 }
288
289 static void
290 process_clnt_dir(char *dir)
291 {
292         struct clnt_info *      clp;
293
294         if (!(clp = insert_new_clnt()))
295                 goto fail_destroy_client;
296
297         if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
298                 goto fail_destroy_client;
299         }
300         memcpy(clp->dirname, dir, strlen(dir));
301         if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
302                 printerr(0, "ERROR: can't open %s: %s\n",
303                          clp->dirname, strerror(errno));
304                 goto fail_destroy_client;
305         }
306         fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL);
307         fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT);
308
309         if (process_clnt_dir_files(clp))
310                 goto fail_keep_client;
311
312         if (insert_clnt_poll(clp))
313                 goto fail_destroy_client;
314
315         return;
316
317 fail_destroy_client:
318         if (clp) {
319                 TAILQ_REMOVE(&clnt_list, clp, list);
320                 destroy_client(clp);
321         }
322 fail_keep_client:
323         /* We couldn't find some subdirectories, but we keep the client
324          * around in case we get a notification on the directory when the
325          * subdirectories are created. */
326         return;
327 }
328
329 void
330 init_client_list(void)
331 {
332         TAILQ_INIT(&clnt_list);
333         /* Eventually plan to grow/shrink poll array: */
334         pollsize = FD_ALLOC_BLOCK;
335         pollarray = calloc(pollsize, sizeof(struct pollfd));
336 }
337
338 /*
339  * This is run after a DNOTIFY signal, and should clear up any
340  * directories that are no longer around, and re-scan any existing
341  * directories, since the DNOTIFY could have been in there.
342  */
343 static void
344 update_old_clients(struct dirent **namelist, int size)
345 {
346         struct clnt_info *clp;
347         void *saveprev;
348         int i, stillhere;
349
350         for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
351                 stillhere = 0;
352                 for (i=0; i < size; i++) {
353                         if (!strcmp(clp->dirname, namelist[i]->d_name)) {
354                                 stillhere = 1;
355                                 break;
356                         }
357                 }
358                 if (!stillhere) {
359                         printerr(2, "destroying client %s\n", clp->dirname);
360                         saveprev = clp->list.tqe_prev;
361                         TAILQ_REMOVE(&clnt_list, clp, list);
362                         destroy_client(clp);
363                         clp = saveprev;
364                 }
365         }
366         for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
367                 if (!process_clnt_dir_files(clp))
368                         insert_clnt_poll(clp);
369         }
370 }
371
372 /* Search for a client by directory name, return 1 if found, 0 otherwise */
373 static int
374 find_client(char *dirname)
375 {
376         struct clnt_info        *clp;
377
378         for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
379                 if (!strcmp(clp->dirname, dirname))
380                         return 1;
381         return 0;
382 }
383
384 /* Used to read (and re-read) list of clients, set up poll array. */
385 int
386 update_client_list(void)
387 {
388         struct dirent **namelist;
389         int i, j;
390
391         if (chdir(pipefsdir) < 0) {
392                 printerr(0, "ERROR: can't chdir to %s: %s\n",
393                          pipefsdir, strerror(errno));
394                 return -1;
395         }
396
397         j = scandir(pipefsdir, &namelist, NULL, alphasort);
398         if (j < 0) {
399                 printerr(0, "ERROR: can't scandir %s: %s\n",
400                          pipefsdir, strerror(errno));
401                 return -1;
402         }
403         update_old_clients(namelist, j);
404         for (i=0; i < j; i++) {
405                 if (i < FD_ALLOC_BLOCK
406                                 && !strncmp(namelist[i]->d_name, "clnt", 4)
407                                 && !find_client(namelist[i]->d_name))
408                         process_clnt_dir(namelist[i]->d_name);
409                 free(namelist[i]);
410         }
411
412         free(namelist);
413         return 0;
414 }
415
416 static int
417 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
418             gss_buffer_desc *context_token)
419 {
420         char    *buf = NULL, *p = NULL, *end = NULL;
421         unsigned int timeout = 0; /* XXX decide on a reasonable value */
422         unsigned int buf_size = 0;
423
424         printerr(1, "doing downcall\n");
425         buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) +
426                 sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length +
427                 sizeof(context_token->length) + context_token->length;
428         p = buf = malloc(buf_size);
429         end = buf + buf_size;
430
431         if (WRITE_BYTES(&p, end, uid)) goto out_err;
432         /* Not setting any timeout for now: */
433         if (WRITE_BYTES(&p, end, timeout)) goto out_err;
434         if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err;
435         if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err;
436         if (write_buffer(&p, end, context_token)) goto out_err;
437
438         if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
439         if (buf) free(buf);
440         return 0;
441 out_err:
442         if (buf) free(buf);
443         printerr(0, "Failed to write downcall!\n");
444         return -1;
445 }
446
447 static int
448 do_error_downcall(int k5_fd, uid_t uid, int err)
449 {
450         char    buf[1024];
451         char    *p = buf, *end = buf + 1024;
452         unsigned int timeout = 0;
453         int     zero = 0;
454
455         printerr(1, "doing error downcall\n");
456
457         if (WRITE_BYTES(&p, end, uid)) goto out_err;
458         if (WRITE_BYTES(&p, end, timeout)) goto out_err;
459         /* use seq_win = 0 to indicate an error: */
460         if (WRITE_BYTES(&p, end, zero)) goto out_err;
461         if (WRITE_BYTES(&p, end, err)) goto out_err;
462
463         if (write(k5_fd, buf, p - buf) < p - buf) goto out_err;
464         return 0;
465 out_err:
466         printerr(0, "Failed to write error downcall!\n");
467         return -1;
468 }
469
470 /*
471  * Create an RPC connection and establish an authenticated
472  * gss context with a server.
473  */
474 int create_auth_rpc_client(struct clnt_info *clp,
475                            AUTH **auth_return,
476                            uid_t uid,
477                            int authtype)
478 {
479         CLIENT                  *rpc_clnt = NULL;
480         struct rpc_gss_sec      sec;
481         AUTH                    *auth = NULL;
482         uid_t                   save_uid = -1;
483         int                     retval = -1;
484         int                     errcode;
485         OM_uint32               min_stat;
486         char                    rpc_errmsg[1024];
487         int                     sockp = RPC_ANYSOCK;
488         int                     sendsz = 32768, recvsz = 32768;
489         struct addrinfo         ai_hints, *a = NULL;
490         char                    service[64];
491         char                    *at_sign;
492
493         sec.qop = GSS_C_QOP_DEFAULT;
494         sec.svc = RPCSEC_GSS_SVC_NONE;
495         sec.cred = GSS_C_NO_CREDENTIAL;
496         sec.req_flags = 0;
497         if (authtype == AUTHTYPE_KRB5) {
498                 sec.mech = (gss_OID)&krb5oid;
499                 sec.req_flags = GSS_C_MUTUAL_FLAG;
500         }
501         else if (authtype == AUTHTYPE_SPKM3) {
502                 sec.mech = (gss_OID)&spkm3oid;
503                 /* XXX sec.req_flags = GSS_C_ANON_FLAG;
504                  * Need a way to switch....
505                  */
506                 sec.req_flags = GSS_C_MUTUAL_FLAG;
507         }
508         else {
509                 printerr(0, "ERROR: Invalid authentication type (%d) "
510                         "in create_auth_rpc_client\n", authtype);
511                 goto out_fail;
512         }
513
514
515         if (authtype == AUTHTYPE_KRB5) {
516 #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
517                 /*
518                  * Do this before creating rpc connection since we won't need
519                  * rpc connection if it fails!
520                  */
521                 if (limit_krb5_enctypes(&sec, uid)) {
522                         printerr(1, "WARNING: Failed while limiting krb5 "
523                                     "encryption types for user with uid %d\n",
524                                  uid);
525                         goto out_fail;
526                 }
527 #endif
528         }
529
530         /* Create the context as the user (not as root) */
531         save_uid = geteuid();
532         if (seteuid(uid) != 0) {
533                 printerr(0, "WARNING: Failed to seteuid for "
534                             "user with uid %d\n", uid);
535                 goto out_fail;
536         }
537         printerr(2, "creating context using euid %d (save_uid %d)\n",
538                         geteuid(), save_uid);
539
540         /* create an rpc connection to the nfs server */
541
542         printerr(2, "creating %s client for server %s\n", clp->protocol,
543                         clp->servername);
544
545         memset(&ai_hints, '\0', sizeof(ai_hints));
546         ai_hints.ai_family = PF_INET;
547         ai_hints.ai_flags |= AI_CANONNAME;
548         if ((strcmp(clp->protocol, "tcp")) == 0) {
549                 ai_hints.ai_socktype = SOCK_STREAM;
550                 ai_hints.ai_protocol = IPPROTO_TCP;
551         } else if ((strcmp(clp->protocol, "udp")) == 0) {
552                 ai_hints.ai_socktype = SOCK_DGRAM;
553                 ai_hints.ai_protocol = IPPROTO_UDP;
554         } else {
555                 printerr(0, "WARNING: unrecognized protocol, '%s', requested "
556                          "for connection to server %s for user with uid %d",
557                          clp->protocol, clp->servername, uid);
558                 goto out_fail;
559         }
560
561         /* extract the service name from clp->servicename */
562         if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
563                 printerr(0, "WARNING: servicename (%s) not formatted as "
564                         "expected with service@host", clp->servicename);
565                 goto out_fail;
566         }
567         if ((at_sign - clp->servicename) >= sizeof(service)) {
568                 printerr(0, "WARNING: service portion of servicename (%s) "
569                         "is too long!", clp->servicename);
570                 goto out_fail;
571         }
572         strncpy(service, clp->servicename, at_sign - clp->servicename);
573         service[at_sign - clp->servicename] = '\0';
574
575         errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
576         if (errcode) {
577                 printerr(0, "WARNING: Error from getaddrinfo for server "
578                          "'%s': %s", clp->servername, gai_strerror(errcode));
579                 goto out_fail;
580         }
581
582         if (a == NULL) {
583                 printerr(0, "WARNING: No address information found for "
584                          "connection to server %s for user with uid %d",
585                          clp->servername, uid);
586                 goto out_fail;
587         }
588         if (a->ai_protocol == IPPROTO_TCP) {
589                 if ((rpc_clnt = clnttcp_create(
590                                         (struct sockaddr_in *) a->ai_addr,
591                                         clp->prog, clp->vers, &sockp,
592                                         sendsz, recvsz)) == NULL) {
593                         snprintf(rpc_errmsg, sizeof(rpc_errmsg),
594                                  "WARNING: can't create tcp rpc_clnt "
595                                  "for server %s for user with uid %d",
596                                  clp->servername, uid);
597                         printerr(0, "%s\n",
598                                  clnt_spcreateerror(rpc_errmsg));
599                         goto out_fail;
600                 }
601         } else if (a->ai_protocol == IPPROTO_UDP) {
602                 const struct timeval timeout = {5, 0};
603                 if ((rpc_clnt = clntudp_bufcreate(
604                                         (struct sockaddr_in *) a->ai_addr,
605                                         clp->prog, clp->vers, timeout,
606                                         &sockp, sendsz, recvsz)) == NULL) {
607                         snprintf(rpc_errmsg, sizeof(rpc_errmsg),
608                                  "WARNING: can't create udp rpc_clnt "
609                                  "for server %s for user with uid %d",
610                                  clp->servername, uid);
611                         printerr(0, "%s\n",
612                                  clnt_spcreateerror(rpc_errmsg));
613                         goto out_fail;
614                 }
615         } else {
616                 /* Shouldn't happen! */
617                 printerr(0, "ERROR: requested protocol '%s', but "
618                          "got addrinfo with protocol %d",
619                          clp->protocol, a->ai_protocol);
620                 goto out_fail;
621         }
622         /* We're done with this */
623         freeaddrinfo(a);
624         a = NULL;
625
626         printerr(2, "creating context with server %s\n", clp->servicename);
627         auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
628         if (!auth) {
629                 /* Our caller should print appropriate message */
630                 printerr(2, "WARNING: Failed to create %s context for "
631                             "user with uid %d for server %s\n",
632                         (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"),
633                          uid, clp->servername);
634                 goto out_fail;
635         }
636
637         /* Restore euid to original value */
638         if (seteuid(save_uid) != 0) {
639                 printerr(0, "WARNING: Failed to restore euid"
640                             " to uid %d\n", save_uid);
641                 goto out_fail;
642         }
643         save_uid = -1;
644
645         /* Success !!! */
646         *auth_return = auth;
647         retval = 0;
648
649   out_fail:
650         if ((save_uid != -1) && (seteuid(save_uid) != 0)) {
651                 printerr(0, "WARNING: Failed to restore euid"
652                             " to uid %d (in error path)\n", save_uid);
653         }
654         if (sec.cred != GSS_C_NO_CREDENTIAL)
655                 gss_release_cred(&min_stat, &sec.cred);
656         if (rpc_clnt) clnt_destroy(rpc_clnt);
657         if (a != NULL) freeaddrinfo(a);
658
659         return retval;
660 }
661
662
663 /*
664  * this code uses the userland rpcsec gss library to create a krb5
665  * context on behalf of the kernel
666  */
667 void
668 handle_krb5_upcall(struct clnt_info *clp)
669 {
670         uid_t                   uid;
671         AUTH                    *auth;
672         struct authgss_private_data pd;
673         gss_buffer_desc         token;
674         char                    **credlist = NULL;
675         char                    **ccname;
676
677         printerr(1, "handling krb5 upcall\n");
678
679         token.length = 0;
680         token.value = NULL;
681
682         if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
683                 printerr(0, "WARNING: failed reading uid from krb5 "
684                             "upcall pipe: %s\n", strerror(errno));
685                 goto out;
686         }
687
688         if (uid == 0) {
689                 int success = 0;
690
691                 /*
692                  * Get a list of credential cache names and try each
693                  * of them until one works or we've tried them all
694                  */
695                 if (gssd_get_krb5_machine_cred_list(&credlist)) {
696                         printerr(0, "WARNING: Failed to obtain machine "
697                                     "credentials for connection to "
698                                     "server %s\n", clp->servername);
699                                 goto out_return_error;
700                 }
701                 for (ccname = credlist; ccname && *ccname; ccname++) {
702                         gssd_setup_krb5_machine_gss_ccache(*ccname);
703                         if ((create_auth_rpc_client(clp, &auth, uid,
704                                                     AUTHTYPE_KRB5)) == 0) {
705                                 /* Success! */
706                                 success++;
707                                 break;
708                         }
709                         printerr(2, "WARNING: Failed to create krb5 context "
710                                     "for user with uid %d with credentials "
711                                     "cache %s for server %s\n",
712                                  uid, *ccname, clp->servername);
713                 }
714                 gssd_free_krb5_machine_cred_list(credlist);
715                 if (!success) {
716                         printerr(0, "WARNING: Failed to create krb5 context "
717                                     "for user with uid %d with any "
718                                     "credentials cache for server %s\n",
719                                  uid, clp->servername);
720                         goto out_return_error;
721                 }
722         }
723         else {
724                 /* Tell krb5 gss which credentials cache to use */
725                 gssd_setup_krb5_user_gss_ccache(uid, clp->servername);
726
727                 if (create_auth_rpc_client(clp, &auth, uid, AUTHTYPE_KRB5)) {
728                         printerr(0, "WARNING: Failed to create krb5 context "
729                                     "for user with uid %d for server %s\n",
730                                  uid, clp->servername);
731                         goto out_return_error;
732                 }
733         }
734
735         if (!authgss_get_private_data(auth, &pd)) {
736                 printerr(0, "WARNING: Failed to obtain authentication "
737                             "data for user with uid %d for server %s\n",
738                          uid, clp->servername);
739                 goto out_return_error;
740         }
741
742         if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid)) {
743                 printerr(0, "WARNING: Failed to serialize krb5 context for "
744                             "user with uid %d for server %s\n",
745                          uid, clp->servername);
746                 goto out_return_error;
747         }
748
749         do_downcall(clp->krb5_fd, uid, &pd, &token);
750
751         if (token.value)
752                 free(token.value);
753 out:
754         return;
755
756 out_return_error:
757         do_error_downcall(clp->krb5_fd, uid, -1);
758         return;
759 }
760
761 /*
762  * this code uses the userland rpcsec gss library to create an spkm3
763  * context on behalf of the kernel
764  */
765 void
766 handle_spkm3_upcall(struct clnt_info *clp)
767 {
768         uid_t                   uid;
769         AUTH                    *auth;
770         struct authgss_private_data pd;
771         gss_buffer_desc         token;
772
773         printerr(2, "handling spkm3 upcall\n");
774
775         token.length = 0;
776         token.value = NULL;
777
778         if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
779                 printerr(0, "WARNING: failed reading uid from spkm3 "
780                          "upcall pipe: %s\n", strerror(errno));
781                 goto out;
782         }
783
784         if (create_auth_rpc_client(clp, &auth, uid, AUTHTYPE_SPKM3)) {
785                 printerr(0, "WARNING: Failed to create spkm3 context for "
786                             "user with uid %d\n", uid);
787                 goto out_return_error;
788         }
789
790         if (!authgss_get_private_data(auth, &pd)) {
791                 printerr(0, "WARNING: Failed to obtain authentication "
792                             "data for user with uid %d for server %s\n",
793                          uid, clp->servername);
794                 goto out_return_error;
795         }
796
797         if (serialize_context_for_kernel(pd.pd_ctx, &token, &spkm3oid)) {
798                 printerr(0, "WARNING: Failed to serialize spkm3 context for "
799                             "user with uid %d for server\n",
800                          uid, clp->servername);
801                 goto out_return_error;
802         }
803
804         do_downcall(clp->spkm3_fd, uid, &pd, &token);
805
806         if (token.value)
807                 free(token.value);
808 out:
809         return;
810
811 out_return_error:
812         do_error_downcall(clp->spkm3_fd, uid, -1);
813         return;
814 }