]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/gssd/gssd_proc.c
gssd: on krb5 upcall, have gssd send a more granular error code
[nfs-utils.git] / utils / gssd / gssd_proc.c
index 509946e62bcf922552d8408b594494e59abefe6f..be4fb1150e1582990240a9213bb8fe5b9291e481 100644 (file)
@@ -72,6 +72,8 @@
 #include "gss_util.h"
 #include "krb5_util.h"
 #include "context.h"
+#include "nfsrpc.h"
+#include "nfslib.h"
 
 /*
  * pollarray:
  *      linked list of struct clnt_info which associates a clntXXX directory
  *     with an index into pollarray[], and other basic data about that client.
  *
- * Directory structure: created by the kernel nfs client
- *      {pipefs_nfsdir}/clntXX             : one per rpc_clnt struct in the kernel
- *      {pipefs_nfsdir}/clntXX/krb5        : read uid for which kernel wants
+ * Directory structure: created by the kernel
+ *      {rpc_pipefs}/{dir}/clntXX         : one per rpc_clnt struct in the kernel
+ *      {rpc_pipefs}/{dir}/clntXX/krb5    : read uid for which kernel wants
  *                                         a context, write the resulting context
- *      {pipefs_nfsdir}/clntXX/info        : stores info such as server name
+ *      {rpc_pipefs}/{dir}/clntXX/info    : stores info such as server name
+ *      {rpc_pipefs}/{dir}/clntXX/gssd    : pipe for all gss mechanisms using
+ *                                         a text-based string of parameters
  *
  * Algorithm:
- *      Poll all {pipefs_nfsdir}/clntXX/krb5 files.  When ready, data read
- *      is a uid; performs rpcsec_gss context initialization protocol to
+ *      Poll all {rpc_pipefs}/{dir}/clntXX/YYYY files.  When data is ready,
+ *      read and process; performs rpcsec_gss context initialization protocol to
  *      get a cred for that user.  Writes result to corresponding krb5 file
  *      in a form the kernel code will understand.
  *      In addition, we make sure we are notified whenever anything is
- *      created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories,
- *      and rescan the whole {pipefs_nfsdir} when this happens.
+ *      created or destroyed in {rpc_pipefs} or in any of the clntXX directories,
+ *      and rescan the whole {rpc_pipefs} when this happens.
  */
 
 struct pollfd * pollarray;
 
 int pollsize;  /* the size of pollaray (in pollfd's) */
 
