Blob Blame History Raw
diff --git a/nfs.conf b/nfs.conf
index bebb2e3d..31994f61 100644
--- a/nfs.conf
+++ b/nfs.conf
@@ -24,6 +24,7 @@
 # keytab-file=/etc/krb5.keytab
 # cred-cache-directory=
 # preferred-realm=
+# set-home=1
 #
 [lockd]
 # port=0
@@ -31,8 +32,11 @@
 #
 [exportd]
 # debug="all|auth|call|general|parse"
+# manage-gids=n
 # state-directory-path=/var/lib/nfs
 # threads=1
+# cache-use-ipaddr=n
+# ttl=1800
 [mountd]
 # debug="all|auth|call|general|parse"
 # manage-gids=n
@@ -42,6 +46,8 @@
 # reverse-lookup=n
 # state-directory-path=/var/lib/nfs
 # ha-callout=
+# cache-use-ipaddr=n
+# ttl=1800
 #
 [nfsdcld]
 # debug=0
@@ -66,9 +72,9 @@
 # vers4.0=y
 # vers4.1=y
 # vers4.2=y
-# rdma=n
-# rdma-port=20049
-#
+rdma=y
+rdma-port=20049
+
 [statd]
 # debug=0
 # port=0
diff --git a/support/export/Makefile.am b/support/export/Makefile.am
index a9e710c0..eec737f6 100644
--- a/support/export/Makefile.am
+++ b/support/export/Makefile.am
@@ -12,7 +12,8 @@ EXTRA_DIST	= mount.x
 noinst_LIBRARIES = libexport.a
 libexport_a_SOURCES = client.c export.c hostname.c \
 		      xtab.c mount_clnt.c mount_xdr.c \
-			  cache.c auth.c v4root.c fsloc.c
+		      cache.c auth.c v4root.c fsloc.c \
+		      v4clients.c
 BUILT_SOURCES 	= $(GENFILES)
 
 noinst_HEADERS = mount.h
diff --git a/support/export/auth.c b/support/export/auth.c
index 0bfa77d1..cea37630 100644
--- a/support/export/auth.c
+++ b/support/export/auth.c
@@ -66,6 +66,10 @@ check_useipaddr(void)
 	int old_use_ipaddr = use_ipaddr;
 	unsigned int len = 0;
 
+	if (use_ipaddr > 1)
+		/* fixed - don't check */
+		return;
+
 	/* add length of m_hostname + 1 for the comma */
 	for (clp = clientlist[MCL_NETGROUP]; clp; clp = clp->m_next)
 		len += (strlen(clp->m_hostname) + 1);
diff --git a/support/export/cache.c b/support/export/cache.c
index f1569afb..3e4f53c0 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -42,13 +42,6 @@
 #include "blkid/blkid.h"
 #endif
 
