summaryrefslogtreecommitdiffstats
path: root/utils/gssd/gssd.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/gssd/gssd.c')
-rw-r--r--utils/gssd/gssd.c1320
1 files changed, 1320 insertions, 0 deletions
diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
new file mode 100644
index 0000000..ca9b326
--- /dev/null
+++ b/utils/gssd/gssd.c
@@ -0,0 +1,1320 @@
+/*
+ gssd.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) 2002 Andy Adamson <andros@UMICH.EDU>.
+ Copyright (c) 2002 Marius Aamodt Eriksen <marius@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.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/inotify.h>
+#include <rpc/rpc.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <memory.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <netdb.h>
+#include <event2/event.h>
+
+#include "gssd.h"
+#include "err_util.h"
+#include "gss_util.h"
+#include "krb5_util.h"
+#include "nfslib.h"
+#include "conffile.h"
+
+static char *pipefs_path = GSSD_PIPEFS_DIR;
+static DIR *pipefs_dir;
+static int pipefs_fd;
+static int inotify_fd;
+struct event *inotify_ev;
+
+char *keytabfile = GSSD_DEFAULT_KEYTAB_FILE;
+char **ccachesearch;
+int use_memcache = 0;
+int root_uses_machine_creds = 1;
+unsigned int context_timeout = 0;
+unsigned int rpc_timeout = 5;
+char *preferred_realm = NULL;
+char *ccachedir = NULL;
+/* set $HOME to "/" by default */
+static bool set_home = true;
+/* Avoid DNS reverse lookups on server names */
+static bool avoid_dns = true;
+static bool use_gssproxy = false;
+pthread_mutex_t clp_lock = PTHREAD_MUTEX_INITIALIZER;
+static bool signal_received = false;
+static struct event_base *evbase = NULL;
+
+int upcall_timeout = DEF_UPCALL_TIMEOUT;
+static bool cancel_timed_out_upcalls = false;
+
+TAILQ_HEAD(topdir_list_head, topdir) topdir_list;
+
+/*
+ * active_thread_list:
+ *
+ * used to track upcalls for timeout purposes.
+ *
+ * protected by the active_thread_list_lock mutex.
+ *
+ * upcall_thread_info structures are added to the tail of the list
+ * by start_upcall_thread(), so entries closer to the head of the list
+ * will be closer to hitting the upcall timeout.
+ *
+ * upcall_thread_info structures are removed from the list upon a
+ * sucessful join of the upcall thread by the watchdog thread (via
+ * scan_active_thread_list().
+ */
+TAILQ_HEAD(active_thread_list_head, upcall_thread_info) active_thread_list;
+pthread_mutex_t active_thread_list_lock = PTHREAD_MUTEX_INITIALIZER;
+
+struct topdir {
+ TAILQ_ENTRY(topdir) list;
+ TAILQ_HEAD(clnt_list_head, clnt_info) clnt_list;
+ int wd;
+ char name[];
+};
+
+/*
+ * topdir_list:
+ * linked list of struct topdir with basic data about a topdir.
+ *
+ * clnt_list:
+ * linked list of struct clnt_info with basic data about a clntXXX dir,
+ * one per topdir.
+ *
+ * Directory structure: created by the kernel
+ * {rpc_pipefs}/{topdir}/clntXX : one per rpc_clnt struct in the kernel
+ * {rpc_pipefs}/{topdir}/clntXX/krb5 : read uid for which kernel wants
+ * a context, write the resulting context
+ * {rpc_pipefs}/{topdir}/clntXX/info : stores info such as server name
+ * {rpc_pipefs}/{topdir}/clntXX/gssd : pipe for all gss mechanisms using
+ * a text-based string of parameters
+ *
+ * Algorithm:
+ * Poll all {rpc_pipefs}/{topdir}/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 {rpc_pipefs} or in any of the clntXX directories,
+ * and rescan the whole {rpc_pipefs} when this happens.
+ */
+
+/*
+ * 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 bool
+gssd_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 false;
+ }
+
+#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);
+ nfs_freeaddrinfo(res);
+ return false;
+ }
+ }
+#endif /* IPV6_SUPPORTED */
+
+ memcpy(sa, res->ai_addr, res->ai_addrlen);
+ nfs_freeaddrinfo(res);
+ return true;
+}
+
+/*
+ * convert a sockaddr to a hostname
+ */
+static char *
+gssd_get_servername(const char *name, const struct sockaddr *sa, const char *addr)
+{
+ socklen_t addrlen;
+ int err;
+ char hbuf[NI_MAXHOST];
+ unsigned char buf[sizeof(struct in6_addr)];
+
+ while (avoid_dns) {
+ /*
+ * Determine if this is a server name, or an IP address.
+ * If it is an IP address, do the DNS lookup otherwise
+ * skip the DNS lookup.
+ */
+ if (strchr(name, '.') == NULL)
+ break; /* local name */
+ else if (inet_pton(AF_INET, name, buf) == 1)
+ break; /* IPv4 address */
+ else if (inet_pton(AF_INET6, name, buf) == 1)
+ break; /* IPv6 addrss */
+
+ return strdup(name);
+ }
+
+ 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(errno) :
+ gai_strerror(err));
+ return NULL;
+ }
+
+ return strdup(hbuf);
+}
+
+static void
+gssd_read_service_info(int dirfd, struct clnt_info *clp)
+{
+ int fd;
+ FILE *info = NULL;
+ int numfields;
+ char *server = NULL;
+ char *service = NULL;
+ int program;
+ int version;
+ char *address = NULL;
+ char *protoname = NULL;
+ char *port = NULL;
+ char *servername = NULL;
+
+ fd = openat(dirfd, "info", O_RDONLY);
+ if (fd < 0) {
+ printerr(0, "ERROR: can't open %s/info: %s\n",
+ clp->relpath, strerror(errno));
+ goto fail;
+ }
+
+ info = fdopen(fd, "r");
+ if (!info) {
+ printerr(0, "ERROR: can't fdopen %s/info: %s\n",
+ clp->relpath, strerror(errno));
+ close(fd);
+ goto fail;
+ }
+
+ /*
+ * Some history:
+ *
+ * The first three lines were added with rpc_pipefs in 2003-01-13.
+ * (commit af2f003391786fb632889c02142c941b212ba4ff)
+ *
+ * The 'protocol' line was added in 2003-06-11.
+ * (commit 9bd741ae48785d0c0e75cf906ff66f893d600c2d)
+ *
+ * The 'port' line was added in 2007-09-26.
+ * (commit bf19aacecbeebccb2c3d150a8bd9416b7dba81fe)
+ */
+ numfields = fscanf(info,
+ "RPC server: %ms\n"
+ "service: %ms (%d) version %d\n"
+ "address: %ms\n"
+ "protocol: %ms\n"
+ "port: %ms\n",
+ &server,
+ &service, &program, &version,
+ &address,
+ &protoname,
+ &port);
+
+
+ switch (numfields) {
+ case 5:
+ protoname = strdup("tcp");
+ if (!protoname)
+ goto fail;
+ /* fall through */
+ case 6:
+ /* fall through */
+ case 7:
+ break;
+ default:
+ goto fail;
+ }
+
+ /*
+ * The user space RPC library has no support for
+ * RPC-over-RDMA at this time, so change 'rdma'
+ * to 'tcp', and '20049' to '2049'.
+ */
+ if (strcmp(protoname, "rdma") == 0) {
+ free(protoname);
+ protoname = strdup("tcp");
+ if (!protoname)
+ goto fail;
+ free(port);
+ port = strdup("2049");
+ if (!port)
+ goto fail;
+ }
+
+ if (!gssd_addrstr_to_sockaddr((struct sockaddr *)&clp->addr,
+ address, port ? port : ""))
+ goto fail;
+
+ servername = gssd_get_servername(server, (struct sockaddr *)&clp->addr, address);
+ if (!servername)
+ goto fail;
+
+ if (asprintf(&clp->servicename, "%s@%s", service, servername) < 0)
+ goto fail;
+
+ clp->servername = servername;
+ clp->prog = program;
+ clp->vers = version;
+ clp->protocol = protoname;
+
+ goto out;
+
+fail:
+ printerr(0, "ERROR: failed to parse %s/info\n", clp->relpath);
+ clp->upcall_address = strdup(address);
+ clp->upcall_port = strdup(port);
+ clp->upcall_program = program;
+ clp->upcall_vers = version;
+ clp->upcall_protoname = strdup(protoname);
+ clp->upcall_service = strdup(service);
+ free(servername);
+ free(protoname);
+ clp->servicename = NULL;
+ clp->servername = NULL;
+ clp->prog = 0;
+ clp->vers = 0;
+ clp->protocol = NULL;
+out:
+ if (info)
+ fclose(info);
+
+ free(server);
+ free(service);
+ free(address);
+ free(port);
+}
+
+/* Actually frees clp and fields that might be used from other
+ * threads if was last reference.
+ */
+void
+gssd_free_client(struct clnt_info *clp)
+{
+ int refcnt;
+
+ pthread_mutex_lock(&clp_lock);
+ refcnt = --clp->refcount;
+ pthread_mutex_unlock(&clp_lock);
+ if (refcnt > 0)
+ return;
+
+ printerr(4, "freeing client %s\n", clp->relpath);
+
+ if (clp->krb5_fd >= 0)
+ close(clp->krb5_fd);
+
+ if (clp->gssd_fd >= 0)
+ close(clp->gssd_fd);
+
+ free(clp->relpath);
+ free(clp->servicename);
+ free(clp->servername);
+ free(clp->protocol);
+ if (!clp->servername) {
+ if (clp->upcall_address)
+ free(clp->upcall_address);
+ if (clp->upcall_port)
+ free(clp->upcall_port);
+ if (clp->upcall_protoname)
+ free(clp->upcall_protoname);
+ if (clp->upcall_service)
+ free(clp->upcall_service);
+ }
+ free(clp);
+}
+
+/* Called when removing from clnt_list to tear down event handling.
+ * Will then free clp if was last reference.
+ */
+static void
+gssd_destroy_client(struct clnt_info *clp)
+{
+ printerr(4, "destroying client %s\n", clp->relpath);
+
+ if (clp->krb5_ev) {
+ event_del(clp->krb5_ev);
+ event_free(clp->krb5_ev);
+ clp->krb5_ev = NULL;
+ }
+
+ if (clp->gssd_ev) {
+ event_del(clp->gssd_ev);
+ event_free(clp->gssd_ev);
+ clp->gssd_ev = NULL;
+ }
+
+ inotify_rm_watch(inotify_fd, clp->wd);
+ gssd_free_client(clp);
+}
+
+static void gssd_scan(void);
+
+/* For each upcall read the upcall info into the buffer, then create a
+ * thread in a detached state so that resources are released back into
+ * the system without the need for a join.
+ */
+static void
+gssd_clnt_gssd_cb(int UNUSED(fd), short UNUSED(which), void *data)
+{
+ struct clnt_info *clp = data;
+
+ /* if there was a failure to translate IP to name for this server,
+ * try again
+ */
+ if (!clp->servername) {
+ if (!gssd_addrstr_to_sockaddr((struct sockaddr *)&clp->addr,
+ clp->upcall_address, clp->upcall_port ?
+ clp->upcall_port : "")) {
+ goto do_upcall;
+ }
+ clp->servername = gssd_get_servername(clp->upcall_address,
+ (struct sockaddr *)&clp->addr, clp->upcall_address);
+ if (!clp->servername)
+ goto do_upcall;
+
+ if (asprintf(&clp->servicename, "%s@%s", clp->upcall_service,
+ clp->servername) < 0) {
+ free(clp->servername);
+ clp->servername = NULL;
+ goto do_upcall;
+ }
+ clp->prog = clp->upcall_program;
+ clp->vers = clp->upcall_vers;
+ clp->protocol = strdup(clp->upcall_protoname);
+ }
+do_upcall:
+ handle_gssd_upcall(clp);
+}
+
+static void
+gssd_clnt_krb5_cb(int UNUSED(fd), short UNUSED(which), void *data)
+{
+ struct clnt_info *clp = data;
+
+ handle_krb5_upcall(clp);
+}
+
+/*
+ * scan_active_thread_list:
+ *
+ * Walks the active_thread_list, trying to join as many upcall threads as
+ * possible. For threads that have terminated, the corresponding
+ * upcall_thread_info will be removed from the list and freed. Threads that
+ * are still busy and have exceeded the upcall_timeout will cause an error to
+ * be logged and may be canceled (depending on the value of
+ * cancel_timed_out_upcalls).
+ *
+ * Returns the number of seconds that the watchdog thread should wait before
+ * calling scan_active_thread_list() again.
+ */
+static int
+scan_active_thread_list(void)
+{
+ struct upcall_thread_info *info;
+ struct timespec now;
+ unsigned int sleeptime;
+ bool sleeptime_set = false;
+ int err;
+ void *tret, *saveprev;
+
+ sleeptime = upcall_timeout;
+ pthread_mutex_lock(&active_thread_list_lock);
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ TAILQ_FOREACH(info, &active_thread_list, list) {
+ err = pthread_tryjoin_np(info->tid, &tret);
+ switch (err) {
+ case 0:
+ /*
+ * The upcall thread has either completed successfully, or
+ * has been canceled _and_ has acted on the cancellation request
+ * (i.e. has hit a cancellation point). We can now remove the
+ * upcall_thread_info from the list and free it.
+ */
+ if (tret == PTHREAD_CANCELED)
+ printerr(2, "watchdog: thread id 0x%lx cancelled successfully\n",
+ info->tid);
+ saveprev = info->list.tqe_prev;
+ TAILQ_REMOVE(&active_thread_list, info, list);
+ free(info);
+ info = saveprev;
+ break;
+ case EBUSY:
+ /*
+ * The upcall thread is still running. If the timeout has expired
+ * then we either cancel the thread, log an error, and do an error
+ * downcall to the kernel (cancel_timed_out_upcalls=true) or simply
+ * log an error (cancel_timed_out_upcalls=false). In either case,
+ * the error is logged only once.
+ */
+ if (now.tv_sec >= info->timeout.tv_sec) {
+ if (cancel_timed_out_upcalls && !(info->flags & UPCALL_THREAD_CANCELED)) {
+ printerr(0, "watchdog: thread id 0x%lx timed out\n",
+ info->tid);
+ pthread_cancel(info->tid);
+ info->flags |= (UPCALL_THREAD_CANCELED|UPCALL_THREAD_WARNED);
+ do_error_downcall(info->fd, info->uid, -ETIMEDOUT);
+ } else {
+ if (!(info->flags & UPCALL_THREAD_WARNED)) {
+ printerr(0, "watchdog: thread id 0x%lx running for %ld seconds\n",
+ info->tid,
+ now.tv_sec - info->timeout.tv_sec + upcall_timeout);
+ info->flags |= UPCALL_THREAD_WARNED;
+ }
+ }
+ } else if (!sleeptime_set) {
+ /*
+ * The upcall thread is still running, but the timeout has not yet
+ * expired. Calculate the time remaining until the timeout will
+ * expire. This is the amount of time the watchdog thread will
+ * wait before running again. We only need to do this for the busy
+ * thread closest to the head of the list - entries appearing later
+ * in the list will time out later.
+ */
+ sleeptime = info->timeout.tv_sec - now.tv_sec;
+ sleeptime_set = true;
+ }
+ break;
+ default:
+ /* EDEADLK, EINVAL, and ESRCH... none of which should happen! */
+ printerr(0, "watchdog: attempt to join thread id 0x%lx returned %d (%s)!\n",
+ info->tid, err, strerror(err));
+ break;
+ }
+ }
+ pthread_mutex_unlock(&active_thread_list_lock);
+
+ return sleeptime;
+}
+
+static void *
+watchdog_thread_fn(void *UNUSED(arg))
+{
+ unsigned int sleeptime;
+
+ for (;;) {
+ sleeptime = scan_active_thread_list();
+ printerr(4, "watchdog: sleeping %u secs\n", sleeptime);
+ sleep(sleeptime);
+ }
+ return (void *)0;
+}
+
+static int
+start_watchdog_thread(void)
+{
+ pthread_attr_t attr;
+ pthread_t th;
+ int ret;
+
+ ret = pthread_attr_init(&attr);
+ if (ret != 0) {
+ printerr(0, "ERROR: failed to init pthread attr: ret %d: %s\n",
+ ret, strerror(errno));
+ return ret;
+ }
+ ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (ret != 0) {
+ printerr(0, "ERROR: failed to create pthread attr: ret %d: %s\n",
+ ret, strerror(errno));
+ return ret;
+ }
+ ret = pthread_create(&th, &attr, watchdog_thread_fn, NULL);
+ if (ret != 0) {
+ printerr(0, "ERROR: pthread_create failed: ret %d: %s\n",
+ ret, strerror(errno));
+ }
+ return ret;
+}
+
+static struct clnt_info *
+gssd_get_clnt(struct topdir *tdi, const char *name)
+{
+ struct clnt_info *clp;
+
+ TAILQ_FOREACH(clp, &tdi->clnt_list, list)
+ if (!strcmp(clp->name, name))
+ return clp;
+
+ printerr(4, "creating client %s/%s\n", tdi->name, name);
+
+ clp = calloc(1, sizeof(struct clnt_info));
+ if (!clp) {
+ printerr(0, "ERROR: can't malloc clnt_info: %s\n",
+ strerror(errno));
+ return NULL;
+ }
+
+ if (asprintf(&clp->relpath, "%s/%s", tdi->name, name) < 0) {
+ clp->relpath = NULL;
+ goto out;
+ }
+
+ clp->wd = inotify_add_watch(inotify_fd, clp->relpath, IN_CREATE | IN_DELETE);
+ if (clp->wd < 0) {
+ if (errno != ENOENT)
+ printerr(0, "ERROR: %s: inotify_add_watch failed for %s: %s\n",
+ __FUNCTION__, clp->relpath, strerror(errno));
+ goto out;
+ }
+
+ clp->name = clp->relpath + strlen(tdi->name) + 1;
+ clp->krb5_fd = -1;
+ clp->gssd_fd = -1;
+ clp->refcount = 1;
+
+ TAILQ_INSERT_HEAD(&tdi->clnt_list, clp, list);
+ return clp;
+
+out:
+ free(clp->relpath);
+ free(clp);
+ return NULL;
+}
+
+static int
+gssd_scan_clnt(struct clnt_info *clp)
+{
+ int clntfd;
+
+ printerr(4, "scanning client %s\n", clp->relpath);
+
+ clntfd = openat(pipefs_fd, clp->relpath, O_RDONLY);
+ if (clntfd < 0) {
+ if (errno != ENOENT)
+ printerr(0, "ERROR: %s: can't openat %s: %s\n",
+ __FUNCTION__, clp->relpath, strerror(errno));
+ return -1;
+ }
+
+ if (clp->gssd_fd == -1)
+ clp->gssd_fd = openat(clntfd, "gssd", O_RDWR | O_NONBLOCK);
+
+ if (clp->gssd_fd == -1 && clp->krb5_fd == -1)
+ clp->krb5_fd = openat(clntfd, "krb5", O_RDWR | O_NONBLOCK);
+
+ if (!clp->gssd_ev && clp->gssd_fd >= 0) {
+ clp->gssd_ev = event_new(evbase, clp->gssd_fd, EV_READ | EV_PERSIST,
+ gssd_clnt_gssd_cb, clp);
+ if (!clp->gssd_ev) {
+ printerr(0, "ERROR: %s: can't create gssd event for %s: %s\n",
+ __FUNCTION__, clp->relpath, strerror(errno));
+ close(clp->gssd_fd);
+ clp->gssd_fd = -1;
+ } else {
+ event_add(clp->gssd_ev, NULL);
+ }
+ }
+
+ if (!clp->krb5_ev && clp->krb5_fd >= 0) {
+ clp->krb5_ev = event_new(evbase, clp->krb5_fd, EV_READ | EV_PERSIST,
+ gssd_clnt_krb5_cb, clp);
+ if (!clp->krb5_ev) {
+ printerr(0, "ERROR: %s: can't create krb5 event for %s: %s\n",
+ __FUNCTION__, clp->relpath, strerror(errno));
+ close(clp->krb5_fd);
+ clp->krb5_fd = -1;
+ } else {
+ event_add(clp->krb5_ev, NULL);
+ }
+ }
+
+ if (clp->krb5_fd == -1 && clp->gssd_fd == -1)
+ /* not fatal, files might appear later */
+ goto out;
+
+ if (clp->prog == 0)
+ gssd_read_service_info(clntfd, clp);
+
+out:
+ close(clntfd);
+ clp->scanned = true;
+ return 0;
+}
+
+static int
+gssd_create_clnt(struct topdir *tdi, const char *name)
+{
+ struct clnt_info *clp;
+
+ clp = gssd_get_clnt(tdi, name);
+ if (!clp)
+ return -1;
+
+ return gssd_scan_clnt(clp);
+}
+
+static struct topdir *
+gssd_get_topdir(const char *name)
+{
+ struct topdir *tdi;
+
+ TAILQ_FOREACH(tdi, &topdir_list, list)
+ if (!strcmp(tdi->name, name))
+ return tdi;
+
+ tdi = malloc(sizeof(*tdi) + strlen(name) + 1);
+ if (!tdi) {
+ printerr(0, "ERROR: Couldn't allocate struct topdir\n");
+ return NULL;
+ }
+
+ tdi->wd = inotify_add_watch(inotify_fd, name, IN_CREATE);
+ if (tdi->wd < 0) {
+ printerr(0, "ERROR: %s: inotify_add_watch failed for top dir %s: %s\n",
+ __FUNCTION__, tdi->name, strerror(errno));
+ free(tdi);
+ return NULL;
+ }
+
+ strcpy(tdi->name, name);
+ TAILQ_INIT(&tdi->clnt_list);
+
+ TAILQ_INSERT_HEAD(&topdir_list, tdi, list);
+ return tdi;
+}
+
+static void
+gssd_scan_topdir(const char *name)
+{
+ struct topdir *tdi;
+ int dfd;
+ DIR *dir;
+ struct clnt_info *clp;
+ struct dirent *d;
+
+ tdi = gssd_get_topdir(name);
+ if (!tdi)
+ return;
+
+ dfd = openat(pipefs_fd, tdi->name, O_RDONLY);
+ if (dfd < 0) {
+ if (errno != ENOENT)
+ printerr(0, "ERROR: %s: can't openat %s: %s\n",
+ __FUNCTION__, tdi->name, strerror(errno));
+ return;
+ }
+
+ dir = fdopendir(dfd);
+ if (!dir) {
+ printerr(0, "ERROR: can't fdopendir %s: %s\n",
+ tdi->name, strerror(errno));
+ return;
+ }
+
+ TAILQ_FOREACH(clp, &tdi->clnt_list, list)
+ clp->scanned = false;
+
+ while ((d = readdir(dir))) {
+ if (d->d_type != DT_DIR)
+ continue;
+
+ if (strncmp(d->d_name, "clnt", strlen("clnt")))
+ continue;
+
+ gssd_create_clnt(tdi, d->d_name);
+ }
+
+ closedir(dir);
+
+ TAILQ_FOREACH(clp, &tdi->clnt_list, list) {
+ void *saveprev;
+
+ if (clp->scanned)
+ continue;
+
+ printerr(3, "orphaned client %s\n", clp->relpath);
+ saveprev = clp->list.tqe_prev;
+ TAILQ_REMOVE(&tdi->clnt_list, clp, list);
+ gssd_destroy_client(clp);
+ clp = saveprev;
+ }
+}
+
+static void
+gssd_scan(void)
+{
+ struct dirent *d;
+
+ printerr(4, "doing a full rescan\n");
+ rewinddir(pipefs_dir);
+
+ while ((d = readdir(pipefs_dir))) {
+ if (d->d_type != DT_DIR)
+ continue;
+
+ if (d->d_name[0] == '.')
+ continue;
+
+ gssd_scan_topdir(d->d_name);
+ }
+
+ if (TAILQ_EMPTY(&topdir_list)) {
+ printerr(0, "ERROR: the rpc_pipefs directory is empty!\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+gssd_scan_cb(int UNUSED(fd), short UNUSED(which), void *UNUSED(data))
+{
+ gssd_scan();
+}
+
+static bool
+gssd_inotify_topdir(struct topdir *tdi, const struct inotify_event *ev)
+{
+ printerr(5, "inotify event for topdir (%s) - "
+ "ev->wd (%d) ev->name (%s) ev->mask (0x%08x)\n",
+ tdi->name, ev->wd, ev->len > 0 ? ev->name : "<?>", ev->mask);
+
+ if (ev->mask & IN_IGNORED) {
+ printerr(0, "ERROR: topdir disappeared!\n");
+ return false;
+ }
+
+ if (ev->len == 0)
+ return false;
+
+ if (ev->mask & IN_CREATE) {
+ if (!(ev->mask & IN_ISDIR))
+ return true;
+
+ if (strncmp(ev->name, "clnt", strlen("clnt")))
+ return true;
+
+ if (gssd_create_clnt(tdi, ev->name))
+ return false;
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+gssd_inotify_clnt(struct topdir *tdi, struct clnt_info *clp, const struct inotify_event *ev)
+{
+ printerr(5, "inotify event for clntdir (%s) - "
+ "ev->wd (%d) ev->name (%s) ev->mask (0x%08x)\n",
+ clp->relpath, ev->wd, ev->len > 0 ? ev->name : "<?>", ev->mask);
+
+ if (ev->mask & IN_IGNORED) {
+ TAILQ_REMOVE(&tdi->clnt_list, clp, list);
+ gssd_destroy_client(clp);
+ return true;
+ }
+
+ if (ev->len == 0)
+ return false;
+
+ if (ev->mask & IN_CREATE) {
+ if (!strcmp(ev->name, "gssd") ||
+ !strcmp(ev->name, "krb5") ||
+ !strcmp(ev->name, "info"))
+ if (gssd_scan_clnt(clp))
+ return false;
+
+ return true;
+
+ } else if (ev->mask & IN_DELETE) {
+ if (!strcmp(ev->name, "gssd") && clp->gssd_fd >= 0) {
+ close(clp->gssd_fd);
+ event_del(clp->gssd_ev);
+ event_free(clp->gssd_ev);
+ clp->gssd_ev = NULL;
+ clp->gssd_fd = -1;
+
+ } else if (!strcmp(ev->name, "krb5") && clp->krb5_fd >= 0) {
+ close(clp->krb5_fd);
+ event_del(clp->krb5_ev);
+ event_free(clp->krb5_ev);
+ clp->krb5_ev = NULL;
+ clp->krb5_fd = -1;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static void
+gssd_inotify_cb(int ifd, short UNUSED(which), void *UNUSED(data))
+{
+ bool rescan = false;
+ struct topdir *tdi;
+ struct clnt_info *clp;
+
+ while (true) {
+ char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
+ const struct inotify_event *ev;
+ ssize_t len;
+ char *ptr;
+
+ len = read(ifd, buf, sizeof(buf));
+ if (len == -1 && errno == EINTR)
+ continue;
+
+ if (len <= 0)
+ break;
+
+ for (ptr = buf; ptr < buf + len;
+ ptr += sizeof(struct inotify_event) + ev->len) {
+ ev = (const struct inotify_event *)ptr;
+
+ if (ev->mask & IN_Q_OVERFLOW) {
+ printerr(0, "ERROR: inotify queue overflow\n");
+ rescan = true;
+ break;
+ }
+
+ TAILQ_FOREACH(tdi, &topdir_list, list) {
+ if (tdi->wd == ev->wd) {
+ if (!gssd_inotify_topdir(tdi, ev))
+ rescan = true;
+ goto found;
+ }
+
+ TAILQ_FOREACH(clp, &tdi->clnt_list, list) {
+ if (clp->wd == ev->wd) {
+ if (!gssd_inotify_clnt(tdi, clp, ev))
+ rescan = true;
+ goto found;
+ }
+ }
+ }
+
+found:
+ if (!tdi) {
+ printerr(5, "inotify event for unknown wd!!! - "
+ "ev->wd (%d) ev->name (%s) ev->mask (0x%08x)\n",
+ ev->wd, ev->len > 0 ? ev->name : "<?>", ev->mask);
+ rescan = true;
+ }
+ }
+ }
+
+ if (rescan)
+ gssd_scan();
+}
+
+static void
+sig_die(int signal)
+{
+ if (signal_received) {
+ gssd_destroy_krb5_principals(root_uses_machine_creds);
+ printerr(1, "forced exiting on signal %d\n", signal);
+ exit(0);
+ }
+
+ signal_received = true;
+ printerr(1, "exiting on signal %d\n", signal);
+ event_base_loopexit(evbase, NULL);
+}
+
+static void
+usage(char *progname)
+{
+ fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm] [-D] [-H] [-U upcall timeout] [-C]\n",
+ progname);
+ exit(1);
+}
+
+inline static void
+read_gss_conf(void)
+{
+ char *s;
+
+ conf_init_file(NFS_CONFFILE);
+ use_memcache = conf_get_bool("gssd", "use-memcache", use_memcache);
+ root_uses_machine_creds = conf_get_bool("gssd", "use-machine-creds",
+ root_uses_machine_creds);
+ avoid_dns = conf_get_bool("gssd", "avoid-dns", avoid_dns);
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+ limit_to_legacy_enctypes = conf_get_bool("gssd", "limit-to-legacy-enctypes",
+ limit_to_legacy_enctypes);
+#endif
+ context_timeout = conf_get_num("gssd", "context-timeout", context_timeout);
+ rpc_timeout = conf_get_num("gssd", "rpc-timeout", rpc_timeout);
+ upcall_timeout = conf_get_num("gssd", "upcall-timeout", upcall_timeout);
+ cancel_timed_out_upcalls = conf_get_bool("gssd", "cancel-timed-out-upcalls",
+ cancel_timed_out_upcalls);
+ s = conf_get_str("gssd", "pipefs-directory");
+ if (!s)
+ s = conf_get_str("general", "pipefs-directory");
+ else
+ printerr(0, "WARNING: Specifying pipefs-directory in the [gssd] "
+ "section of %s is deprecated. Use the [general] "
+ "section instead.", NFS_CONFFILE);
+ if (s)
+ pipefs_path = s;
+ s = conf_get_str("gssd", "keytab-file");
+ if (s)
+ keytabfile = s;
+ s = conf_get_str("gssd", "cred-cache-directory");
+ if (s)
+ ccachedir = strdup(s);
+ s = conf_get_str("gssd", "preferred-realm");
+ if (s)
+ preferred_realm = s;
+
+ use_gssproxy = conf_get_bool("gssd", "use-gss-proxy", use_gssproxy);
+ set_home = conf_get_bool("gssd", "set-home", set_home);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fg = 0;
+ int verbosity = 0;
+ int rpc_verbosity = 0;
+ int opt;
+ int i;
+ int rc;
+ extern char *optarg;
+ char *progname;
+ struct event *sighup_ev;
+
+ read_gss_conf();
+
+ verbosity = conf_get_num("gssd", "verbosity", verbosity);
+ rpc_verbosity = conf_get_num("gssd", "rpc-verbosity", rpc_verbosity);
+
+ while ((opt = getopt(argc, argv, "HDfvrlmnMp:k:d:t:T:R:U:C")) != -1) {
+ switch (opt) {
+ case 'f':
+ fg = 1;
+ break;
+ case 'm':
+ /* Accept but ignore this. Now the default. */
+ break;
+ case 'M':
+ use_memcache = 1;
+ break;
+ case 'n':
+ root_uses_machine_creds = 0;
+ break;
+ case 'v':
+ verbosity++;
+ break;
+ case 'r':
+ rpc_verbosity++;
+ break;
+ case 'p':
+ pipefs_path = optarg;
+ break;
+ case 'k':
+ keytabfile = optarg;
+ break;
+ case 'd':
+ free(ccachedir);
+ ccachedir = strdup(optarg);
+ break;
+ case 't':
+ context_timeout = atoi(optarg);
+ break;
+ case 'T':
+ rpc_timeout = atoi(optarg);
+ break;
+ case 'R':
+ preferred_realm = strdup(optarg);
+ break;
+ case 'l':
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+ limit_to_legacy_enctypes = 1;
+#else
+ errx(1, "Encryption type limits not supported by Kerberos libraries.");
+#endif
+ break;
+ case 'D':
+ avoid_dns = false;
+ break;
+ case 'H':
+ set_home = false;
+ break;
+ case 'U':
+ upcall_timeout = atoi(optarg);
+ break;
+ case 'C':
+ cancel_timed_out_upcalls = true;
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ /*
+ * Some krb5 routines try to scrape info out of files in the user's
+ * home directory. This can easily deadlock when that homedir is on a
+ * kerberized NFS mount. By setting $HOME to "/" by default, we prevent
+ * this behavior in routines that use $HOME in preference to the results
+ * of getpw*.
+ *
+ * Some users do not use Kerberized home dirs and need $HOME to remain
+ * unchanged. Those users can leave $HOME unchanged by setting set_home
+ * to false.
+ */
+ if (set_home) {
+ if (setenv("HOME", "/", 1)) {
+ printerr(0, "gssd: Unable to set $HOME: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+
+ if (use_gssproxy) {
+ if (setenv("GSS_USE_PROXY", "yes", 1) < 0) {
+ printerr(0, "gssd: Unable to set $GSS_USE_PROXY: %s\n",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (ccachedir) {
+ char *ptr;
+
+ for (ptr = ccachedir, i = 2; *ptr; ptr++)
+ if (*ptr == ':')
+ i++;
+
+ ccachesearch = malloc(i * sizeof(char *));
+ if (!ccachesearch) {
+ printerr(0, "malloc failure\n");
+ exit(EXIT_FAILURE);
+ }
+
+ i = 0;
+ ccachesearch[i++] = strtok(ccachedir, ":");
+ while(ccachesearch[i - 1])
+ ccachesearch[i++] = strtok(NULL, ":");
+
+ } else {
+ ccachesearch = malloc(3 * sizeof(char *));
+ if (!ccachesearch) {
+ printerr(0, "malloc failure\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ccachesearch[0] = GSSD_DEFAULT_CRED_DIR;
+ ccachesearch[1] = GSSD_USER_CRED_DIR;
+ ccachesearch[2] = NULL;
+ }
+
+ if (preferred_realm == NULL)
+ gssd_k5_get_default_realm(&preferred_realm);
+
+ if ((progname = strrchr(argv[0], '/')))
+ progname++;
+ else
+ progname = argv[0];
+
+ if (upcall_timeout > MAX_UPCALL_TIMEOUT)
+ upcall_timeout = MAX_UPCALL_TIMEOUT;
+ else if (upcall_timeout < MIN_UPCALL_TIMEOUT)
+ upcall_timeout = MIN_UPCALL_TIMEOUT;
+
+ initerr(progname, verbosity, fg);
+#ifdef HAVE_LIBTIRPC_SET_DEBUG
+ /*
+ * Only set the libtirpc debug level if explicitly requested via -r.
+ */
+ if (rpc_verbosity > 0)
+ libtirpc_set_debug(progname, rpc_verbosity, fg);
+#else
+ if (rpc_verbosity > 0)
+ printerr(0, "Warning: libtirpc does not "
+ "support setting debug levels\n");
+#endif
+
+ daemon_init(fg);
+
+ if (gssd_check_mechs() != 0)
+ errx(1, "Problem with gssapi library");
+
+ evbase = event_base_new();
+ if (!evbase) {
+ printerr(0, "ERROR: failed to create event base: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ pipefs_dir = opendir(pipefs_path);
+ if (!pipefs_dir) {
+ printerr(0, "ERROR: opendir(%s) failed: %s\n", pipefs_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ pipefs_fd = dirfd(pipefs_dir);
+ if (fchdir(pipefs_fd)) {
+ printerr(0, "ERROR: fchdir(%s) failed: %s\n", pipefs_path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ inotify_fd = inotify_init1(IN_NONBLOCK);
+ if (inotify_fd == -1) {
+ printerr(0, "ERROR: inotify_init1 failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ signal(SIGINT, sig_die);
+ signal(SIGTERM, sig_die);
+ sighup_ev = evsignal_new(evbase, SIGHUP, gssd_scan_cb, NULL);
+ if (!sighup_ev) {
+ printerr(0, "ERROR: failed to create SIGHUP event: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ evsignal_add(sighup_ev, NULL);
+ inotify_ev = event_new(evbase, inotify_fd, EV_READ | EV_PERSIST,
+ gssd_inotify_cb, NULL);
+ if (!inotify_ev) {
+ printerr(0, "ERROR: failed to create inotify event: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ event_add(inotify_ev, NULL);
+
+ TAILQ_INIT(&active_thread_list);
+
+ rc = start_watchdog_thread();
+ if (rc != 0) {
+ printerr(0, "ERROR: failed to start watchdog thread: %d\n", rc);
+ exit(EXIT_FAILURE);
+ }
+
+ TAILQ_INIT(&topdir_list);
+ gssd_scan();
+ daemon_ready();
+
+ rc = event_base_dispatch(evbase);
+
+ printerr(0, "event_dispatch() returned %i!\n", rc);
+
+ gssd_destroy_krb5_principals(root_uses_machine_creds);
+
+ while (!TAILQ_EMPTY(&topdir_list)) {
+ struct topdir *tdi = TAILQ_FIRST(&topdir_list);
+ TAILQ_REMOVE(&topdir_list, tdi, list);
+ while (!TAILQ_EMPTY(&tdi->clnt_list)) {
+ struct clnt_info *clp = TAILQ_FIRST(&tdi->clnt_list);
+ TAILQ_REMOVE(&tdi->clnt_list, clp, list);
+ gssd_destroy_client(clp);
+ }
+ free(tdi);
+ }
+
+ event_free(inotify_ev);
+ event_free(sighup_ev);
+ event_base_free(evbase);
+
+ close(inotify_fd);
+ close(pipefs_fd);
+ closedir(pipefs_dir);
+
+ free(preferred_realm);
+ free(ccachesearch);
+ free(ccachedir);
+
+ return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}