+/*
+ * convert a presentation address string to a sockaddr_storage struct. Returns
+ * true on success or false on failure.
+ *
+ * Note that we do not populate the sin6_scope_id field here for IPv6 addrs.
+ * gssd nececessarily relies on hostname resolution and DNS AAAA records
+ * do not generally contain scope-id's. This means that GSSAPI auth really
+ * can't work with IPv6 link-local addresses.
+ *
+ * We *could* consider changing this if we did something like adopt the
+ * Microsoft "standard" of using the ipv6-literal.net domainname, but it's
+ * not really feasible at present.
+ */
+static int
+addrstr_to_sockaddr(struct sockaddr *sa, const char *node, const char *port)
+{
+       int rc;
+       struct addrinfo *res;
+       struct addrinfo hints = { .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV };
+
+#ifndef IPV6_SUPPORTED
+       hints.ai_family = AF_INET;
+#endif /* IPV6_SUPPORTED */
+
+       rc = getaddrinfo(node, port, &hints, &res);
+       if (rc) {
+               printerr(0, "ERROR: unable to convert %s|%s to sockaddr: %s\n",
+                        node, port, rc == EAI_SYSTEM ? strerror(errno) :
+                                               gai_strerror(rc));
+               return 0;
+       }
+
+#ifdef IPV6_SUPPORTED
+       /*
+        * getnameinfo ignores the scopeid. If the address turns out to have
+        * a non-zero scopeid, we can't use it -- the resolved host might be
+        * completely different from the one intended.
+        */
+       if (res->ai_addr->sa_family == AF_INET6) {
+               struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
+               if (sin6->sin6_scope_id) {
+                       printerr(0, "ERROR: address %s has non-zero "
+                                   "sin6_scope_id!\n", node);
+                       freeaddrinfo(res);
+                       return 0;
+               }
+       }
+#endif /* IPV6_SUPPORTED */
+
+       memcpy(sa, res->ai_addr, res->ai_addrlen);
+       freeaddrinfo(res);
+       return 1;
+}
+
+/*
+ * convert a sockaddr to a hostname
+ */
+static char *
+sockaddr_to_hostname(const struct sockaddr *sa, const char *addr)
+{
+       socklen_t               addrlen;
+       int                     err;
+       char                    *hostname;
+       char                    hbuf[NI_MAXHOST];
+
+       switch (sa->sa_family) {
+       case AF_INET:
+               addrlen = sizeof(struct sockaddr_in);
+               break;
+#ifdef IPV6_SUPPORTED
+       case AF_INET6:
+               addrlen = sizeof(struct sockaddr_in6);
+               break;
+#endif /* IPV6_SUPPORTED */
+       default:
+               printerr(0, "ERROR: unrecognized addr family %d\n",
+                        sa->sa_family);
+               return NULL;
+       }
+
+       err = getnameinfo(sa, addrlen, hbuf, sizeof(hbuf), NULL, 0,
+                         NI_NAMEREQD);
+       if (err) {
+               printerr(0, "ERROR: unable to resolve %s to hostname: %s\n",
+                        addr, err == EAI_SYSTEM ? strerror(err) :
+                                                  gai_strerror(err));
+               return NULL;
+       }
+
+       hostname = strdup(hbuf);
+
+       return hostname;
+}
+
 /* XXX buffer problems: */
 static int
 read_service_info(char *info_file_name, char **servicename, char **servername,
-                 int *prog, int *vers, char **protocol, int *port) {
+                 int *prog, int *vers, char **protocol,
+                 struct sockaddr *addr) {
 #define INFOBUFLEN 256
        char            buf[INFOBUFLEN + 1];
        static char     dummy[128];
@@ -115,11 +214,9 @@ read_service_info(char *info_file_name, char **servicename, char **servername,
        char            program[16];
        char            version[16];
        char            protoname[16];
-       char            cb_port[128];
+       char            port[128];
        char            *p;
-       in_addr_t       inaddr;
        int             fd = -1;
-       struct hostent  *ent = NULL;
        int             numfields;
 
        *servicename = *servername = *protocol = NULL;
@@ -149,32 +246,39 @@ read_service_info(char *info_file_name, char **servicename, char **servername,
                goto fail;
        }
 
-       cb_port[0] = '\0';
+       port[0] = '\0';
        if ((p = strstr(buf, "port")) != NULL)
-               sscanf(p, "port: %127s\n", cb_port);
+               sscanf(p, "port: %127s\n", port);
 
        /* check service, program, and version */
-       if(memcmp(service, "nfs", 3)) return -1;
+       if (memcmp(service, "nfs", 3) != 0)
+               return -1;
        *prog = atoi(program + 1); /* skip open paren */
        *vers = atoi(version);
-       if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4)))
+
+       if (strlen(service) == 3 ) {
+               if ((*prog != 100003) || ((*vers != 2) && (*vers != 3) &&
+                   (*vers != 4)))
+                       goto fail;
+       } else if (memcmp(service, "nfs4_cb", 7) == 0) {
+               if (*vers != 1)
+                       goto fail;
+       }
+
+       if (!addrstr_to_sockaddr(addr, address, port))
                goto fail;
 
-       /* create service name */
-       inaddr = inet_addr(address);
-       if (!(ent = gethostbyaddr(&inaddr, sizeof(inaddr), AF_INET))) {
-               printerr(0, "ERROR: can't resolve server %s name\n", address);
+       *servername = sockaddr_to_hostname(addr, address);
+       if (*servername == NULL)
                goto fail;
-       }
-       if (!(*servername = calloc(strlen(ent->h_name) + 1, 1)))
+
+       nbytes = snprintf(buf, INFOBUFLEN, "%s@%s", service, *servername);
+       if (nbytes > INFOBUFLEN)
                goto fail;
-       memcpy(*servername, ent->h_name, strlen(ent->h_name));
-       snprintf(buf, INFOBUFLEN, "%s@%s", service, ent->h_name);
+
        if (!(*servicename = calloc(strlen(buf) + 1, 1)))
                goto fail;
        memcpy(*servicename, buf, strlen(buf));
-       if (cb_port[0] != '\0')
-               *port = atoi(cb_port);
 
        if (!(*protocol = strdup(protoname)))
                goto fail;
@@ -198,9 +302,13 @@ destroy_client(struct clnt_info *clp)
        if (clp->spkm3_poll_index != -1)
                memset(&pollarray[clp->spkm3_poll_index], 0,
                                        sizeof(struct pollfd));
+       if (clp->gssd_poll_index != -1)
+               memset(&pollarray[clp->gssd_poll_index], 0,
+                                       sizeof(struct pollfd));
        if (clp->dir_fd != -1) close(clp->dir_fd);
        if (clp->krb5_fd != -1) close(clp->krb5_fd);
        if (clp->spkm3_fd != -1) close(clp->spkm3_fd);
+       if (clp->gssd_fd != -1) close(clp->gssd_fd);
        free(clp->dirname);
        free(clp->servicename);
        free(clp->servername);
@@ -220,8 +328,10 @@ insert_new_clnt(void)
        }
        clp->krb5_poll_index = -1;
        clp->spkm3_poll_index = -1;
+       clp->gssd_poll_index = -1;
        clp->krb5_fd = -1;
        clp->spkm3_fd = -1;
+       clp->gssd_fd = -1;
        clp->dir_fd = -1;
 
        TAILQ_INSERT_HEAD(&clnt_list, clp, list);