-/*
- * Invoked by RPC service loop
- */
-void	cache_set_fds(fd_set *fdset);
-int	cache_process_req(fd_set *readfds);
-void cache_process_loop(void);
-
 enum nfsd_fsid {
 	FSID_DEV = 0,
 	FSID_NUM,
@@ -96,7 +89,6 @@ static bool path_lookup_error(int err)
  * Record is terminated with newline.
  *
  */
-static int cache_export_ent(char *buf, int buflen, char *domain, struct exportent *exp, char *path);
 
 #define INITIAL_MANAGED_GROUPS 100
 
@@ -114,6 +106,7 @@ static void auth_unix_ip(int f)
 	char class[20];
 	char ipaddr[INET6_ADDRSTRLEN + 1];
 	char *client = NULL;
+	struct addrinfo *ai = NULL;
 	struct addrinfo *tmp = NULL;
 	char buf[RPC_CHAN_BUF_SIZE], *bp;
 	int blen;
@@ -139,21 +132,26 @@ static void auth_unix_ip(int f)
 
 	auth_reload();
 
-	/* addr is a valid, interesting address, find the domain name... */
-	if (!use_ipaddr) {
-		struct addrinfo *ai = NULL;
-
-		ai = client_resolve(tmp->ai_addr);
-		if (ai) {
-			client = client_compose(ai);
-			nfs_freeaddrinfo(ai);
-		}
+	/* addr is a valid address, find the domain name... */
+	ai = client_resolve(tmp->ai_addr);
+	if (ai) {
+		client = client_compose(ai);
+		nfs_freeaddrinfo(ai);
 	}
+	if (!client)
+		xlog(D_AUTH, "failed authentication for IP %s", ipaddr);
+	else if	(!use_ipaddr)
+		xlog(D_AUTH, "successful authentication for IP %s as %s",
+		     ipaddr, *client ? client : "DEFAULT");
+	else
+		xlog(D_AUTH, "successful authentication for IP %s",
+			     ipaddr);
+
 	bp = buf; blen = sizeof(buf);
 	qword_add(&bp, &blen, "nfsd");
 	qword_add(&bp, &blen, ipaddr);
-	qword_adduint(&bp, &blen, time(0) + DEFAULT_TTL);
-	if (use_ipaddr) {
+	qword_adduint(&bp, &blen, time(0) + default_ttl);
+	if (use_ipaddr && client) {
 		memmove(ipaddr + 1, ipaddr, strlen(ipaddr) + 1);
 		ipaddr[0] = '$';
 		qword_add(&bp, &blen, ipaddr);
@@ -225,7 +223,7 @@ static void auth_unix_gid(int f)
 
 	bp = buf; blen = sizeof(buf);
 	qword_adduint(&bp, &blen, uid);
-	qword_adduint(&bp, &blen, time(0) + DEFAULT_TTL);
+	qword_adduint(&bp, &blen, time(0) + default_ttl);
 	if (rv >= 0) {
 		qword_adduint(&bp, &blen, ngroups);
 		for (i=0; i<ngroups; i++)
@@ -873,18 +871,13 @@ static void nfsd_fh(int f)
 	    !is_mountpoint(found->e_mountpoint[0]?
 			   found->e_mountpoint:
 			   found->e_path)) {
-		/* Cannot export this yet 
+		/* Cannot export this yet
 		 * should log a warning, but need to rate limit
 		   xlog(L_WARNING, "%s not exported as %d not a mountpoint",
 		   found->e_path, found->e_mountpoint);
 		 */
 		/* FIXME we need to make sure we re-visit this later */
 		goto out;
-	} else if (cache_export_ent(buf, sizeof(buf), dom, found, found_path) < 0) {
-		if (!path_lookup_error(errno))
-			goto out;
-		/* The kernel is saying the path is unexportable */
-		found = NULL;
 	}
 
 	bp = buf; blen = sizeof(buf);
@@ -905,6 +898,8 @@ static void nfsd_fh(int f)
 	qword_addeol(&bp, &blen);
 	if (blen <= 0 || cache_write(f, buf, bp - buf) != bp - buf)
 		xlog(L_ERROR, "nfsd_fh: error writing reply");
+	if (!found)
+		xlog(D_AUTH, "denied access to %s", *dom == '$' ? dom+1 : dom);
 out:
 	if (found_path)
 		free(found_path);
@@ -966,7 +961,7 @@ static int dump_to_cache(int f, char *buf, int blen, char *domain,
 	ssize_t err;
 
 	if (ttl <= 1)
-		ttl = DEFAULT_TTL;
+		ttl = default_ttl;
 
 	qword_add(&bp, &blen, domain);
 	qword_add(&bp, &blen, path);
@@ -996,8 +991,13 @@ static int dump_to_cache(int f, char *buf, int blen, char *domain,
 			qword_add(&bp, &blen, "uuid");
 			qword_addhex(&bp, &blen, u, 16);
 		}
-	} else
+		xlog(D_AUTH, "granted access to %s for %s",
+		     path, *domain == '$' ? domain+1 : domain);
+	} else {
 		qword_adduint(&bp, &blen, now + ttl);
+		xlog(D_AUTH, "denied access to %s for %s",
+		     path, *domain == '$' ? domain+1 : domain);
+	}
 	qword_addeol(&bp, &blen);
 	if (blen <= 0) {
 		errno = ENOBUFS;
@@ -1530,6 +1530,7 @@ void cache_process_loop(void)
 	for (;;) {
 
 		cache_set_fds(&readfds);
+		v4clients_set_fds(&readfds);
 
 		selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, (struct timeval *) 0);
@@ -1545,6 +1546,7 @@ void cache_process_loop(void)
 
 		default:
 			cache_process_req(&readfds);
+			v4clients_process(&readfds);
 		}
 	}
 }
diff --git a/support/export/export.h b/support/export/export.h
index 4296db1a..8d5a0d30 100644
--- a/support/export/export.h
+++ b/support/export/export.h
@@ -3,13 +3,14 @@
  *
  * support/export/export.h
  *
- * Declarations for export support 
+ * Declarations for export support
  */
 
 #ifndef EXPORT_H
 #define EXPORT_H
 
 #include "nfslib.h"
+#include "exportfs.h"
 
 unsigned int	auth_reload(void);
 nfs_export *	auth_authenticate(const char *what,
@@ -17,8 +18,14 @@ nfs_export *	auth_authenticate(const char *what,
 					const char *path);
 
 void		cache_open(void);
+void		cache_set_fds(fd_set *fdset);
+int		cache_process_req(fd_set *readfds);
 void		cache_process_loop(void);
 
+void		v4clients_init(void);
+void		v4clients_set_fds(fd_set *fdset);
+int		v4clients_process(fd_set *fdset);
+
 struct nfs_fh_len *
 		cache_get_filehandle(nfs_export *exp, int len, char *p);
 int		cache_export(nfs_export *exp, char *path);
diff --git a/support/export/v4clients.c b/support/export/v4clients.c
new file mode 100644
index 00000000..dd985463
--- /dev/null
+++ b/support/export/v4clients.c
@@ -0,0 +1,227 @@
+/*
+ * support/export/v4clients.c
+ *
+ * Montior clients appearing in, and disappearing from, /proc/fs/nfsd/clients
+ * and log relevant information.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <errno.h>
+#include "export.h"
+
+/* search.h declares 'struct entry' and nfs_prot.h
+ * does too.  Easiest fix is to trick search.h into
+ * calling its struct "struct Entry".
+ */
+#define entry Entry
+#include <search.h>
+#undef entry
+
+static int clients_fd = -1;
+
+void v4clients_init(void)
+{
+	if (clients_fd >= 0)
+		return;
+	clients_fd = inotify_init1(IN_NONBLOCK);
+	if (clients_fd < 0) {
+		xlog_err("Unable to initialise v4clients watcher: %s\n",
+			 strerror(errno));
+		return;
+	}
+	if (inotify_add_watch(clients_fd, "/proc/fs/nfsd/clients",
+			      IN_CREATE | IN_DELETE) < 0) {
+		xlog_err("Unable to watch /proc/fs/nfsd/clients: %s\n",
+			 strerror(errno));
+		close(clients_fd);
+		clients_fd = -1;
+		return;
+	}
+}
+
+void v4clients_set_fds(fd_set *fdset)
+{
+	if (clients_fd >= 0)
+		FD_SET(clients_fd, fdset);
+}
+
+static void *tree_root;
+static int have_unconfirmed;
+
+struct ent {
+	unsigned long num;
+	char *clientid;
+	char *addr;
+	int vers;
+	int unconfirmed;
+	int wid;
+};
+
+static int ent_cmp(const void *av, const void *bv)
+{
+	const struct ent *a = av;
+	const struct ent *b = bv;
+
+	if (a->num < b->num)
+		return -1;
+	if (a->num > b->num)
+		return 1;
+	return 0;
+}
+
+static void free_ent(struct ent *ent)
+{
+	free(ent->clientid);
+	free(ent->addr);
+	free(ent);
+}
+
+static char *dup_line(char *line)
+{
+	char *ret;
+	char *e = strchr(line, '\n');
+	if (!e)
+		e = line + strlen(line);
+	ret = malloc(e - line + 1);
+	if (ret) {
+		memcpy(ret, line, e - line);
+		ret[e-line] = 0;
+	}
+	return ret;
+}
+
+static void read_info(struct ent *key)
+{
+	char buf[2048];
+	char *path;
+	int was_unconfirmed = key->unconfirmed;
+	FILE *f;
+
+	if (asprintf(&path, "/proc/fs/nfsd/clients/%lu/info", key->num) < 0)
+		return;
+
+	f = fopen(path, "r");
+	if (!f) {
+		free(path);
+		return;
+	}
+	if (key->wid < 0)
+		key->wid = inotify_add_watch(clients_fd, path, IN_MODIFY);
+
+	while (fgets(buf, sizeof(buf), f)) {
+		if (strncmp(buf, "clientid: ", 10) == 0) {
+			free(key->clientid);
+			key->clientid = dup_line(buf+10);
+		}
+		if (strncmp(buf, "address: ", 9) == 0) {
+			free(key->addr);
+			key->addr = dup_line(buf+9);
+		}
+		if (strncmp(buf, "minor version: ", 15) == 0)
+			key->vers = atoi(buf+15);
+		if (strncmp(buf, "status: ", 8) == 0 &&
+		    strstr(buf, " unconfirmed") != NULL) {
+			key->unconfirmed = 1;
+			have_unconfirmed = 1;
+		}
+		if (strncmp(buf, "status: ", 8) == 0 &&
+		    strstr(buf, " confirmed") != NULL)
+			key->unconfirmed = 0;
+	}
+	fclose(f);
+	free(path);
+
+	if (was_unconfirmed && !key->unconfirmed)
+		xlog(L_NOTICE, "v4.%d client attached: %s from %s",
+		     key->vers, key->clientid ?: "-none-",
+		     key->addr ?: "-none-");
+	if (!key->unconfirmed && key->wid >= 0) {
+		inotify_rm_watch(clients_fd, key->wid);
+		key->wid = -1;
+	}
+}
+
+static void add_id(int id)
+{
+	struct ent **ent;
+	struct ent *key;
+
+	key = calloc(1, sizeof(*key));
+	if (!key) {
+		return;
+	}
+	key->num = id;
+	key->wid = -1;
+
+	ent = tsearch(key, &tree_root, ent_cmp);
+
+	if (!ent || *ent != key)
+		/* Already existed, or insertion failed */
+		free_ent(key);
+	else
+		read_info(key);
+}
+
+static void del_id(unsigned long id)
+{
+	struct ent key = {.num = id};
+	struct ent **e, *ent;
+
+	e = tfind(&key, &tree_root, ent_cmp);
+	if (!e || !*e)
+		return;
+	ent = *e;
+	tdelete(ent, &tree_root, ent_cmp);
+	if (!ent->unconfirmed)
+		xlog(L_NOTICE, "v4.%d client detached: %s from %s",
+		     ent->vers, ent->clientid, ent->addr);
+	if (ent->wid >= 0)
+		inotify_rm_watch(clients_fd, ent->wid);
+	free_ent(ent);
+}
+
+static void check_id(unsigned long id)
+{
+	struct ent key = {.num = id};
+	struct ent **e, *ent;
+
+	e = tfind(&key, &tree_root, ent_cmp);
+	if (!e || !*e)
+		return;
+	ent = *e;
+	if (ent->unconfirmed)
+		read_info(ent);
+}
+
+int v4clients_process(fd_set *fdset)
+{
+	char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
+	const struct inotify_event *ev;
+	ssize_t len;
+	char *ptr;
+
+	if (clients_fd < 0 ||
+	    !FD_ISSET(clients_fd, fdset))
+		return 0;
+
+	while ((len = read(clients_fd, buf, sizeof(buf))) > 0) {
+		for (ptr = buf; ptr < buf + len;
+		     ptr += sizeof(struct inotify_event) + ev->len) {
+			int id;
+			ev = (const struct inotify_event *)ptr;
+
+			id = atoi(ev->name);
+			if (id <= 0)
+				continue;
+			if (ev->mask & IN_CREATE)
+				add_id(id);
+			if (ev->mask & IN_DELETE)
+				del_id(id);
+			if (ev->mask & IN_MODIFY)
+				check_id(id);
+		}
+	}
+	return 1;
+}
diff --git a/support/export/v4root.c b/support/export/v4root.c
index 6f640aa9..3654bd7c 100644
--- a/support/export/v4root.c
+++ b/support/export/v4root.c
@@ -45,7 +45,7 @@ static nfs_export pseudo_root = {
 		.e_nsqgids = 0,
 		.e_fsid = 0,
 		.e_mountpoint = NULL,
-		.e_ttl = DEFAULT_TTL,
+		.e_ttl = 0,
 	},
 	.m_exported = 0,
 	.m_xtabent = 1,
@@ -84,6 +84,7 @@ v4root_create(char *path, nfs_export *export)
 	struct exportent *curexp = &export->m_export;
 
 	dupexportent(&eep, &pseudo_root.m_export);
+	eep.e_ttl = default_ttl;
 	eep.e_hostname = curexp->e_hostname;
 	strncpy(eep.e_path, path, sizeof(eep.e_path)-1);
 	if (strcmp(path, "/") != 0)
diff --git a/support/include/conffile.h b/support/include/conffile.h
index 7d974fe9..c4a3ca62 100644
--- a/support/include/conffile.h
+++ b/support/include/conffile.h
@@ -61,6 +61,7 @@ extern _Bool    conf_get_bool(const char *, const char *, _Bool);
 extern char    *conf_get_str(const char *, const char *);
 extern char    *conf_get_str_with_def(const char *, const char *, char *);
 extern char    *conf_get_section(const char *, const char *, const char *);
+extern char    *conf_get_entry(const char *, const char *, const char *);
 extern int      conf_init_file(const char *);
 extern void     conf_cleanup(void);
 extern int      conf_match_num(const char *, const char *, int);
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
index daa7e2a0..81d13721 100644
--- a/support/include/exportfs.h
+++ b/support/include/exportfs.h
@@ -105,7 +105,8 @@ typedef struct mexport {
 } nfs_export;
 
 #define HASH_TABLE_SIZE 1021
-#define DEFAULT_TTL	(30 * 60)
+
+extern int default_ttl;
 
 typedef struct _exp_hash_entry {
 	nfs_export * p_first;
diff --git a/support/misc/xstat.c b/support/misc/xstat.c
index a438fbcc..6f751f7f 100644
--- a/support/misc/xstat.c
+++ b/support/misc/xstat.c
@@ -85,6 +85,7 @@ int xlstat(const char *pathname, struct stat *statbuf)
 		return 0;
 	else if (errno != ENOSYS)
 		return -1;
+	errno = 0;
 	return fstatat(AT_FDCWD, pathname, statbuf, AT_NO_AUTOMOUNT |
 			AT_SYMLINK_NOFOLLOW);
 }
@@ -95,6 +96,7 @@ int xstat(const char *pathname, struct stat *statbuf)
 		return 0;
 	else if (errno != ENOSYS)
 		return -1;
+	errno = 0;
 	return fstatat(AT_FDCWD, pathname, statbuf, AT_NO_AUTOMOUNT);
 }
 
diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
index a4ea0676..fd4a17ad 100644
--- a/support/nfs/conffile.c
+++ b/support/nfs/conffile.c
@@ -132,6 +132,39 @@ conf_hash(const char *s)
 	return hash;
 }
 
