X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fgssd%2Fgssd_proc.c;h=aa394359e27398fd3dfe25462ca2be177b7088d4;hp=788ecf1d7551a765eb904f17985f7ecdfe6bcc8b;hb=245fad6be5a32866b2cefad55b3e2d50f6b197af;hpb=5206cbfa8934cb7331a840f3446d35cdbb795e34 diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c index 788ecf1..aa39435 100644 --- a/utils/gssd/gssd_proc.c +++ b/utils/gssd/gssd_proc.c @@ -38,15 +38,20 @@ */ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "config.h" + #include #include #include #include #include +#include #include #include @@ -65,9 +70,10 @@ #include "gssd.h" #include "err_util.h" #include "gss_util.h" -#include "gss_oids.h" #include "krb5_util.h" #include "context.h" +#include "nfsrpc.h" +#include "nfslib.h" /* * pollarray: @@ -78,32 +84,129 @@ * 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 - * /pipefsdir/clntXX : one per rpc_clnt struct in the kernel - * /pipefsdir/clntXX/krb5 : read uid for which kernel wants - * a context, write the resulting context - * /pipefsdir/clntXX/info : stores info such as server name + * 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 + * {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 /pipefsdir/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 pipefsdir/ or in an of the clntXX directories, - * and rescan the whole pipefsdir 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 *prog, int *vers, char **protocol, + struct sockaddr *addr) { #define INFOBUFLEN 256 - char buf[INFOBUFLEN]; + char buf[INFOBUFLEN + 1]; static char dummy[128]; int nbytes; static char service[128]; @@ -111,9 +214,9 @@ read_service_info(char *info_file_name, char **servicename, char **servername, char program[16]; char version[16]; char protoname[16]; - in_addr_t inaddr; + char port[128]; + char *p; int fd = -1; - struct hostent *ent = NULL; int numfields; *servicename = *servername = *protocol = NULL; @@ -126,11 +229,12 @@ read_service_info(char *info_file_name, char **servicename, char **servername, if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1) goto fail; close(fd); + buf[nbytes] = '\0'; - numfields = sscanf(buf,"RPC server: %s\n" - "service: %s %s version %s\n" - "address: %s\n" - "protocol: %s\n", + numfields = sscanf(buf,"RPC server: %127s\n" + "service: %127s %15s version %15s\n" + "address: %127s\n" + "protocol: %15s\n", dummy, service, program, version, address, @@ -142,23 +246,36 @@ read_service_info(char *info_file_name, char **servicename, char **servername, goto fail; } + port[0] = '\0'; + if ((p = strstr(buf, "port")) != NULL) + 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)); @@ -169,22 +286,29 @@ read_service_info(char *info_file_name, char **servicename, char **servername, fail: printerr(0, "ERROR: failed to read service info\n"); if (fd != -1) close(fd); - if (*servername) free(*servername); - if (*servicename) free(*servicename); - if (*protocol) free(*protocol); + free(*servername); + free(*servicename); + free(*protocol); + *servicename = *servername = *protocol = NULL; return -1; } static void destroy_client(struct clnt_info *clp) { + if (clp->krb5_poll_index != -1) + memset(&pollarray[clp->krb5_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->dirname) free(clp->dirname); - if (clp->servicename) free(clp->servicename); - if (clp->servername) free(clp->servername); - if (clp->protocol) free(clp->protocol); + if (clp->gssd_fd != -1) close(clp->gssd_fd); + free(clp->dirname); + free(clp->servicename); + free(clp->servername); + free(clp->protocol); free(clp); } @@ -199,9 +323,9 @@ insert_new_clnt(void) goto out; } 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); @@ -212,21 +336,42 @@ out: static int process_clnt_dir_files(struct clnt_info * clp) { - char kname[32]; - char sname[32]; - char info_file_name[32]; - - snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname); - clp->krb5_fd = open(kname, O_RDWR); - snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname); - clp->spkm3_fd = open(sname, O_RDWR); - if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1)) + char name[PATH_MAX]; + char gname[PATH_MAX]; + char info_file_name[PATH_MAX]; + + if (clp->gssd_fd == -1) { + snprintf(gname, sizeof(gname), "%s/gssd", clp->dirname); + clp->gssd_fd = open(gname, 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 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->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->krb5_fd == -1) && (clp->gssd_fd == -1)) return -1; snprintf(info_file_name, sizeof(info_file_name), "%s/info", clp->dirname); - if (read_service_info(info_file_name, &clp->servicename, + if ((clp->servicename == NULL) && + read_service_info(info_file_name, &clp->servicename, &clp->servername, &clp->prog, &clp->vers, - &clp->protocol)) + &clp->protocol, (struct sockaddr *) &clp->addr)) return -1; return 0; } @@ -250,18 +395,44 @@ get_poll_index(int *ind) return 0; } + +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"); + return -1; + } + pollarray[clp->krb5_poll_index].fd = clp->krb5_fd; + pollarray[clp->krb5_poll_index].events |= POLLIN; + } + + return 0; +} + 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)); @@ -273,23 +444,8 @@ process_clnt_dir(char *dir) if (process_clnt_dir_files(clp)) goto fail_keep_client; - if(clp->krb5_fd != -1) { - if (get_poll_index(&clp->krb5_poll_index)) { - printerr(0, "ERROR: Too many krb5 clients\n"); - goto fail_destroy_client; - } - pollarray[clp->krb5_poll_index].fd = clp->krb5_fd; - pollarray[clp->krb5_poll_index].events |= POLLIN; - } - - if(clp->spkm3_fd != -1) { - if (get_poll_index(&clp->spkm3_poll_index)) { - printerr(0, "ERROR: Too many spkm3 clients\n"); - goto fail_destroy_client; - } - pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd; - pollarray[clp->spkm3_poll_index].events |= POLLIN; - } + if (insert_clnt_poll(clp)) + goto fail_destroy_client; return; @@ -314,51 +470,171 @@ init_client_list(void) pollarray = calloc(pollsize, sizeof(struct pollfd)); } +/* + * This is run after a DNOTIFY signal, and should clear up any + * directories that are no longer around, and re-scan any existing + * directories, since the DNOTIFY could have been in there. + */ static void -destroy_client_list(void) +update_old_clients(struct dirent **namelist, int size, char *pdir) { - struct clnt_info *clp; + 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++) { + snprintf(fname, sizeof(fname), "%s/%s", + pdir, namelist[i]->d_name); + if (strcmp(clp->dirname, fname) == 0) { + stillhere = 1; + break; + } + } + if (!stillhere) { + printerr(2, "destroying client %s\n", clp->dirname); + saveprev = clp->list.tqe_prev; + TAILQ_REMOVE(&clnt_list, clp, list); + destroy_client(clp); + clp = saveprev; + } + } + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { + if (!process_clnt_dir_files(clp)) + insert_clnt_poll(clp); + } +} - printerr(1, "processing client list\n"); +/* Search for a client by directory name, return 1 if found, 0 otherwise */ +static int +find_client(char *dirname, char *pdir) +{ + struct clnt_info *clp; + char fname[PATH_MAX]; - while (clnt_list.tqh_first != NULL) { - clp = clnt_list.tqh_first; - TAILQ_REMOVE(&clnt_list, clp, list); - destroy_client(clp); + 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; + int i, j; - destroy_client_list(); - - if (chdir(pipefsdir) < 0) { + if (chdir(pipe_name) < 0) { printerr(0, "ERROR: can't chdir to %s: %s\n", - pipefsdir, strerror(errno)); + pipe_name, strerror(errno)); return -1; } - memset(pollarray, 0, pollsize * sizeof(struct pollfd)); - - j = scandir(pipefsdir, &namelist, NULL, alphasort); + j = scandir(pipe_name, &namelist, NULL, alphasort); if (j < 0) { printerr(0, "ERROR: can't scandir %s: %s\n", - pipefsdir, strerror(errno)); + pipe_name, strerror(errno)); return -1; } + + 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)) - process_clnt_dir(namelist[i]->d_name); + && !strncmp(namelist[i]->d_name, "clnt", 4) + && !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; +} + +/* Encryption types supported by the kernel rpcsec_gss code */ +int num_krb5_enctypes = 0; +krb5_enctype *krb5_enctypes = NULL; + +/* + * Parse the supported encryption type information + */ +static int +parse_enctypes(char *enctypes) +{ + int n = 0; + char *curr, *comma; + int i; + static char *cached_types; + + if (cached_types && strcmp(cached_types, enctypes) == 0) + return 0; + free(cached_types); + + if (krb5_enctypes != NULL) { + free(krb5_enctypes); + krb5_enctypes = NULL; + num_krb5_enctypes = 0; + } + + /* count the number of commas */ + for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) { + comma = strchr(curr, ','); + if (comma != NULL) + n++; + else + break; + } + /* If no more commas and we're not at the end, there's one more value */ + if (*curr != '\0') + n++; + + /* Empty string, return an error */ + if (n == 0) + return ENOENT; + + /* Allocate space for enctypes array */ + if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) { + return ENOMEM; + } + + /* Now parse each value into the array */ + for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) { + krb5_enctypes[i++] = atoi(curr); + comma = strchr(curr, ','); + if (comma == NULL) + break; + } + + num_krb5_enctypes = n; + if ((cached_types = malloc(strlen(enctypes)+1))) + strcpy(cached_types, enctypes); + return 0; } @@ -366,23 +642,29 @@ static int do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, gss_buffer_desc *context_token) { - char buf[2048]; - char *p = buf, *end = buf + 2048; - unsigned int timeout = 0; /* XXX decide on a reasonable value */ + char *buf = NULL, *p = NULL, *end = NULL; + unsigned int timeout = context_timeout; + unsigned int buf_size = 0; printerr(1, "doing downcall\n"); + buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) + + sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length + + sizeof(context_token->length) + context_token->length; + p = buf = malloc(buf_size); + end = buf + buf_size; if (WRITE_BYTES(&p, end, uid)) goto out_err; - /* Not setting any timeout for now: */ if (WRITE_BYTES(&p, end, timeout)) goto out_err; if (WRITE_BYTES(&p, end, pd->pd_seq_win)) goto out_err; if (write_buffer(&p, end, &pd->pd_ctx_hndl)) goto out_err; if (write_buffer(&p, end, context_token)) goto out_err; if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; + if (buf) free(buf); return 0; out_err: - printerr(0, "Failed to write downcall!\n"); + if (buf) free(buf); + printerr(1, "Failed to write downcall!\n"); return -1; } @@ -405,15 +687,101 @@ do_error_downcall(int k5_fd, uid_t uid, int err) if (write(k5_fd, buf, p - buf) < p - buf) goto out_err; return 0; out_err: - printerr(0, "Failed to write error downcall!\n"); + printerr(1, "Failed to write error downcall!\n"); 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. */ int create_auth_rpc_client(struct clnt_info *clp, + CLIENT **clnt_return, AUTH **auth_return, uid_t uid, int authtype) @@ -424,6 +792,21 @@ int create_auth_rpc_client(struct clnt_info *clp, uid_t save_uid = -1; int retval = -1; OM_uint32 min_stat; + char rpc_errmsg[1024]; + 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(); + if (setfsuid(uid) != 0) { + printerr(0, "WARNING: Failed to setfsuid for " + "user with uid %d\n", uid); + goto out_fail; + } + printerr(2, "creating context using fsuid %d (save_uid %d)\n", + uid, save_uid); sec.qop = GSS_C_QOP_DEFAULT; sec.svc = RPCSEC_GSS_SVC_NONE; @@ -433,10 +816,6 @@ int create_auth_rpc_client(struct clnt_info *clp, sec.mech = (gss_OID)&krb5oid; sec.req_flags = GSS_C_MUTUAL_FLAG; } - else if (authtype == AUTHTYPE_SPKM3) { - sec.mech = (gss_OID)&spkm3oid; - sec.req_flags = GSS_C_ANON_FLAG; - } else { printerr(0, "ERROR: Invalid authentication type (%d) " "in create_auth_rpc_client\n", authtype); @@ -450,7 +829,7 @@ int create_auth_rpc_client(struct clnt_info *clp, * Do this before creating rpc connection since we won't need * rpc connection if it fails! */ - if (limit_krb5_enctypes(&sec, uid)) { + if (limit_krb5_enctypes(&sec)) { printerr(1, "WARNING: Failed while limiting krb5 " "encryption types for user with uid %d\n", uid); @@ -459,25 +838,50 @@ int create_auth_rpc_client(struct clnt_info *clp, #endif } - /* Create the context as the user (not as root) */ - save_uid = geteuid(); - if (seteuid(uid) != 0) { - printerr(0, "WARNING: Failed to seteuid for " - "user with uid %d\n", uid); - goto out_fail; - } - printerr(2, "creating context using euid %d (save_uid %d)\n", - geteuid(), save_uid); - /* create an rpc connection to the nfs server */ printerr(2, "creating %s client for server %s\n", clp->protocol, clp->servername); - if ((rpc_clnt = clnt_create(clp->servername, clp->prog, clp->vers, - clp->protocol)) == NULL) { - printerr(0, "WARNING: can't create rpc_clnt for server " - "%s for user with uid %d\n", - clp->servername, uid); + + if ((strcmp(clp->protocol, "tcp")) == 0) { + protocol = IPPROTO_TCP; + } else if ((strcmp(clp->protocol, "udp")) == 0) { + protocol = IPPROTO_UDP; + } else { + printerr(0, "WARNING: unrecognized protocol, '%s', requested " + "for connection to server %s for user with uid %d\n", + clp->protocol, clp->servername, uid); + goto out_fail; + } + + 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; + } + + if (!populate_port(addr, salen, clp->prog, clp->vers, protocol)) + goto out_fail; + + 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; } @@ -491,180 +895,347 @@ int create_auth_rpc_client(struct clnt_info *clp, goto out_fail; } - /* Restore euid to original value */ - if (seteuid(save_uid) != 0) { - printerr(0, "WARNING: Failed to restore euid" - " to uid %d\n", save_uid); - goto out_fail; - } - save_uid = -1; - /* Success !!! */ + rpc_clnt->cl_auth = auth; + *clnt_return = rpc_clnt; *auth_return = auth; retval = 0; - out_fail: - if ((save_uid != -1) && (seteuid(save_uid) != 0)) { - printerr(0, "WARNING: Failed to restore euid" - " to uid %d (in error path)\n", save_uid); - } + out: if (sec.cred != GSS_C_NO_CREDENTIAL) gss_release_cred(&min_stat, &sec.cred); + /* Restore euid to original value */ + if (((int)save_uid != -1) && (setfsuid(save_uid) != (int)uid)) { + printerr(0, "WARNING: Failed to restore fsuid" + " to uid %d from %d\n", save_uid, uid); + } + return retval; + + out_fail: + /* Only destroy here if failure. Otherwise, caller is responsible */ if (rpc_clnt) clnt_destroy(rpc_clnt); - return retval; + goto out; } +static char * +user_cachedir(char *dirname, uid_t uid) +{ + struct passwd *pw; + char *ptr; + + if ((pw = getpwuid(uid)) == NULL) { + printerr(0, "user_cachedir: Failed to find '%d' uid" + " for cache directory\n"); + return NULL; + } + ptr = malloc(strlen(dirname)+strlen(pw->pw_name)+2); + if (ptr) + sprintf(ptr, "%s/%s", dirname, pw->pw_name); + return ptr; +} /* * 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; - AUTH *auth; + CLIENT *rpc_clnt = NULL; + AUTH *auth = NULL; struct authgss_private_data pd; gss_buffer_desc token; char **credlist = NULL; char **ccname; + char **dirname, *dir, *userdir; + 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; - - 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) { - int success = 0; - - /* - * 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, "WARNING: Failed to obtain machine " - "credentials 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, &auth, uid, - AUTHTYPE_KRB5)) == 0) { - /* Success! */ - success++; - break; + memset(&pd, 0, sizeof(struct authgss_private_data)); + + /* + * 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 : ""); + 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++) { + /* See if the user name is needed */ + if (strncmp(*dirname, GSSD_USER_CRED_DIR, + strlen(GSSD_USER_CRED_DIR)) == 0) { + userdir = user_cachedir(*dirname, uid); + if (userdir == NULL) + continue; + dir = userdir; + } else + dir = *dirname; + + err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, dir); + + if (userdir) { + free(userdir); + userdir = NULL; } - 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(0, "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; + 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) + break; } } - else { - /* Tell krb5 gss which credentials cache to use */ - gssd_setup_krb5_user_gss_ccache(uid, clp->servername); - - if (create_auth_rpc_client(clp, &auth, uid, AUTHTYPE_KRB5)) { - printerr(0, "WARNING: Failed to create krb5 context " - "for user with uid %d for server %s\n", + if (create_resp != 0) { + if (uid == 0 && (root_uses_machine_creds == 1 || + service != NULL)) { + int nocache = 0; + int success = 0; + 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; + } + 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", uid, clp->servername); goto out_return_error; } } if (!authgss_get_private_data(auth, &pd)) { - printerr(0, "WARNING: Failed to obtain authentication " + printerr(1, "WARNING: Failed to obtain authentication " "data for user with uid %d for server %s\n", uid, clp->servername); goto out_return_error; } - if (serialize_context_for_kernel(pd.pd_ctx, &token)) { + if (serialize_context_for_kernel(pd.pd_ctx, &token, &krb5oid, NULL)) { printerr(0, "WARNING: Failed to serialize krb5 context for " "user with uid %d for server %s\n", uid, clp->servername); goto out_return_error; } - do_downcall(clp->krb5_fd, uid, &pd, &token); + do_downcall(fd, uid, &pd, &token); +out: if (token.value) free(token.value); -out: +#ifndef HAVE_LIBTIRPC + if (pd.pd_ctx_hndl.length != 0) + authgss_free_private_data(&pd); +#endif + if (auth) + AUTH_DESTROY(auth); + if (rpc_clnt) + clnt_destroy(rpc_clnt); return; out_return_error: - do_error_downcall(clp->krb5_fd, uid, -1); - return; + do_error_downcall(fd, uid, downcall_err); + goto out; } -/* - * 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) +handle_krb5_upcall(struct clnt_info *clp) { uid_t uid; - AUTH *auth; - struct authgss_private_data pd; - gss_buffer_desc token; - printerr(2, "handling spkm3 upcall\n"); + if (read(clp->krb5_fd, &uid, sizeof(uid)) < (ssize_t)sizeof(uid)) { + printerr(0, "WARNING: failed reading uid from krb5 " + "upcall pipe: %s\n", strerror(errno)); + return; + } - token.length = 0; - token.value = NULL; + return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, 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)); +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; + char *enctypes = 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; } - if (create_auth_rpc_client(clp, &auth, uid, AUTHTYPE_SPKM3)) { - printerr(0, "WARNING: Failed to create spkm3 context for " - "user with uid %d\n", uid); - goto out_return_error; + /* 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; } - if (!authgss_get_private_data(auth, &pd)) { - printerr(0, "WARNING: Failed to obtain authentication " - "data for user with uid %d for server %s\n", - uid, clp->servername); - goto out_return_error; + /* read supported encryption types if supplied */ + if ((p = strstr(lbuf, "enctypes=")) != NULL) { + enctypes = malloc(lbuflen); + if (!enctypes) + goto out; + if (sscanf(p, "enctypes=%s", enctypes) != 1) { + printerr(0, "WARNING: handle_gssd_upcall: " + "failed to parse encryption types " + "in upcall string '%s'\n", lbuf); + goto out; + } + if (parse_enctypes(enctypes) != 0) { + printerr(0, "WARNING: handle_gssd_upcall: " + "parsing encryption types failed: errno %d\n", errno); + } } - if (serialize_context_for_kernel(pd.pd_ctx, &token)) { - printerr(0, "WARNING: Failed to serialize spkm3 context for " - "user with uid %d for server\n", - uid, clp->servername); - goto out_return_error; + /* 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; + } } - do_downcall(clp->spkm3_fd, uid, &pd, &token); + /* + * 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 (token.value) - free(token.value); -out: - return; + if (strcmp(mech, "krb5") == 0) + process_krb5_upcall(clp, uid, clp->gssd_fd, target, service); + else + printerr(0, "WARNING: handle_gssd_upcall: " + "received unknown gss mech '%s'\n", mech); -out_return_error: - do_error_downcall(clp->spkm3_fd, uid, -1); - return; +out: + free(lbuf); + free(mech); + free(enctypes); + free(target); + free(service); + return; } +