@@ -232,26 +342,50 @@ out:
 static int
 process_clnt_dir_files(struct clnt_info * clp)
 {
-       char    kname[32];
-       char    sname[32];
-       char    info_file_name[32];
+       char    name[PATH_MAX];
+       char    gname[PATH_MAX];
+       char    info_file_name[PATH_MAX];
 
-       if (clp->krb5_fd == -1) {
-               snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname);
-               clp->krb5_fd = open(kname, O_RDWR);
+       if (clp->gssd_fd == -1) {
+               snprintf(gname, sizeof(gname), "%s/gssd", clp->dirname);
+               clp->gssd_fd = open(gname, O_RDWR);
        }
-       if (clp->spkm3_fd == -1) {
-               snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname);
-               clp->spkm3_fd = open(sname, O_RDWR);
+       if (clp->gssd_fd == -1) {
+               if (clp->krb5_fd == -1) {
+                       snprintf(name, sizeof(name), "%s/krb5", clp->dirname);
+                       clp->krb5_fd = open(name, O_RDWR);
+               }
+               if (clp->spkm3_fd == -1) {
+                       snprintf(name, sizeof(name), "%s/spkm3", clp->dirname);
+                       clp->spkm3_fd = open(name, O_RDWR);
+               }
+
+               /* If we opened a gss-specific pipe, let's try opening
+                * the new upcall pipe again. If we succeed, close
+                * gss-specific pipe(s).
+                */
+               if (clp->krb5_fd != -1 || clp->spkm3_fd != -1) {
+                       clp->gssd_fd = open(gname, O_RDWR);
+                       if (clp->gssd_fd != -1) {
+                               if (clp->krb5_fd != -1)
+                                       close(clp->krb5_fd);
+                               clp->krb5_fd = -1;
+                               if (clp->spkm3_fd != -1)
+                                       close(clp->spkm3_fd);
+                               clp->spkm3_fd = -1;
+                       }
+               }
        }
-       if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1))
+
+       if ((clp->krb5_fd == -1) && (clp->spkm3_fd == -1) &&
+                       (clp->gssd_fd == -1))
                return -1;
        snprintf(info_file_name, sizeof(info_file_name), "%s/info",
                        clp->dirname);
        if ((clp->servicename == NULL) &&
             read_service_info(info_file_name, &clp->servicename,
                                &clp->servername, &clp->prog, &clp->vers,
-                               &clp->protocol, &clp->port))
+                               &clp->protocol, (struct sockaddr *) &clp->addr))
                return -1;
        return 0;
 }
@@ -279,6 +413,15 @@ get_poll_index(int *ind)
 static int
 insert_clnt_poll(struct clnt_info *clp)
 {
+       if ((clp->gssd_fd != -1) && (clp->gssd_poll_index == -1)) {
+               if (get_poll_index(&clp->gssd_poll_index)) {
+                       printerr(0, "ERROR: Too many gssd clients\n");
+                       return -1;
+               }
+               pollarray[clp->gssd_poll_index].fd = clp->gssd_fd;
+               pollarray[clp->gssd_poll_index].events |= POLLIN;
+       }
+
        if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) {
                if (get_poll_index(&clp->krb5_poll_index)) {
                        printerr(0, "ERROR: Too many krb5 clients\n");
@@ -301,17 +444,18 @@ insert_clnt_poll(struct clnt_info *clp)
 }
 
 static void
-process_clnt_dir(char *dir)
+process_clnt_dir(char *dir, char *pdir)
 {
        struct clnt_info *      clp;
 
        if (!(clp = insert_new_clnt()))
                goto fail_destroy_client;
 
-       if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) {
+       /* An extra for the '/', and an extra for the null */
+       if (!(clp->dirname = calloc(strlen(dir) + strlen(pdir) + 2, 1))) {
                goto fail_destroy_client;
        }
-       memcpy(clp->dirname, dir, strlen(dir));
+       sprintf(clp->dirname, "%s/%s", pdir, dir);
        if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) {
                printerr(0, "ERROR: can't open %s: %s\n",
                         clp->dirname, strerror(errno));
@@ -355,16 +499,24 @@ init_client_list(void)
  * directories, since the DNOTIFY could have been in there.
  */
 static void
-update_old_clients(struct dirent **namelist, int size)
+update_old_clients(struct dirent **namelist, int size, char *pdir)
 {
        struct clnt_info *clp;
        void *saveprev;
        int i, stillhere;
+       char fname[PATH_MAX];
 
        for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
+               /* only compare entries in the global list that are from the
+                * same pipefs parent directory as "pdir"
+                */
+               if (strncmp(clp->dirname, pdir, strlen(pdir)) != 0) continue;
+
                stillhere = 0;
                for (i=0; i < size; i++) {
-                       if (!strcmp(clp->dirname, namelist[i]->d_name)) {
+                       snprintf(fname, sizeof(fname), "%s/%s",
+                                pdir, namelist[i]->d_name);
+                       if (strcmp(clp->dirname, fname) == 0) {
                                stillhere = 1;
                                break;
                        }
@@ -385,48 +537,69 @@ update_old_clients(struct dirent **namelist, int size)
 
 /* Search for a client by directory name, return 1 if found, 0 otherwise */
 static int
-find_client(char *dirname)
+find_client(char *dirname, char *pdir)
 {
        struct clnt_info        *clp;
+       char fname[PATH_MAX];
 
-       for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next)
-               if (!strcmp(clp->dirname, dirname))
+       for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) {
+               snprintf(fname, sizeof(fname), "%s/%s", pdir, dirname);
+               if (strcmp(clp->dirname, fname) == 0)
                        return 1;
+       }
        return 0;
 }
 
-/* Used to read (and re-read) list of clients, set up poll array. */
-int
-update_client_list(void)
+static int
+process_pipedir(char *pipe_name)
 {
        struct dirent **namelist;
        int i, j;
 
-       if (chdir(pipefs_nfsdir) < 0) {
+       if (chdir(pipe_name) < 0) {
                printerr(0, "ERROR: can't chdir to %s: %s\n",
-                        pipefs_nfsdir, strerror(errno));
+                        pipe_name, strerror(errno));
                return -1;
        }
 
-       j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort);
+       j = scandir(pipe_name, &namelist, NULL, alphasort);
        if (j < 0) {
                printerr(0, "ERROR: can't scandir %s: %s\n",
-                        pipefs_nfsdir, strerror(errno));
+                        pipe_name, strerror(errno));
                return -1;
        }
-       update_old_clients(namelist, j);
+
+       update_old_clients(namelist, j, pipe_name);
        for (i=0; i < j; i++) {
                if (i < FD_ALLOC_BLOCK
                                && !strncmp(namelist[i]->d_name, "clnt", 4)
-                               && !find_client(namelist[i]->d_name))
-                       process_clnt_dir(namelist[i]->d_name);
+                               && !find_client(namelist[i]->d_name, pipe_name))
+                       process_clnt_dir(namelist[i]->d_name, pipe_name);
                free(namelist[i]);
        }
 
        free(namelist);
+
        return 0;
 }
 