+/*
+ * free all the component parts of a conf_binding struct
+ */
+static void free_confbind(struct conf_binding *cb)
+{
+	if (!cb)
+		return;
+	if (cb->section)
+		free(cb->section);
+	if (cb->arg)
+		free(cb->arg);
+	if (cb->tag)
+		free(cb->tag);
+	if (cb->value)
+		free(cb->value);
+	free(cb);
+}
+
+static void free_conftrans(struct conf_trans *ct)
+{
+	if (!ct)
+		return;
+	if (ct->section)
+		free(ct->section);
+	if (ct->arg)
+		free(ct->arg);
+	if (ct->tag)
+		free(ct->tag);
+	if (ct->value)
+		free(ct->value);
+	free(ct);
+}
+
 /*
  * Insert a tag-value combination from LINE (the equal sign is at POS)
  */
@@ -147,11 +180,7 @@ conf_remove_now(const char *section, const char *tag)
 				&& strcasecmp(cb->tag, tag) == 0) {
 			LIST_REMOVE(cb, link);
 			xlog(LOG_INFO,"[%s]:%s->%s removed", section, tag, cb->value);
-			free(cb->section);
-			free(cb->arg);
-			free(cb->tag);
-			free(cb->value);
-			free(cb);
+			free_confbind(cb);
 			return 0;
 		}
 	}
@@ -171,11 +200,7 @@ conf_remove_section_now(const char *section)
 			unseen = 0;
 			LIST_REMOVE(cb, link);
 			xlog(LOG_INFO, "[%s]:%s->%s removed", section, cb->tag, cb->value);
-			free(cb->section);
-			free(cb->arg);
-			free(cb->tag);
-			free(cb->value);
-			free(cb);
+			free_confbind(cb);
 			}
 		}
 	return unseen;
@@ -571,11 +596,7 @@ static void conf_free_bindings(void)
 		for (; cb; cb = next) {
 			next = LIST_NEXT(cb, link);
 			LIST_REMOVE(cb, link);
-			free(cb->section);
-			free(cb->arg);
-			free(cb->tag);
-			free(cb->value);
-			free(cb);
+			free_confbind(cb);
 		}
 		LIST_INIT(&conf_bindings[i]);
 	}
@@ -774,11 +795,7 @@ conf_cleanup(void)
 	for (node = TAILQ_FIRST(&conf_trans_queue); node; node = next) {
 		next = TAILQ_NEXT(node, link);
 		TAILQ_REMOVE (&conf_trans_queue, node, link);
-		if (node->section) free(node->section);
-		if (node->arg) free(node->arg);
-		if (node->tag) free(node->tag);
-		if (node->value) free(node->value);
-		free (node);
+		free_conftrans(node);
 	}
 	TAILQ_INIT(&conf_trans_queue);
 }
@@ -874,6 +891,29 @@ conf_get_str_with_def(const char *section, const char *tag, char *def)
 	return result;
 }
 
+/*
+ * Retrieve an entry without interpreting its contents
+ */
+char *
+conf_get_entry(const char *section, const char *arg, const char *tag)
+{
+	struct conf_binding *cb;
+
+	cb = LIST_FIRST (&conf_bindings[conf_hash (section)]);
+	for (; cb; cb = LIST_NEXT (cb, link)) {
+		if (strcasecmp(section, cb->section) != 0)
+			continue;
+		if (arg && (cb->arg == NULL || strcasecmp(arg, cb->arg) != 0))
+			continue;
+		if (!arg && cb->arg)
+			continue;
+		if (strcasecmp(tag, cb->tag) != 0)
+			continue;
+		return cb->value;
+	}
+	return 0;
+}
+
 /*
  * Find a section that may or may not have an argument
  */
