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=a145081e8ae234627ad2b98c1db78fbd8b25061d;hb=245fad6be5a32866b2cefad55b3e2d50f6b197af;hpb=b13f13b0f2ebdadc47eef8bf3fd4eb076e144fda diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c index a145081..aa39435 100644 --- a/utils/gssd/gssd_proc.c +++ b/utils/gssd/gssd_proc.c @@ -38,10 +38,14 @@ */ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include "config.h" + #include #include #include @@ -66,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: @@ -79,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 - * {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]; + char buf[INFOBUFLEN + 1]; static char dummy[128]; int nbytes; static char service[128]; @@ -112,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; @@ -129,6 +229,7 @@ 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: %127s\n" "service: %127s %15s version %15s\n" @@ -145,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; @@ -178,9 +286,10 @@ 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; } @@ -190,16 +299,16 @@ destroy_client(struct clnt_info *clp) if (clp->krb5_poll_index != -1) memset(&pollarray[clp->krb5_poll_index], 0, sizeof(struct pollfd)); - if (clp->spkm3_poll_index != -1) - memset(&pollarray[clp->spkm3_poll_index], 0, + 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); } @@ -214,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); @@ -227,26 +336,42 @@ 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 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->spkm3_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 ((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; } @@ -274,6 +399,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"); @@ -283,30 +417,22 @@ insert_clnt_poll(struct clnt_info *clp) pollarray[clp->krb5_poll_index].events |= POLLIN; } - if ((clp->spkm3_fd != -1) && (clp->spkm3_poll_index == -1)) { - if (get_poll_index(&clp->spkm3_poll_index)) { - printerr(0, "ERROR: Too many spkm3 clients\n"); - return -1; - } - pollarray[clp->spkm3_poll_index].fd = clp->spkm3_fd; - pollarray[clp->spkm3_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)); @@ -350,16 +476,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; } @@ -380,45 +514,127 @@ 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; +} + +/* 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; } @@ -427,7 +643,7 @@ do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, gss_buffer_desc *context_token) { char *buf = NULL, *p = NULL, *end = NULL; - unsigned int timeout = 0; /* XXX decide on a reasonable value */ + unsigned int timeout = context_timeout; unsigned int buf_size = 0; printerr(1, "doing downcall\n"); @@ -438,7 +654,6 @@ do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, 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; @@ -449,7 +664,7 @@ do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, return 0; out_err: if (buf) free(buf); - printerr(0, "Failed to write downcall!\n"); + printerr(1, "Failed to write downcall!\n"); return -1; } @@ -476,6 +691,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. @@ -491,14 +791,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(); @@ -518,13 +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; - /* XXX sec.req_flags = GSS_C_ANON_FLAG; - * Need a way to switch.... - */ - sec.req_flags = GSS_C_MUTUAL_FLAG; - } else { printerr(0, "ERROR: Invalid authentication type (%d) " "in create_auth_rpc_client\n", authtype); @@ -538,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); @@ -552,15 +843,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", @@ -568,80 +854,43 @@ 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); + 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 ((at_sign - clp->servicename) >= sizeof(service)) { - printerr(0, "WARNING: service portion of servicename (%s) " - "is too long!\n", clp->servicename); - 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); if (!auth) { /* Our caller should print appropriate message */ - printerr(2, "WARNING: Failed to create %s context for " + printerr(2, "WARNING: Failed to create krb5 context for " "user with uid %d for server %s\n", - (authtype == AUTHTYPE_KRB5 ? "krb5":"spkm3"), uid, clp->servername); goto out_fail; } @@ -655,9 +904,8 @@ 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)) { + 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); } @@ -670,40 +918,99 @@ int create_auth_rpc_client(struct clnt_info *clp, 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; CLIENT *rpc_clnt = NULL; AUTH *auth = NULL; struct authgss_private_data pd; gss_buffer_desc token; char **credlist = NULL; char **ccname; - char **dirname; + 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; 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 : ""); + 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) + /* 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; + } + 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) @@ -711,45 +1018,52 @@ 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(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; - } + 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(0, "WARNING: Failed to create krb5 context " + printerr(1, "WARNING: Failed to create krb5 context " "for user with uid %d for server %s\n", uid, clp->servername); goto out_return_error; @@ -757,26 +1071,28 @@ handle_krb5_upcall(struct clnt_info *clp) } 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, &krb5oid)) { + 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); +#ifndef HAVE_LIBTIRPC if (pd.pd_ctx_hndl.length != 0) authgss_free_private_data(&pd); +#endif if (auth) AUTH_DESTROY(auth); if (rpc_clnt) @@ -784,66 +1100,142 @@ out: return; out_return_error: - do_error_downcall(clp->krb5_fd, uid, -1); + 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; - CLIENT *rpc_clnt = NULL; - AUTH *auth = NULL; - 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, &rpc_clnt, &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, &spkm3oid)) { - 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; + } + } + + /* + * 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; + } } - do_downcall(clp->spkm3_fd, uid, &pd, &token); + 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: - if (token.value) - free(token.value); - if (auth) - AUTH_DESTROY(auth); - if (rpc_clnt) - clnt_destroy(rpc_clnt); - return; - -out_return_error: - do_error_downcall(clp->spkm3_fd, uid, -1); - goto out; + free(lbuf); + free(mech); + free(enctypes); + free(target); + free(service); + return; } +