+/* Used to read (and re-read) list of clients, set up poll array. */
+int
+update_client_list(void)
+{
+       int retval = -1;
+       struct topdirs_info *tdi;
+
+       TAILQ_FOREACH(tdi, &topdirs_list, list) {
+               retval = process_pipedir(tdi->dirname);
+               if (retval)
+                       printerr(1, "WARNING: error processing %s\n",
+                                tdi->dirname);
+
+       }
+       return retval;
+}
+
 static int
 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
            gss_buffer_desc *context_token)
@@ -480,6 +653,91 @@ out_err:
        return -1;
 }
 
+/*
+ * If the port isn't already set, do an rpcbind query to the remote server
+ * using the program and version and get the port. 
+ *
+ * Newer kernels send the value of the port= mount option in the "info"
+ * file for the upcall or '0' for NFSv2/3. For NFSv4 it sends the value
+ * of the port= option or '2049'. The port field in a new sockaddr should
+ * reflect the value that was sent by the kernel.
+ */
+static int
+populate_port(struct sockaddr *sa, const socklen_t salen,
+             const rpcprog_t program, const rpcvers_t version,
+             const unsigned short protocol)
+{
+       struct sockaddr_in      *s4 = (struct sockaddr_in *) sa;
+#ifdef IPV6_SUPPORTED
+       struct sockaddr_in6     *s6 = (struct sockaddr_in6 *) sa;
+#endif /* IPV6_SUPPORTED */
+       unsigned short          port;
+
+       /*
+        * Newer kernels send the port in the upcall. If we already have
+        * the port, there's no need to look it up.
+        */
+       switch (sa->sa_family) {
+       case AF_INET:
+               if (s4->sin_port != 0) {
+                       printerr(2, "DEBUG: port already set to %d\n",
+                                ntohs(s4->sin_port));
+                       return 1;
+               }
+               break;
+#ifdef IPV6_SUPPORTED
+       case AF_INET6:
+               if (s6->sin6_port != 0) {
+                       printerr(2, "DEBUG: port already set to %d\n",
+                                ntohs(s6->sin6_port));
+                       return 1;
+               }
+               break;
+#endif /* IPV6_SUPPORTED */
+       default:
+               printerr(0, "ERROR: unsupported address family %d\n",
+                           sa->sa_family);
+               return 0;
+       }
+
+       /*
+        * Newer kernels that send the port in the upcall set the value to
+        * 2049 for NFSv4 mounts when one isn't specified. The check below is
+        * only for kernels that don't send the port in the upcall. For those
+        * we either have to do an rpcbind query or set it to the standard
+        * port. Doing a query could be problematic (firewalls, etc), so take
+        * the latter approach.
+        */
+       if (program == 100003 && version == 4) {
+               port = 2049;
+               goto set_port;
+       }
+
+       port = nfs_getport(sa, salen, program, version, protocol);
+       if (!port) {
+               printerr(0, "ERROR: unable to obtain port for prog %ld "
+                           "vers %ld\n", program, version);
+               return 0;
+       }
+
+set_port:
+       printerr(2, "DEBUG: setting port to %hu for prog %lu vers %lu\n", port,
+                program, version);
+
+       switch (sa->sa_family) {
+       case AF_INET:
+               s4->sin_port = htons(port);
+               break;
+#ifdef IPV6_SUPPORTED
+       case AF_INET6:
+               s6->sin6_port = htons(port);
+               break;
+#endif /* IPV6_SUPPORTED */
+       }
+
+       return 1;
+}
+
 /*
  * Create an RPC connection and establish an authenticated
  * gss context with a server.
@@ -495,14 +753,12 @@ int create_auth_rpc_client(struct clnt_info *clp,
        AUTH                    *auth = NULL;
        uid_t                   save_uid = -1;
        int                     retval = -1;
-       int                     errcode;
        OM_uint32               min_stat;
        char                    rpc_errmsg[1024];
-       int                     sockp = RPC_ANYSOCK;
-       int                     sendsz = 32768, recvsz = 32768;
-       struct addrinfo         ai_hints, *a = NULL;
-       char                    service[64];
-       char                    *at_sign;
+       int                     protocol;
+       struct timeval          timeout = {5, 0};
+       struct sockaddr         *addr = (struct sockaddr *) &clp->addr;
+       socklen_t               salen;
 
        /* Create the context as the user (not as root) */
        save_uid = geteuid();