@@ -1144,14 +1184,7 @@ conf_set(int transaction, const char *section, const char *arg,
 	return 0;
 
 fail:
-	if (node->tag)
-		free(node->tag);
-	if (node->arg)
-		free(node->arg);
-	if (node->section)
-		free(node->section);
-	if (node)
-		free(node);
+	free_conftrans(node);
 	return 1;
 }
 
@@ -1177,10 +1210,7 @@ conf_remove(int transaction, const char *section, const char *tag)
 	return 0;
 
 fail:
-	if (node && node->section)
-		free (node->section);
-	if (node)
-		free (node);
+	free_conftrans(node);
 	return 1;
 }
 
@@ -1201,8 +1231,7 @@ conf_remove_section(int transaction, const char *section)
 	return 0;
 
 fail:
-	if (node)
-		free(node);
+	free_conftrans(node);
 	return 1;
 }
 
@@ -1233,15 +1262,7 @@ conf_end(int transaction, int commit)
 				}
 			}
 			TAILQ_REMOVE (&conf_trans_queue, node, link);
-			if (node->section)
-				free(node->section);
-			if (node->arg)
-				free(node->arg);
-			if (node->tag)
-				free(node->tag);
-			if (node->value)
-				free(node->value);
-			free (node);
+			free_conftrans(node);
 		}
 	}
 	return 0;
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
index 037febd0..2c8f0752 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c
@@ -47,6 +47,8 @@ struct flav_info flav_map[] = {
 
 const int flav_map_size = sizeof(flav_map)/sizeof(flav_map[0]);
 
+int default_ttl = 30 * 60;
+
 static char	*efname = NULL;
 static XFILE	*efp = NULL;
 static int	first;
@@ -100,7 +102,7 @@ static void init_exportent (struct exportent *ee, int fromkernel)
 	ee->e_nsquids = 0;
 	ee->e_nsqgids = 0;
 	ee->e_uuid = NULL;
-	ee->e_ttl = DEFAULT_TTL;
+	ee->e_ttl = default_ttl;
 }
 
 struct exportent *
