diff options
Diffstat (limited to 'utils/gssd/gssd_proc.c')
-rw-r--r-- | utils/gssd/gssd_proc.c | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c new file mode 100644 index 0000000..1e7ebae --- /dev/null +++ b/utils/gssd/gssd_proc.c @@ -0,0 +1,661 @@ +/* + gssd_proc.c + + Copyright (c) 2000-2004 The Regents of the University of Michigan. + All rights reserved. + + Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>. + Copyright (c) 2001 Andy Adamson <andros@UMICH.EDU>. + Copyright (c) 2002 Marius Aamodt Eriksen <marius@UMICH.EDU>. + Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU> + Copyright (c) 2004 Kevin Coffman <kwc@umich.edu> + All rights reserved, all wrongs reversed. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "config.h" +#include <sys/param.h> +#include <rpc/rpc.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <stdio.h> +#include <stdlib.h> +#include <pwd.h> +#include <grp.h> +#include <string.h> +#include <dirent.h> +#include <poll.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <gssapi/gssapi.h> +#include <netdb.h> + +#include "gssd.h" +#include "err_util.h" +#include "gss_util.h" +#include "gss_oids.h" +#include "krb5_util.h" +#include "context.h" + +/* + * pollarray: + * array of struct pollfd suitable to pass to poll. initialized to + * zero - a zero struct is ignored by poll() because the events mask is 0. + * + * clnt_list: + * 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 + * + * Algorithm: + * Poll all /pipefsdir/clntXX/krb5 files. When ready, data read + * is a uid; 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. + */ + +struct pollfd * pollarray; + +int pollsize; /* the size of pollaray (in pollfd's) */ + +/* XXX buffer problems: */ +static int +read_service_info(char *info_file_name, char **servicename, char **servername, + int *prog, int *vers, char **protocol) { +#define INFOBUFLEN 256 + char buf[INFOBUFLEN]; + static char dummy[128]; + int nbytes; + static char service[128]; + static char address[128]; + char program[16]; + char version[16]; + char protoname[16]; + in_addr_t inaddr; + int fd = -1; + struct hostent *ent = NULL; + int numfields; + + *servicename = *servername = *protocol = NULL; + + if ((fd = open(info_file_name, O_RDONLY)) == -1) { + printerr(0, "ERROR: can't open %s: %s\n", info_file_name, + strerror(errno)); + goto fail; + } + if ((nbytes = read(fd, buf, INFOBUFLEN)) == -1) + goto fail; + close(fd); + + numfields = sscanf(buf,"RPC server: %s\n" + "service: %s %s version %s\n" + "address: %s\n" + "protocol: %s\n", + dummy, + service, program, version, + address, + protoname); + + if (numfields == 5) { + strcpy(protoname, "tcp"); + } else if (numfields != 6) { + goto fail; + } + + /* check service, program, and version */ + if(memcmp(service, "nfs", 3)) return -1; + *prog = atoi(program + 1); /* skip open paren */ + *vers = atoi(version); + if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4))) + 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); + goto fail; + } + if (!(*servername = calloc(strlen(ent->h_name) + 1, 1))) + 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 (!(*protocol = strdup(protoname))) + goto fail; + return 0; +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); + return -1; +} + +static void +destroy_client(struct clnt_info *clp) +{ + 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); + free(clp); +} + +static struct clnt_info * +insert_new_clnt(void) +{ + struct clnt_info *clp = NULL; + + if (!(clp = (struct clnt_info *)calloc(1,sizeof(struct clnt_info)))) { + printerr(0, "ERROR: can't malloc clnt_info: %s\n", + strerror(errno)); + goto out; + } + clp->krb5_poll_index = -1; + clp->spkm3_poll_index = -1; + clp->krb5_fd = -1; + clp->spkm3_fd = -1; + clp->dir_fd = -1; + + TAILQ_INSERT_HEAD(&clnt_list, clp, list); +out: + return clp; +} + +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)) + return -1; + snprintf(info_file_name, sizeof(info_file_name), "%s/info", + clp->dirname); + if (read_service_info(info_file_name, &clp->servicename, + &clp->servername, &clp->prog, &clp->vers, + &clp->protocol)) + return -1; + return 0; +} + +static int +get_poll_index(int *ind) +{ + int i; + + *ind = -1; + for (i=0; i<FD_ALLOC_BLOCK; i++) { + if (pollarray[i].events == 0) { + *ind = i; + break; + } + } + if (*ind == -1) { + printerr(0, "ERROR: No pollarray slots open\n"); + return -1; + } + return 0; +} + +static void +process_clnt_dir(char *dir) +{ + struct clnt_info * clp; + + if (!(clp = insert_new_clnt())) + goto fail_destroy_client; + + if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) { + goto fail_destroy_client; + } + memcpy(clp->dirname, dir, strlen(dir)); + if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) { + printerr(0, "ERROR: can't open %s: %s\n", + clp->dirname, strerror(errno)); + goto fail_destroy_client; + } + fcntl(clp->dir_fd, F_SETSIG, DNOTIFY_SIGNAL); + fcntl(clp->dir_fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_MULTISHOT); + + 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; + } + + return; + +fail_destroy_client: + if (clp) { + TAILQ_REMOVE(&clnt_list, clp, list); + destroy_client(clp); + } +fail_keep_client: + /* We couldn't find some subdirectories, but we keep the client + * around in case we get a notification on the directory when the + * subdirectories are created. */ + return; +} + +void +init_client_list(void) +{ + TAILQ_INIT(&clnt_list); + /* Eventually plan to grow/shrink poll array: */ + pollsize = FD_ALLOC_BLOCK; + pollarray = calloc(pollsize, sizeof(struct pollfd)); +} + +static void +destroy_client_list(void) +{ + struct clnt_info *clp; + + printerr(1, "processing client list\n"); + + while (clnt_list.tqh_first != NULL) { + clp = clnt_list.tqh_first; + TAILQ_REMOVE(&clnt_list, clp, list); + destroy_client(clp); + } +} + +/* Used to read (and re-read) list of clients, set up poll array. */ +int +update_client_list(void) +{ + struct dirent **namelist; + int i,j; + + destroy_client_list(); + + if (chdir(pipefsdir) < 0) { + printerr(0, "ERROR: can't chdir to %s: %s\n", + pipefsdir, strerror(errno)); + return -1; + } + + memset(pollarray, 0, pollsize * sizeof(struct pollfd)); + + j = scandir(pipefsdir, &namelist, NULL, alphasort); + if (j < 0) { + printerr(0, "ERROR: can't scandir %s: %s\n", + pipefsdir, strerror(errno)); + return -1; + } + 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); + free(namelist[i]); + } + + free(namelist); + return 0; +} + +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 */ + + printerr(1, "doing downcall\n"); + + if (WRITE_BYTES(&p, end, uid)) return -1; + /* Not setting any timeout for now: */ + if (WRITE_BYTES(&p, end, timeout)) return -1; + if (WRITE_BYTES(&p, end, pd->pd_seq_win)) return -1; + if (write_buffer(&p, end, &pd->pd_ctx_hndl)) return -1; + if (write_buffer(&p, end, context_token)) return -1; + + if (write(k5_fd, buf, p - buf) < p - buf) return -1; + return 0; +} + +static int +do_error_downcall(int k5_fd, uid_t uid, int err) +{ + char buf[1024]; + char *p = buf, *end = buf + 1024; + unsigned int timeout = 0; + int zero = 0; + + printerr(1, "doing error downcall\n"); + + if (WRITE_BYTES(&p, end, uid)) return -1; + if (WRITE_BYTES(&p, end, timeout)) return -1; + /* use seq_win = 0 to indicate an error: */ + if (WRITE_BYTES(&p, end, zero)) return -1; + if (WRITE_BYTES(&p, end, err)) return -1; + + if (write(k5_fd, buf, p - buf) < p - buf) return -1; + return 0; + +} + +/* + * Create an RPC connection and establish an authenticated + * gss context with a server. + */ +int create_auth_rpc_client(struct clnt_info *clp, + AUTH **auth_return, + uid_t uid, + int authtype) +{ + CLIENT *rpc_clnt = NULL; + struct rpc_gss_sec sec; + AUTH *auth = NULL; + uid_t save_uid = -1; + int retval = -1; + OM_uint32 min_stat; + + sec.qop = GSS_C_QOP_DEFAULT; + sec.svc = RPCSEC_GSS_SVC_NONE; + sec.cred = GSS_C_NO_CREDENTIAL; + sec.req_flags = 0; + if (authtype == AUTHTYPE_KRB5) { + 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); + goto out_fail; + } + + + if (authtype == AUTHTYPE_KRB5) { +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES + /* + * Do this before creating rpc connection since we won't need + * rpc connection if it fails! + */ + if (limit_krb5_enctypes(&sec, uid)) { + printerr(1, "WARNING: Failed while limiting krb5 " + "encryption types for user with uid %d\n", + uid); + goto out_fail; + } +#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); + goto out_fail; + } + + 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 krb5 context for " + "user with uid %d for server %s\n", + uid, clp->servername); + 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 !!! */ + *auth_return = auth; + retval = 0; + + out_fail: + if (sec.cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&min_stat, &sec.cred); + if (rpc_clnt) clnt_destroy(rpc_clnt); + + return retval; +} + + +/* + * 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) +{ + uid_t uid; + AUTH *auth; + struct authgss_private_data pd; + gss_buffer_desc token; + char **credlist = NULL; + char **ccname; + + printerr(1, "handling krb5 upcall\n"); + + 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; + } + 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; + } + } + 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", + uid, clp->servername); + goto out_return_error; + } + } + + 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; + } + + if (serialize_context_for_kernel(pd.pd_ctx, &token)) { + 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); + + if (token.value) + free(token.value); +out: + return; + +out_return_error: + do_error_downcall(clp->krb5_fd, uid, -1); + return; +} + +/* + * 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) +{ + uid_t uid; + AUTH *auth; + struct authgss_private_data pd; + gss_buffer_desc token; + + printerr(2, "handling spkm3 upcall\n"); + + 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, &auth, uid, AUTHTYPE_SPKM3)) { + printerr(0, "WARNING: Failed to create spkm3 context for " + "user with uid %d\n", uid); + goto out_return_error; + } + + 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; + } + + 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; + } + + do_downcall(clp->spkm3_fd, uid, &pd, &token); + + if (token.value) + free(token.value); +out: + return; + +out_return_error: + do_error_downcall(clp->spkm3_fd, uid, -1); + return; +} |