@@ -556,15 +812,10 @@ int create_auth_rpc_client(struct clnt_info *clp,
        printerr(2, "creating %s client for server %s\n", clp->protocol,
                        clp->servername);
 
-       memset(&ai_hints, '\0', sizeof(ai_hints));
-       ai_hints.ai_family = PF_INET;
-       ai_hints.ai_flags |= AI_CANONNAME;
        if ((strcmp(clp->protocol, "tcp")) == 0) {
-               ai_hints.ai_socktype = SOCK_STREAM;
-               ai_hints.ai_protocol = IPPROTO_TCP;
+               protocol = IPPROTO_TCP;
        } else if ((strcmp(clp->protocol, "udp")) == 0) {
-               ai_hints.ai_socktype = SOCK_DGRAM;
-               ai_hints.ai_protocol = IPPROTO_UDP;
+               protocol = IPPROTO_UDP;
        } else {
                printerr(0, "WARNING: unrecognized protocol, '%s', requested "
                         "for connection to server %s for user with uid %d\n",
@@ -572,72 +823,36 @@ int create_auth_rpc_client(struct clnt_info *clp,
                goto out_fail;
        }
 
-       /* extract the service name from clp->servicename */
-       if ((at_sign = strchr(clp->servicename, '@')) == NULL) {
-               printerr(0, "WARNING: servicename (%s) not formatted as "
-                       "expected with service@host\n", clp->servicename);
-               goto out_fail;
-       }
-       if ((at_sign - clp->servicename) >= sizeof(service)) {
-               printerr(0, "WARNING: service portion of servicename (%s) "
-                       "is too long!\n", clp->servicename);
+       switch (addr->sa_family) {
+       case AF_INET:
+               salen = sizeof(struct sockaddr_in);
+               break;
+#ifdef IPV6_SUPPORTED
+       case AF_INET6:
+               salen = sizeof(struct sockaddr_in6);
+               break;
+#endif /* IPV6_SUPPORTED */
+       default:
+               printerr(1, "ERROR: Unknown address family %d\n",
+                        addr->sa_family);
                goto out_fail;
        }
-       strncpy(service, clp->servicename, at_sign - clp->servicename);
-       service[at_sign - clp->servicename] = '\0';
 
-       errcode = getaddrinfo(clp->servername, service, &ai_hints, &a);
-       if (errcode) {
-               printerr(0, "WARNING: Error from getaddrinfo for server "
-                        "'%s': %s\n", clp->servername, gai_strerror(errcode));
+       if (!populate_port(addr, salen, clp->prog, clp->vers, protocol))
                goto out_fail;
-       }
 
-       if (a == NULL) {
-               printerr(0, "WARNING: No address information found for "
-                        "connection to server %s for user with uid %d\n",
+       rpc_clnt = nfs_get_rpcclient(addr, salen, protocol, clp->prog,
+                                    clp->vers, &timeout);
+       if (!rpc_clnt) {
+               snprintf(rpc_errmsg, sizeof(rpc_errmsg),
+                        "WARNING: can't create %s rpc_clnt to server %s for "
+                        "user with uid %d",
+                        protocol == IPPROTO_TCP ? "tcp" : "udp",
                         clp->servername, uid);
+               printerr(0, "%s\n",
+                        clnt_spcreateerror(rpc_errmsg));
                goto out_fail;
        }
-       if (clp->port)
-               ((struct sockaddr_in *)a->ai_addr)->sin_port = htons(clp->port);
-       if (a->ai_protocol == IPPROTO_TCP) {
-               if ((rpc_clnt = clnttcp_create(
-                                       (struct sockaddr_in *) a->ai_addr,
-                                       clp->prog, clp->vers, &sockp,
-                                       sendsz, recvsz)) == NULL) {
-                       snprintf(rpc_errmsg, sizeof(rpc_errmsg),
-                                "WARNING: can't create tcp rpc_clnt "
-                                "for server %s for user with uid %d",
-                                clp->servername, uid);
-                       printerr(0, "%s\n",
-                                clnt_spcreateerror(rpc_errmsg));
-                       goto out_fail;
-               }
-       } else if (a->ai_protocol == IPPROTO_UDP) {
-               const struct timeval timeout = {5, 0};
-               if ((rpc_clnt = clntudp_bufcreate(
-                                       (struct sockaddr_in *) a->ai_addr,
-                                       clp->prog, clp->vers, timeout,
-                                       &sockp, sendsz, recvsz)) == NULL) {
-                       snprintf(rpc_errmsg, sizeof(rpc_errmsg),
-                                "WARNING: can't create udp rpc_clnt "
-                                "for server %s for user with uid %d",
-                                clp->servername, uid);
-                       printerr(0, "%s\n",
-                                clnt_spcreateerror(rpc_errmsg));
-                       goto out_fail;
-               }
-       } else {
-               /* Shouldn't happen! */
-               printerr(0, "ERROR: requested protocol '%s', but "
-                        "got addrinfo with protocol %d\n",
-                        clp->protocol, a->ai_protocol);
-               goto out_fail;
-       }
-       /* We're done with this */
-       freeaddrinfo(a);
-       a = NULL;
 
        printerr(2, "creating context with server %s\n", clp->servicename);
        auth = authgss_create_default(rpc_clnt, clp->servicename, &sec);
@@ -659,7 +874,6 @@ int create_auth_rpc_client(struct clnt_info *clp,
   out:
        if (sec.cred != GSS_C_NO_CREDENTIAL)
                gss_release_cred(&min_stat, &sec.cred);
-       if (a != NULL) freeaddrinfo(a);
        /* Restore euid to original value */
        if ((save_uid != -1) && (setfsuid(save_uid) != uid)) {
                printerr(0, "WARNING: Failed to restore fsuid"
@@ -674,15 +888,14 @@ int create_auth_rpc_client(struct clnt_info *clp,
        goto out;
 }
 
-
 /*
  * this code uses the userland rpcsec gss library to create a krb5
  * context on behalf of the kernel
  */
-void
-handle_krb5_upcall(struct clnt_info *clp)
+static void
+process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
+                   char *service)
 {
-       uid_t                   uid;
        CLIENT                  *rpc_clnt = NULL;
        AUTH                    *auth = NULL;
        struct authgss_private_data pd;
@@ -691,23 +904,51 @@ handle_krb5_upcall(struct clnt_info *clp)
        char                    **ccname;
        char                    **dirname;
        int                     create_resp = -1;
+       int                     err, downcall_err = -EACCES;
 
-       printerr(1, "handling krb5 upcall\n");
+       printerr(1, "handling krb5 upcall (%s)\n", clp->dirname);
 
+       if (tgtname) {
+               if (clp->servicename) {
+                       free(clp->servicename);
+                       clp->servicename = strdup(tgtname);
+               }
+       }
        token.length = 0;
        token.value = NULL;
        memset(&pd, 0, sizeof(struct authgss_private_data));
 
-       if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
-               printerr(0, "WARNING: failed reading uid from krb5 "
-                           "upcall pipe: %s\n", strerror(errno));
-               goto out;
-       }
-
-       if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) {
+       /*
+        * If "service" is specified, then the kernel is indicating that
+        * we must use machine credentials for this request.  (Regardless
+        * of the uid value or the setting of root_uses_machine_creds.)
+        * If the service value is "*", then any service name can be used.
+        * Otherwise, it specifies the service name that should be used.
+        * (For now, the values of service will only be "*" or "nfs".)
+        *
+        * Restricting gssd to use "nfs" service name is needed for when
+        * the NFS server is doing a callback to the NFS client.  In this
+        * case, the NFS server has to authenticate itself as "nfs" --
+        * even if there are other service keys such as "host" or "root"
+        * in the keytab.
+        *
+        * Another case when the kernel may specify the service attribute
+        * is when gssd is being asked to create the context for a
+        * SETCLIENT_ID operation.  In this case, machine credentials
+        * must be used for the authentication.  However, the service name
+        * used for this case is not important.
+        *
+        */
+       printerr(2, "%s: service is '%s'\n", __func__,
+                service ? service : "<null>");
+       if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 &&
+                               service == NULL)) {
                /* Tell krb5 gss which credentials cache to use */
                for (dirname = ccachesearch; *dirname != NULL; dirname++) {
-                       if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0)
+                       err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname);
+                       if (err == -EKEYEXPIRED)
+                               downcall_err = -EKEYEXPIRED;
+                       else if (!err)
                                create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
                                                             AUTHTYPE_KRB5);
                        if (create_resp == 0)
@@ -715,43 +956,50 @@ handle_krb5_upcall(struct clnt_info *clp)
                }
        }
        if (create_resp != 0) {
-               if (uid == 0 && root_uses_machine_creds == 1) {
+               if (uid == 0 && (root_uses_machine_creds == 1 ||
+                               service != NULL)) {
+                       int nocache = 0;
                        int success = 0;
-
-                       gssd_refresh_krb5_machine_credential(clp->servername,
-                                                            NULL);
-                       /*
-                        * Get a list of credential cache names and try each
-                        * of them until one works or we've tried them all
-                        */
-                       if (gssd_get_krb5_machine_cred_list(&credlist)) {
-                               printerr(0, "ERROR: No credentials found "
-                                        "for connection to server %s\n",
-                                        clp->servername);
-                                       goto out_return_error;
-                       }
-                       for (ccname = credlist; ccname && *ccname; ccname++) {
-                               gssd_setup_krb5_machine_gss_ccache(*ccname);
-                               if ((create_auth_rpc_client(clp, &rpc_clnt,
-                                                           &auth, uid,
-                                                           AUTHTYPE_KRB5)) == 0) {
-                                       /* Success! */
-                                       success++;
-                                       break;
+                       do {
+                               gssd_refresh_krb5_machine_credential(clp->servername,
+                                                                    NULL, service);
+                               /*
+                                * Get a list of credential cache names and try each
+                                * of them until one works or we've tried them all
+                                */
+                               if (gssd_get_krb5_machine_cred_list(&credlist)) {
+                                       printerr(0, "ERROR: No credentials found "
+                                                "for connection to server %s\n",
+                                                clp->servername);
+                                               goto out_return_error;
                                }
-                               printerr(2, "WARNING: Failed to create krb5 context "
-                                        "for user with uid %d with credentials "
-                                        "cache %s for server %s\n",
-                                        uid, *ccname, clp->servername);
-                       }
-                       gssd_free_krb5_machine_cred_list(credlist);
-                       if (!success) {
-                               printerr(1, "WARNING: Failed to create krb5 context "
-                                        "for user with uid %d with any "
-                                        "credentials cache for server %s\n",
-                                        uid, clp->servername);
-                               goto out_return_error;
-                       }
+                               for (ccname = credlist; ccname && *ccname; ccname++) {
+                                       gssd_setup_krb5_machine_gss_ccache(*ccname);
+                                       if ((create_auth_rpc_client(clp, &rpc_clnt,
+                                                                   &auth, uid,
+                                                                   AUTHTYPE_KRB5)) == 0) {
+                                               /* Success! */
+                                               success++;
+                                               break;
+                                       } 
+                                       printerr(2, "WARNING: Failed to create machine krb5 context "
+                                                "with credentials cache %s for server %s\n",
+                                                *ccname, clp->servername);
+                               }
+                               gssd_free_krb5_machine_cred_list(credlist);                     
+                               if (!success) {
+                                       if(nocache == 0) {
+                                               nocache++;
+                                               printerr(2, "WARNING: Machine cache is prematurely expired or corrupted "
+                                                           "trying to recreate cache for server %s\n", clp->servername);
+                                       } else {
+                                               printerr(1, "WARNING: Failed to create machine krb5 context "
+                                                "with any credentials cache for server %s\n",
+                                                clp->servername);
+                                               goto out_return_error;
+                                       }
+                               }
+                       } while(!success);
                } else {
                        printerr(1, "WARNING: Failed to create krb5 context "
                                 "for user with uid %d for server %s\n",
@@ -774,7 +1022,7 @@ handle_krb5_upcall(struct clnt_info *clp)
                goto out_return_error;
        }
 
-       do_downcall(clp->krb5_fd, uid, &pd, &token);
+       do_downcall(fd, uid, &pd, &token);
 
 out:
        if (token.value)
@@ -790,7 +1038,7 @@ out:
        return;
 
 out_return_error:
-       do_error_downcall(clp->krb5_fd, uid, -1);
+       do_error_downcall(fd, uid, downcall_err);
        goto out;
 }
 
@@ -798,26 +1046,19 @@ out_return_error:
  * this code uses the userland rpcsec gss library to create an spkm3
  * context on behalf of the kernel
  */
-void
-handle_spkm3_upcall(struct clnt_info *clp)
+static void
+process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd)
 {
-       uid_t                   uid;
        CLIENT                  *rpc_clnt = NULL;
        AUTH                    *auth = NULL;
        struct authgss_private_data pd;
        gss_buffer_desc         token;
 
-       printerr(2, "handling spkm3 upcall\n");
+       printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname);
 
        token.length = 0;
        token.value = NULL;
 
-       if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
-               printerr(0, "WARNING: failed reading uid from spkm3 "
-                        "upcall pipe: %s\n", strerror(errno));
-               goto out;
-       }
-
        if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) {
                printerr(0, "WARNING: Failed to create spkm3 context for "
                            "user with uid %d\n", uid);
@@ -838,7 +1079,7 @@ handle_spkm3_upcall(struct clnt_info *clp)
                goto out_return_error;
        }
 
-       do_downcall(clp->spkm3_fd, uid, &pd, &token);
+       do_downcall(fd, uid, &pd, &token);
 
 out:
        if (token.value)
@@ -850,6 +1091,139 @@ out:
        return;
 
 out_return_error:
-       do_error_downcall(clp->spkm3_fd, uid, -1);
+       do_error_downcall(fd, uid, -1);
        goto out;
 }
+
+void
+handle_krb5_upcall(struct clnt_info *clp)
+{
+       uid_t                   uid;
+
+       if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+               printerr(0, "WARNING: failed reading uid from krb5 "
+                           "upcall pipe: %s\n", strerror(errno));
+               return;
+       }
+
+       return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL);
+}
+
+void
+handle_spkm3_upcall(struct clnt_info *clp)
+{
+       uid_t                   uid;
+
+       if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+               printerr(0, "WARNING: failed reading uid from spkm3 "
+                        "upcall pipe: %s\n", strerror(errno));
+               return;
+       }
+
+       return process_spkm3_upcall(clp, uid, clp->spkm3_fd);
+}
+
+void
+handle_gssd_upcall(struct clnt_info *clp)
+{
+       uid_t                   uid;
+       char                    *lbuf = NULL;
+       int                     lbuflen = 0;
+       char                    *p;
+       char                    *mech = NULL;
+       char                    *target = NULL;
+       char                    *service = NULL;
+
+       printerr(1, "handling gssd upcall (%s)\n", clp->dirname);
+
+       if (readline(clp->gssd_fd, &lbuf, &lbuflen) != 1) {
+               printerr(0, "WARNING: handle_gssd_upcall: "
+                           "failed reading request\n");
+               return;
+       }
+       printerr(2, "%s: '%s'\n", __func__, lbuf);
+
+       /* find the mechanism name */
+       if ((p = strstr(lbuf, "mech=")) != NULL) {
+               mech = malloc(lbuflen);
+               if (!mech)
+                       goto out;
+               if (sscanf(p, "mech=%s", mech) != 1) {
+                       printerr(0, "WARNING: handle_gssd_upcall: "
+                                   "failed to parse gss mechanism name "
+                                   "in upcall string '%s'\n", lbuf);
+                       goto out;
+               }
+       } else {
+               printerr(0, "WARNING: handle_gssd_upcall: "
+                           "failed to find gss mechanism name "
+                           "in upcall string '%s'\n", lbuf);
+               goto out;
+       }
+
+       /* read uid */
+       if ((p = strstr(lbuf, "uid=")) != NULL) {
+               if (sscanf(p, "uid=%d", &uid) != 1) {
+                       printerr(0, "WARNING: handle_gssd_upcall: "
+                                   "failed to parse uid "
+                                   "in upcall string '%s'\n", lbuf);
+                       goto out;
+               }
+       } else {
+               printerr(0, "WARNING: handle_gssd_upcall: "
+                           "failed to find uid "
+                           "in upcall string '%s'\n", lbuf);
+               goto out;
+       }
+
+       /* read target name */
+       if ((p = strstr(lbuf, "target=")) != NULL) {
+               target = malloc(lbuflen);
+               if (!target)
+                       goto out;
+               if (sscanf(p, "target=%s", target) != 1) {
+                       printerr(0, "WARNING: handle_gssd_upcall: "
+                                   "failed to parse target name "
+                                   "in upcall string '%s'\n", lbuf);
+                       goto out;
+               }
+       }
+
+       /*
+        * read the service name
+        *
+        * The presence of attribute "service=" indicates that machine
+        * credentials should be used for this request.  If the value
+        * is "*", then any machine credentials available can be used.
+        * If the value is anything else, then machine credentials for
+        * the specified service name (always "nfs" for now) should be
+        * used.
+        */
+       if ((p = strstr(lbuf, "service=")) != NULL) {
+               service = malloc(lbuflen);
+               if (!service)
+                       goto out;
+               if (sscanf(p, "service=%s", service) != 1) {
+                       printerr(0, "WARNING: handle_gssd_upcall: "
+                                   "failed to parse service type "
+                                   "in upcall string '%s'\n", lbuf);
+                       goto out;
+               }
+       }
+
+       if (strcmp(mech, "krb5") == 0)
+               process_krb5_upcall(clp, uid, clp->gssd_fd, target, service);
+       else if (strcmp(mech, "spkm3") == 0)
+               process_spkm3_upcall(clp, uid, clp->gssd_fd);
+       else
+               printerr(0, "WARNING: handle_gssd_upcall: "
+                           "received unknown gss mech '%s'\n", mech);
+
+out:
+       free(lbuf);
+       free(mech);
+       free(target);
+       free(service);
+       return; 
+}
+