diff --git a/support/nfs/getport.c b/support/nfs/getport.c
index e458d8fe..813f7bf9 100644
--- a/support/nfs/getport.c
+++ b/support/nfs/getport.c
@@ -904,7 +904,7 @@ int nfs_getport_ping(struct sockaddr *sap, const socklen_t salen,
  * listen on AF_LOCAL.
  *
  * If that doesn't work (for example, if portmapper is running, or rpcbind
- * isn't listening on /var/run/rpcbind.sock), send a query via UDP to localhost
+ * isn't listening on /run/rpcbind.sock), send a query via UDP to localhost
  * (UDP doesn't leave a socket in TIME_WAIT, and the timeout is a relatively
  * short 3 seconds).
  */
diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
index d2187f8a..4436a38a 100644
--- a/systemd/nfs.conf.man
+++ b/systemd/nfs.conf.man
@@ -132,12 +132,22 @@ but on the server, this will resolve to the path
 .B exportd
 Recognized values:
 .BR threads ,
+.BR cache-use-upaddr ,
+.BR ttl ,
 .BR state-directory-path
 
 See
 .BR exportd (8)
 for details.
 
+Note that setting 
+.B "\[dq]debug = auth\[dq]"
+for
+.B exportd
+is equivalent to providing the
+.B \-\-log\-auth
+option.
+
 .TP
 .B nfsdcltrack
 Recognized values:
@@ -188,6 +198,8 @@ Recognized values:
 .BR port ,
 .BR threads ,
 .BR reverse-lookup ,
+.BR cache-use-upaddr ,
+.BR ttl ,
 .BR state-directory-path ,
 .BR ha-callout .
 
@@ -197,6 +209,14 @@ section, are used to configure mountd.  See
 .BR rpc.mountd (8)
 for details.
 
+Note that setting 
+.B "\[dq]debug = auth\[dq]"
+for
+.B mountd
+is equivalent to providing the
+.B \-\-log\-auth
+option.
+
 The
 .B state-directory-path
 value in the
@@ -253,7 +273,8 @@ Recognized values:
 .BR rpc-timeout ,
 .BR keytab-file ,
 .BR cred-cache-directory ,
-.BR preferred-realm .
+.BR preferred-realm ,
+.BR set-home .
 
 See
 .BR rpc.gssd (8)
diff --git a/tests/test-lib.sh b/tests/test-lib.sh
index 57af37b1..e47ad135 100644
--- a/tests/test-lib.sh
+++ b/tests/test-lib.sh
@@ -56,5 +56,5 @@ start_statd() {
 
 # shut down statd
 kill_statd() {
-	kill `cat /var/run/rpc.statd.pid`
+	kill `cat /run/rpc.statd.pid`
 }
diff --git a/tools/nfsconf/nfsconf.man b/tools/nfsconf/nfsconf.man
index 30791988..d44e86fb 100644
--- a/tools/nfsconf/nfsconf.man
+++ b/tools/nfsconf/nfsconf.man
@@ -11,6 +11,12 @@ nfsconf \- Query various NFS configuration settings
 .IR infile.conf ]
 .RI [ outfile ]
 .P
+.B nfsconf \-\-entry
+.RB [ \-\-arg  
+.IR subsection]
+.IR section
+.IR tag
+.P
 .B nfsconf \-\-get
 .RB [ \-v | \-\-verbose ]
 .RB [ \-f | \-\-file
@@ -58,6 +64,8 @@ from a range of nfs-utils configuration files.
 The following modes are available:
 .IP "\fB\-d, \-\-dump\fP"
 Output an alphabetically sorted dump of the current configuration in conf file format. Accepts an optional filename in which to write the output.
+.IP "\fB\-e, \-\-entry\fP"
+retrieve the config entry rather than its current expanded value
 .IP "\fB\-i, \-\-isset\fP"
 Test if a specific tag has a value set.
 .IP "\fB\-g, \-\-get\fP"
@@ -75,7 +83,7 @@ Increase verbosity and print debugging information.
 .B \-f, \-\-file \fIinfile\fR
 Select a different config file to operate upon, default is
 .I /etc/nfs.conf
-.SS Options only valid in \fB\-\-get\fR and \fB\-\-isset\fR modes.
+.SS Options only valid in \fB\-\-entry\fR and \fB\-\-get\fR and \fB\-\-isset\fR modes.
 .TP
 .B \-a, \-\-arg \fIsubsection\fR
 Select a specific sub-section
diff --git a/tools/nfsconf/nfsconfcli.c b/tools/nfsconf/nfsconfcli.c
index 361d386e..b2ef96d1 100644
--- a/tools/nfsconf/nfsconfcli.c
+++ b/tools/nfsconf/nfsconfcli.c
@@ -11,6 +11,7 @@
 typedef enum {
 	MODE_NONE,
 	MODE_GET,
+	MODE_ENTRY,
 	MODE_ISSET,
 	MODE_DUMP,
 	MODE_SET,
@@ -30,6 +31,8 @@ static void usage(const char *name)
 	fprintf(stderr, "      Outputs the configuration to the named file\n");
 	fprintf(stderr, "  --get [--arg subsection] {section} {tag}\n");
 	fprintf(stderr, "      Output one specific config value\n");
+	fprintf(stderr, "  --entry [--arg subsection] {section} {tag}\n");
+	fprintf(stderr, "      Output the uninterpreted config entry\n");
 	fprintf(stderr, "  --isset [--arg subsection] {section} {tag}\n");
 	fprintf(stderr, "      Return code indicates if config value is present\n");
 	fprintf(stderr, "  --set [--arg subsection] {section} {tag} {value}\n");
@@ -55,6 +58,7 @@ int main(int argc, char **argv)
 		int index = 0;
 		struct option long_options[] = {
 			{"get",		no_argument, 0, 'g' },
+			{"entry",	no_argument, 0, 'e' },
 			{"set",		no_argument, 0, 's' },
 			{"unset",	no_argument, 0, 'u' },
 			{"arg",	  required_argument, 0, 'a' },
@@ -66,7 +70,7 @@ int main(int argc, char **argv)
 			{NULL,			  0, 0, 0 }
 		};
 
-		c = getopt_long(argc, argv, "gsua:id::f:vm:", long_options, &index);
+		c = getopt_long(argc, argv, "gesua:id::f:vm:", long_options, &index);
 		if (c == -1) break;
 
 		switch (c) {
@@ -86,6 +90,9 @@ int main(int argc, char **argv)
 			case 'g':
 				mode = MODE_GET;
 				break;
+			case 'e':
+				mode = MODE_ENTRY;
+				break;
 			case 's':
 				mode = MODE_SET;
 				break;
@@ -167,8 +174,8 @@ int main(int argc, char **argv)
 		if (dumpfile)
 			fclose(out);
 	} else
-	/* --iset and --get share a lot of code */
-	if (mode == MODE_GET || mode == MODE_ISSET) {
+	/* --isset and --get share a lot of code */
+	if (mode == MODE_GET || mode == MODE_ISSET || mode == MODE_ENTRY) {
 		char * section = NULL;
 		char * tag = NULL;
 		const char * val;
@@ -186,14 +193,17 @@ int main(int argc, char **argv)
 		tag = argv[optind++];
 
 		/* retrieve the specified tags value */
-		val = conf_get_section(section, arg, tag);
+		if (mode == MODE_ENTRY)
+			val = conf_get_entry(section, arg, tag);
+		else
+			val = conf_get_section(section, arg, tag);
 		if (val != NULL) {
 			/* ret=0, success, mode --get wants to output the value as well */
-			if (mode == MODE_GET)
+			if (mode != MODE_ISSET)
 				printf("%s\n", val);
 		} else {
 			/* ret=1, no value found, tell the user if they asked */
-			if (mode == MODE_GET && verbose)
+			if (mode != MODE_ISSET && verbose)
 				fprintf(stderr, "Tag '%s' not found\n", tag);
 			ret = 1;
 		}
diff --git a/tools/nfsdclnts/nfsdclnts.py b/tools/nfsdclnts/nfsdclnts.py
index 5e7e03c2..b7280f2c 100755
--- a/tools/nfsdclnts/nfsdclnts.py
+++ b/tools/nfsdclnts/nfsdclnts.py
@@ -223,6 +223,7 @@ def nfsd4_show():
 
     global verbose
     verbose = False
+    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
     if args.verbose:
         verbose = True
 
diff --git a/utils/blkmapd/device-discovery.c b/utils/blkmapd/device-discovery.c
index f5f9b10b..77ebe736 100644
--- a/utils/blkmapd/device-discovery.c
+++ b/utils/blkmapd/device-discovery.c
@@ -64,7 +64,7 @@
 #define EVENT_BUFSIZE (1024 * EVENT_SIZE)
 
 #define RPCPIPE_DIR	"/var/lib/nfs/rpc_pipefs"
-#define PID_FILE	"/var/run/blkmapd.pid"
+#define PID_FILE	"/run/blkmapd.pid"
 
 #define CONF_SAVE(w, f) do {			\
 	char *p = f;				\
diff --git a/utils/exportd/exportd.c b/utils/exportd/exportd.c
index 7130bcbf..f36f51d2 100644
--- a/utils/exportd/exportd.c
+++ b/utils/exportd/exportd.c
@@ -42,9 +42,14 @@ static struct option longopts[] =
 	{ "foreground", 0, 0, 'F' },
 	{ "debug", 1, 0, 'd' },
 	{ "help", 0, 0, 'h' },
+	{ "manage-gids", 0, 0, 'g' },
 	{ "num-threads", 1, 0, 't' },
+	{ "log-auth", 0, 0, 'l' },
+	{ "cache-use-ipaddr", 0, 0, 'i' },
+	{ "ttl", 0, 0, 'T' },
 	{ NULL, 0, 0, 0 }
 };
+static char shortopts[] = "d:fghs:t:liT:";
 
 /*
  * Signal handlers.
@@ -174,33 +179,43 @@ usage(const char *prog, int n)
 {
 	fprintf(stderr,
 		"Usage: %s [-f|--foreground] [-h|--help] [-d kind|--debug kind]\n"
+"	[-g|--manage-gids] [-l|--log-auth] [-i|--cache-use-ipaddr] [-T|--ttl ttl]\n"
 "	[-s|--state-directory-path path]\n"
 "	[-t num|--num-threads=num]\n", prog);
 	exit(n);
 }
 
-inline static void 
+inline static void
 read_exportd_conf(char *progname, char **argv)
 {
 	char *s;
+	int ttl;
 
 	conf_init_file(NFS_CONFFILE);
 
 	xlog_set_debug(progname);
 
+	manage_gids = conf_get_bool("exportd", "manage-gids", manage_gids);
 	num_threads = conf_get_num("exportd", "threads", num_threads);
+	if (conf_get_bool("mountd", "cache-use-ipaddr", 0))
+		use_ipaddr = 2;
 
 	s = conf_get_str("exportd", "state-directory-path");
 	if (s && !state_setup_basedir(argv[0], s))
 		exit(1);
+
+	ttl = conf_get_num("mountd", "ttl", default_ttl);
+	if (ttl > 0)
+		default_ttl = ttl;
 }
 
 int
 main(int argc, char **argv)
 {
 	char *progname;
-	int	foreground = 0;
-	int	 c;
+	int foreground = 0;
+	int c;
+	int ttl;
 
 	/* Set the basename */
 	if ((progname = strrchr(argv[0], '/')) != NULL)
@@ -214,17 +229,35 @@ main(int argc, char **argv)
 	/* Read in config setting */
 	read_exportd_conf(progname, argv);
 
-	while ((c = getopt_long(argc, argv, "d:fhs:t:", longopts, NULL)) != EOF) {
+	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF) {
 		switch (c) {
 		case 'd':
 			xlog_sconfig(optarg, 1);
 			break;
+		case 'l':
+			xlog_sconfig("auth", 1);
+			break;
 		case 'f':
 			foreground++;
 			break;
+		case 'g':
+			manage_gids = 1;
+			break;
 		case 'h':
 			usage(progname, 0);
 			break;
+		case 'i':
+			use_ipaddr = 2;
+			break;
+		case 'T':
+			ttl = atoi(optarg);
+			if (ttl <= 0) {
+				fprintf(stderr, "%s: bad ttl number of seconds: %s\n",
+					argv[0], optarg);
+				usage(argv[0], 1);
+			}
+			default_ttl = ttl;
+			break;
 		case 's':
 			if (!state_setup_basedir(argv[0], optarg))
 				exit(1);
@@ -241,8 +274,8 @@ main(int argc, char **argv)
 
 	if (!setup_state_path_names(progname, ETAB, ETABTMP, ETABLCK, &etab))
 		return 1;
-	
-	if (!foreground) 
+
+	if (!foreground)
 		xlog_stderr(0);
 
 	daemon_init(foreground);
@@ -264,6 +297,7 @@ main(int argc, char **argv)
 
 	/* Open files now to avoid sharing descriptors among forked processes */
 	cache_open();
+	v4clients_init();
 
 	/* Process incoming upcalls */
 	cache_process_loop();
diff --git a/utils/exportd/exportd.man b/utils/exportd/exportd.man
index 1d65b5e0..fae434b5 100644
--- a/utils/exportd/exportd.man
+++ b/utils/exportd/exportd.man
@@ -10,35 +10,74 @@ nfsv4.exportd \- NFSv4 Server Mount Daemon
 .SH DESCRIPTION
 The
 .B nfsv4.exportd
-is used to manage NFSv4 exports. The NFSv4 server
-receives a mount request from a client and pass it up to 
-.B nfsv4.exportd. 
-.B nfsv4.exportd 
-then uses the exports(5) export
-table to verify the validity of the mount request.
-.PP
-An NFS server maintains a table of local physical file systems
-that are accessible to NFS clients.
-Each file system in this table is referred to as an
-.IR "exported file system" ,
-or
-.IR export ,
-for short.
-.PP
-Each file system in the export table has an access control list.
+is used to manage NFSv4 exports.
+The NFS server
+.RI ( nfsd )
+maintains a cache of authentication and authorization information which
+is used to identify the source of each request, and then what access
+permissions that source has to any local filesystem.  When required
+information is not found in the cache, the server sends a request to
 .B nfsv4.exportd
-uses these access control lists to determine
-whether an NFS client is permitted to access a given file system.
-For details on how to manage your NFS server's export table, see the
-.BR exports (5)
-and
-.BR exportfs (8)
-man pages.
+to fill in the missing information.  
+.B nfsv4.exportd
+uses a table of information stored in
+.B /var/lib/nfs/etab
+and maintained by
+.BR exportfs (8),
+possibly based on the contents of 
+.BR exports (5),
+to respond to each request.
 .SH OPTIONS
 .TP
 .B \-d kind " or " \-\-debug kind
 Turn on debugging. Valid kinds are: all, auth, call, general and parse.
 .TP
+.BR \-l " or " \-\-log\-auth
+Enable logging of responses to authentication and access requests from
+nfsd.  Each response is then cached by the kernel for 30 minutes (or as set by
+.B \-\-ttl
+below), and will be refreshed after 15 minutes (half the ttl time) if
+the relevant client remains active.
+Note that
+.B -l
+is equivalent to
+.B "-d auth"
+and so can be enabled in
+.B /etc/nfs.conf
+with
+.B "\[dq]debug = auth\[dq]"
+in the
+.B "[exportd]"
+section.
+.TP
+.BR \-i " or " \-\-cache\-use\-ipaddr
+Normally each client IP address is matched against each host identifier
+(name, wildcard, netgroup etc) found in
+.B /etc/exports
+and a combined identity is formed from all matching identifiers.
+Often many clients will map to the same combined identity so performing
+this mapping reduces the number of distinct access details that the
+kernel needs to store.
+Specifying the
+.B \-i
+option suppresses this mapping so that access to each filesystem is
+requested and cached separately for each client IP address.  Doing this
+can increase the burden of updating the cache slightly, but can make the
+log messages produced by the
+.B -l
+option easier to read.
+.TP
+.B \-T " or " \-\-ttl
+Provide a time-to-live (TTL) for cached information given to the kernel.
+The kernel will normally request an update if the information is needed
+after half of this time has expired.  Increasing the provided number,
+which is in seconds, reduces the rate of cache update requests, and this
+is particularly noticeable when these requests are logged with
+.BR \-l .
+However increasing also means that changes to hostname to address
+mappings can take longer to be noticed.
+The default TTL is 1800 (30 minutes).
+.TP
 .B \-F " or " \-\-foreground
 Run in foreground (do not daemonize)
 .TP
@@ -46,11 +85,27 @@ Run in foreground (do not daemonize)
 Display usage message.
 .TP
 .BR "\-t N" " or " "\-\-num\-threads=N " or  " \-\-num\-threads N "
-This option specifies the number of worker threads that rpc.mountd
+This option specifies the number of worker threads that 
+.B nfsv4.exports
 spawns.  The default is 1 thread, which is probably enough.  More
 threads are usually only needed for NFS servers which need to handle
 mount storms of hundreds of NFS mounts in a few seconds, or when
 your DNS server is slow or unreliable.
+.TP
+.BR \-g " or " \-\-manage-gids
+Accept requests from the kernel to map user id numbers into lists of
+group id numbers for use in access control.  An NFS request will
+normally (except when using Kerberos or other cryptographic
+authentication) contain a user-id and a list of group-ids.  Due to a
+limitation in the NFS protocol, at most 16 groups ids can be listed.
+If you use the
+.B \-g
+flag, then the list of group ids received from the client will be
+replaced by a list of group ids determined by an appropriate lookup on
+the server. Note that the 'primary' group id is not affected so a
+.B newgroup
+command on the client will still be effective.  This function requires
+a Linux Kernel with version at least 2.6.21.
 .SH CONFIGURATION FILE
 Many of the options that can be set on the command line can also be
 controlled through values set in the
@@ -63,6 +118,9 @@ configuration file.
 Values recognized in the
 .B [exportd]
 section include 
+.B cache\-use\-ipaddr ,
+.BR ttl ,
+.BR manage-gids ", and"
 .B debug 
 which each have the same effect as the option with the same name.
 .SH FILES
@@ -76,6 +134,8 @@ listing exports, export options, and access control lists
 .BR exports (5),
 .BR showmount (8),
 .BR nfs.conf (5),
-.BR firwall-cmd (1),
+.BR firewall-cmd (1),
 .sp
-RFC 3530 - "Network File System (NFS) version 4 Protocol"
+RFC 7530 - "Network File System (NFS) Version 4 Protocol"
+.br
+RFC 8881 - "Network File System (NFS) Version 4 Minor Version 1 Protocol"
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index 262dd19a..25d757d8 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c
@@ -383,7 +383,7 @@ unexportfs_parsed(char *hname, char *path, int verbose)
 	 * so need to deal with it.
 	*/
 	size_t nlen = strlen(path);
-	while (path[nlen - 1] == '/')
+	while ((nlen > 1) && (path[nlen - 1] == '/'))
 		nlen--;
 
 	for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
index 85bc4b07..1541d371 100644
--- a/utils/gssd/gssd.c
+++ b/utils/gssd/gssd.c
@@ -87,6 +87,8 @@ 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;
@@ -900,7 +902,7 @@ sig_die(int signal)
 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]\n",
+	fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm] [-D] [-H]\n",
 		progname);
 	exit(1);
 }
@@ -941,6 +943,7 @@ read_gss_conf(void)
 		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
@@ -961,7 +964,7 @@ main(int argc, char *argv[])
 	verbosity = conf_get_num("gssd", "verbosity", verbosity);
 	rpc_verbosity = conf_get_num("gssd", "rpc-verbosity", rpc_verbosity);
 
-	while ((opt = getopt(argc, argv, "DfvrlmnMp:k:d:t:T:R:")) != -1) {
+	while ((opt = getopt(argc, argv, "HDfvrlmnMp:k:d:t:T:R:")) != -1) {
 		switch (opt) {
 			case 'f':
 				fg = 1;
@@ -1009,6 +1012,9 @@ main(int argc, char *argv[])
 			case 'D':
 				avoid_dns = false;
 				break;
+			case 'H':
+				set_home = false;
+				break;
 			default:
 				usage(argv[0]);
 				break;
@@ -1018,13 +1024,19 @@ main(int argc, char *argv[])
 	/*
 	 * 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 unconditionally to "/", we
-	 * prevent this behavior in routines that use $HOME in preference to
-	 * the results of getpw*.
+	 * 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 (setenv("HOME", "/", 1)) {
-		printerr(0, "gssd: Unable to set $HOME: %s\n", strerror(errno));
-		exit(1);
+	if (set_home) {
+		if (setenv("HOME", "/", 1)) {
+			printerr(0, "gssd: Unable to set $HOME: %s\n", strerror(errno));
+			exit(1);
+		}
 	}
 
 	if (use_gssproxy) {
diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man
index 26095a89..9ae6def9 100644
--- a/utils/gssd/gssd.man
+++ b/utils/gssd/gssd.man
@@ -8,7 +8,7 @@
 rpc.gssd \- RPCSEC_GSS daemon
 .SH SYNOPSIS
 .B rpc.gssd
-.RB [ \-DfMnlvr ]
+.RB [ \-DfMnlvrH ]
 .RB [ \-k
 .IR keytab ]
 .RB [ \-p
@@ -282,6 +282,16 @@ The default timeout is set to 5 seconds.
 If you get messages like "WARNING: can't create tcp rpc_clnt to server
 %servername% for user with uid %uid%: RPC: Remote system error -
 Connection timed out", you should consider an increase of this timeout.
+.TP
+.B -H
+Avoids setting $HOME to "/". This allows rpc.gssd to read per user k5identity
+files versus trying to read /.k5identity for each user.
+
+If
+.B \-H
+is not set, rpc.gssd will use the first match found in
+/var/kerberos/krb5/user/$EUID/client.keytab and will not use a principal based on
+host and/or service parameters listed in $HOME/.k5identity.
 .SH CONFIGURATION FILE
 Many of the options that can be set on the command line can also be
 controlled through values set in the
@@ -339,6 +349,13 @@ Equivalent to
 .B preferred-realm
 Equivalent to
 .BR -R .
+.TP
+.B set-home
+Setting to
+.B false
+is equivalent to providing the
+.B -H
+flag.
 .P
 In addtion, the following value is recognized from the
 .B [general]
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index 612063ba..39e85fd5 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -31,6 +31,7 @@
 #include "pseudoflavors.h"
 #include "nfsd_path.h"
 #include "nfslib.h"
+#include "export.h"
 
 extern void my_svc_run(void);
 
@@ -74,8 +75,12 @@ static struct option longopts[] =
 	{ "reverse-lookup", 0, 0, 'r' },
 	{ "manage-gids", 0, 0, 'g' },
 	{ "no-udp", 0, 0, 'u' },
+	{ "log-auth", 0, 0, 'l'},
+	{ "cache-use-ipaddr", 0, 0, 'i'},
+	{ "ttl", 1, 0, 'T'},
 	{ NULL, 0, 0, 0 }
 };
+static char shortopts[] = "o:nFd:p:P:hH:N:V:vurs:t:gliT:";
 
 #define NFSVERSBIT(vers)	(0x1 << (vers - 1))
 #define NFSVERSBIT_ALL		(NFSVERSBIT(2) | NFSVERSBIT(3) | NFSVERSBIT(4))
@@ -669,6 +674,7 @@ inline static void
 read_mountd_conf(char **argv)
 {
 	char	*s;
+	int	ttl;
 
 	conf_init_file(NFS_CONFFILE);
 
@@ -679,6 +685,8 @@ read_mountd_conf(char **argv)
 	num_threads = conf_get_num("mountd", "threads", num_threads);
 	reverse_resolve = conf_get_bool("mountd", "reverse-lookup", reverse_resolve);
 	ha_callout_prog = conf_get_str("mountd", "ha-callout");
+	if (conf_get_bool("mountd", "cache-use-ipaddr", 0))
+		use_ipaddr = 2;
 
 	s = conf_get_str("mountd", "state-directory-path");
 	if (s && !state_setup_basedir(argv[0], s))
@@ -701,6 +709,10 @@ read_mountd_conf(char **argv)
 		else
 			NFSCTL_VERUNSET(nfs_version, vers);
 	}
+
+	ttl = conf_get_num("mountd", "ttl", default_ttl);
+	if (ttl > 0)
+		default_ttl = ttl;
 }
 
 int
@@ -710,6 +722,7 @@ main(int argc, char **argv)
 	unsigned int listeners = 0;
 	int	foreground = 0;
 	int	c;
+	int	ttl;
 	struct sigaction sa;
 	struct rlimit rlim;
 
@@ -727,7 +740,7 @@ main(int argc, char **argv)
 
 	/* Parse the command line options and arguments. */
 	opterr = 0;
-	while ((c = getopt_long(argc, argv, "o:nFd:p:P:hH:N:V:vurs:t:g", longopts, NULL)) != EOF)
+	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF)
 		switch (c) {
 		case 'g':
 			manage_gids = 1;
@@ -798,6 +811,21 @@ main(int argc, char **argv)
 		case 'u':
 			NFSCTL_UDPUNSET(_rpcprotobits);
 			break;
+		case 'l':
+			xlog_sconfig("auth", 1);
+			break;
+		case 'i':
+			use_ipaddr = 2;
+			break;
+		case 'T':
+			ttl = atoi(optarg);
+			if (ttl <= 0) {
+				fprintf(stderr, "%s: bad ttl number of seconds: %s\n",
+					argv[0], optarg);
+				usage(argv[0], 1);
+			}
+			default_ttl = ttl;
+			break;
 		case 0:
 			break;
 		case '?':
@@ -897,6 +925,7 @@ main(int argc, char **argv)
 	nfsd_path_init();
 	/* Open files now to avoid sharing descriptors among forked processes */
 	cache_open();
+	v4clients_init();
 
 	xlog(L_NOTICE, "Version " VERSION " starting");
 	my_svc_run();
@@ -913,6 +942,7 @@ usage(const char *prog, int n)
 {
 	fprintf(stderr,
 "Usage: %s [-F|--foreground] [-h|--help] [-v|--version] [-d kind|--debug kind]\n"
+"	[-l|--log-auth] [-i|--cache-use-ipaddr] [-T|--ttl ttl]\n"
 "	[-o num|--descriptors num]\n"
 "	[-p|--port port] [-V version|--nfs-version version]\n"
 "	[-N version|--no-nfs-version version] [-n|--no-tcp]\n"
diff --git a/utils/mountd/mountd.h b/utils/mountd/mountd.h
index f058f01d..d3077531 100644
--- a/utils/mountd/mountd.h
+++ b/utils/mountd/mountd.h
@@ -60,9 +60,4 @@ bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai);
 bool namelist_client_matches(nfs_export *exp, char *dom);
 bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai);
 
-static inline bool is_ipaddr_client(char *dom)
-{
-	return dom[0] == '$';
-}
-
 #endif /* MOUNTD_H */
diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man
index 9978afcd..77e6299a 100644
--- a/utils/mountd/mountd.man
+++ b/utils/mountd/mountd.man
@@ -13,24 +13,24 @@ The
 .B rpc.mountd
 daemon implements the server side of the NFS MOUNT protocol,
 an NFS side protocol used by NFS version 2 [RFC1094] and NFS version 3 [RFC1813].
+It also responds to requests from the Linux kernel to authenticate
+clients and provides details of access permissions.
 .PP
-An NFS server maintains a table of local physical file systems
-that are accessible to NFS clients.
-Each file system in this table is referred to as an
-.IR "exported file system" ,
-or
-.IR export ,
-for short.
-.PP
-Each file system in the export table has an access control list.
-.B rpc.mountd
-uses these access control lists to determine
-whether an NFS client is permitted to access a given file system.
-For details on how to manage your NFS server's export table, see the
-.BR exports (5)
-and
-.BR exportfs (8)
-man pages.
+The NFS server
+.RI ( nfsd )
+maintains a cache of authentication and authorization information which
+is used to identify the source of each request, and then what access
+permissions that source has to any local filesystem.  When required
+information is not found in the cache, the server sends a request to
+.B mountd
+to fill in the missing information.  Mountd uses a table of information
+stored in
+.B /var/lib/nfs/etab
+and maintained by
+.BR exportfs (8),
+possibly based on the contents of 
+.BR exports (5),
+to respond to each request.
 .SS Mounting exported NFS File Systems
 The NFS MOUNT protocol has several procedures.
 The most important of these are
@@ -78,11 +78,69 @@ A client may continue accessing an export even after invoking UMNT.
 If the client reboots without sending a UMNT request, stale entries
 remain for that client in
 .IR /var/lib/nfs/rmtab .
+.SS Mounting File Systems with NFSv4
+Version 4 (and later) of NFS does not use a separate NFS MOUNT
+protocol.  Instead mounting is performed using regular NFS requests
+handled by the NFS server in the Linux kernel
+.RI ( nfsd ).
+Consequently
+.I /var/lib/nfs/rmtab
+is not updated to reflect any NFSv4 activity.
 .SH OPTIONS
 .TP
 .B \-d kind " or " \-\-debug kind
 Turn on debugging. Valid kinds are: all, auth, call, general and parse.
 .TP
+.BR \-l " or " \-\-log\-auth
+Enable logging of responses to authentication and access requests from
+nfsd.  Each response is then cached by the kernel for 30 minutes (or as set by
+.B \-\-ttl
+below), and will be refreshed after 15 minutes (half the ttl time) if
+the relevant client remains active.
+Note that
+.B -l
+is equivalent to
+.B "-d auth"
+and so can be enabled in
+.B /etc/nfs.conf
+with
+.B "\[dq]debug = auth\[dq]"
+in the
+.B "[mountd]"
+section.
+.IP
+.B rpc.mountd
+will always log authentication responses to MOUNT requests when NFSv3 is
+used, but to get similar logs for NFSv4, this option is required.
+.TP
+.BR \-i " or " \-\-cache\-use\-ipaddr
+Normally each client IP address is matched against each host identifier
+(name, wildcard, netgroup etc) found in
+.B /etc/exports
+and a combined identity is formed from all matching identifiers.
+Often many clients will map to the same combined identity so performing
+this mapping reduces the number of distinct access details that the
+kernel needs to store.
+Specifying the
+.B \-i
+option suppresses this mapping so that access to each filesystem is
+requested and cached separately for each client IP address.  Doing this
+can increase the burden of updating the cache slightly, but can make the
+log messages produced by the
+.B -l
+option easier to read.
+.TP
+.B \-T " or " \-\-ttl
+Provide a time-to-live (TTL) for cached information given to the kernel.
+The kernel will normally request an update if the information is needed
+after half of this time has expired.  Increasing the provided number,
+which is in seconds, reduces the rate of cache update requests, and this
+is particularly noticeable when these requests are logged with
+.BR \-l .
+However increasing also means that changes to hostname to address
+mappings can take longer to be noticed.
+The default TTL is 1800 (30 minutes).
+.TP
 .B \-F " or " \-\-foreground
 Run in foreground (do not daemonize)
 .TP
@@ -213,9 +271,11 @@ Values recognized in the
 .B [mountd]
 section include
 .BR manage-gids ,
+.BR cache\-use\-ipaddr ,
 .BR descriptors ,
 .BR port ,
 .BR threads ,
+.BR ttl ,
 .BR reverse-lookup ", and"
 .BR state-directory-path ,
 .B ha-callout
@@ -295,5 +355,9 @@ table of clients accessing server's exports
 RFC 1094 - "NFS: Network File System Protocol Specification"
 .br
 RFC 1813 - "NFS Version 3 Protocol Specification"
+.br
+RFC 7530 - "Network File System (NFS) Version 4 Protocol"
+.br
+RFC 8881 - "Network File System (NFS) Version 4 Minor Version 1 Protocol"
 .SH AUTHOR
 Olaf Kirch, H. J. Lu, G. Allan Morris III, and a host of others.
diff --git a/utils/mountd/svc_run.c b/utils/mountd/svc_run.c
index 41b96d7f..167b9757 100644
--- a/utils/mountd/svc_run.c
+++ b/utils/mountd/svc_run.c
@@ -56,10 +56,9 @@
 #ifdef HAVE_LIBTIRPC
 #include <rpc/rpc_com.h>
 #endif
+#include "export.h"
 
 void my_svc_run(void);
-void cache_set_fds(fd_set *fdset);
-int cache_process_req(fd_set *readfds);
 
 #if defined(__GLIBC__) && LONG_MAX != INT_MAX
 /* bug in glibc 2.3.6 and earlier, we need
@@ -101,6 +100,7 @@ my_svc_run(void)
 
 		readfds = svc_fdset;
 		cache_set_fds(&readfds);
+		v4clients_set_fds(&readfds);
 
 		selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, (struct timeval *) 0);
@@ -116,6 +116,7 @@ my_svc_run(void)
 
 		default:
 			selret -= cache_process_req(&readfds);
+			selret -= v4clients_process(&readfds);
 			if (selret)
 				svc_getreqset(&readfds);
 		}
diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c
index 606b912d..ed82b8f2 100644
--- a/utils/statd/sm-notify.c
+++ b/utils/statd/sm-notify.c
@@ -901,7 +901,7 @@ find_host(uint32_t xid)
 }
 
 /*
- * Record pid in /var/run/sm-notify.pid
+ * Record pid in /run/sm-notify.pid
  * This file should remain until a reboot, even if the
  * program exits.
  * If file already exists, fail.
@@ -913,7 +913,7 @@ static int record_pid(void)
 	int fd;
 
 	(void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid());
-	fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
+	fd = open("/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
 	if (fd < 0)
 		return 0;
 
diff --git a/utils/statd/start-statd b/utils/statd/start-statd
index 54ced822..2baf73c3 100755
--- a/utils/statd/start-statd
+++ b/utils/statd/start-statd
@@ -1,18 +1,18 @@
 #!/bin/sh
 # nfsmount calls this script when mounting a filesystem with locking
 # enabled, but when statd does not seem to be running (based on
-# /var/run/rpc.statd.pid).
+# /run/rpc.statd.pid).
 # It should run statd with whatever flags are apropriate for this
 # site.
 PATH="/sbin:/usr/sbin:/bin:/usr/bin"
 
 # Use flock to serialize the running of this script
-exec 9> /var/run/rpc.statd.lock
+exec 9> /run/rpc.statd.lock
 flock -e 9
 
-if [ -s /var/run/rpc.statd.pid ] &&
-       [ 1`cat /var/run/rpc.statd.pid` -gt 1 ] &&
-       kill -0 `cat /var/run/rpc.statd.pid` > /dev/null 2>&1
+if [ -s /run/rpc.statd.pid ] &&
+       [ 1`cat /run/rpc.statd.pid` -gt 1 ] &&
+       kill -0 `cat /run/rpc.statd.pid` > /dev/null 2>&1
 then
     # statd already running - must have been slow to respond.
     exit 0
diff --git a/utils/statd/statd.c b/utils/statd/statd.c
index 32169d47..a469a67a 100644
--- a/utils/statd/statd.c
+++ b/utils/statd/statd.c
@@ -161,7 +161,7 @@ usage(void)
 	fprintf(stderr,"      -H                   Specify a high-availability callout program.\n");
 }
 
-static const char *pidfile = "/var/run/rpc.statd.pid";
+static const char *pidfile = "/run/rpc.statd.pid";
 
 int pidfd = -1;
 static void create_pidfile(void)
diff --git a/utils/statd/statd.man b/utils/statd/statd.man
index ecd3e889..7441ffde 100644
--- a/utils/statd/statd.man
+++ b/utils/statd/statd.man
@@ -440,7 +440,7 @@ directory containing notify list
 .I /var/lib/nfs/state
 NSM state number for this host
 .TP 2.5i
-.I /var/run/run.statd.pid
+.I /run/run.statd.pid
 pid file
 .TP 2.5i
 .I /etc/netconfig