b56a23
diff --git a/nfs.conf b/nfs.conf
b56a23
index 05247ff9..86ed7d53 100644
b56a23
--- a/nfs.conf
b56a23
+++ b/nfs.conf
b56a23
@@ -38,6 +38,8 @@ use-gss-proxy=1
b56a23
 # reverse-lookup=n
b56a23
 # state-directory-path=/var/lib/nfs
b56a23
 # ha-callout=
b56a23
+# cache-use-ipaddr=n
b56a23
+# ttl=1800
b56a23
 #
b56a23
 [nfsdcld]
b56a23
 # debug=0
b56a23
diff --git a/support/export/Makefile.am b/support/export/Makefile.am
b56a23
index 13f7a49c..d6ee502f 100644
b56a23
--- a/support/export/Makefile.am
b56a23
+++ b/support/export/Makefile.am
b56a23
@@ -11,7 +11,8 @@ EXTRA_DIST	= mount.x
b56a23
 
b56a23
 noinst_LIBRARIES = libexport.a
b56a23
 libexport_a_SOURCES = client.c export.c hostname.c \
b56a23
-		      xtab.c mount_clnt.c mount_xdr.c
b56a23
+		      xtab.c mount_clnt.c mount_xdr.c \
b56a23
+			  cache.c auth.c v4root.c v4clients.c
b56a23
 BUILT_SOURCES 	= $(GENFILES)
b56a23
 
b56a23
 noinst_HEADERS = mount.h
b56a23
diff --git a/utils/mountd/auth.c b/support/export/auth.c
b56a23
similarity index 98%
b56a23
rename from utils/mountd/auth.c
b56a23
rename to support/export/auth.c
b56a23
index 8299256e..73ad6f73 100644
b56a23
--- a/utils/mountd/auth.c
b56a23
+++ b/support/export/auth.c
b56a23
@@ -22,7 +22,7 @@
b56a23
 #include "misc.h"
b56a23
 #include "nfslib.h"
b56a23
 #include "exportfs.h"
b56a23
-#include "mountd.h"
b56a23
+#include "export.h"
b56a23
 #include "v4root.h"
b56a23
 
b56a23
 enum auth_error
b56a23
@@ -43,11 +43,13 @@ extern int use_ipaddr;
b56a23
 
b56a23
 extern struct state_paths etab;
b56a23
 
b56a23
+/*
b56a23
 void
b56a23
 auth_init(void)
b56a23
 {
b56a23
 	auth_reload();
b56a23
 }
b56a23
+*/
b56a23
 
b56a23
 /*
b56a23
  * A client can match many different netgroups and it's tough to know
b56a23
@@ -64,6 +66,10 @@ check_useipaddr(void)
b56a23
 	int old_use_ipaddr = use_ipaddr;
b56a23
 	unsigned int len = 0;
b56a23
 
b56a23
+	if (use_ipaddr > 1)
b56a23
+		/* fixed - don't check */
b56a23
+		return;
b56a23
+
b56a23
 	/* add length of m_hostname + 1 for the comma */
b56a23
 	for (clp = clientlist[MCL_NETGROUP]; clp; clp = clp->m_next)
b56a23
 		len += (strlen(clp->m_hostname) + 1);
b56a23
diff --git a/utils/mountd/cache.c b/support/export/cache.c
b56a23
similarity index 95%
b56a23
rename from utils/mountd/cache.c
b56a23
rename to support/export/cache.c
b56a23
index c73e29be..98d50828 100644
b56a23
--- a/utils/mountd/cache.c
b56a23
+++ b/support/export/cache.c
b56a23
@@ -29,21 +29,18 @@
b56a23
 #include "misc.h"
b56a23
 #include "nfslib.h"
b56a23
 #include "exportfs.h"
b56a23
-#include "mountd.h"
b56a23
-#include "fsloc.h"
b56a23
+#include "export.h"
b56a23
 #include "pseudoflavors.h"
b56a23
 #include "xcommon.h"
b56a23
 
b56a23
+#ifdef HAVE_JUNCTION_SUPPORT
b56a23
+#include "../../utils/mountd/fsloc.h"
b56a23
+#endif
b56a23
+
b56a23
 #ifdef USE_BLKID
b56a23
 #include "blkid/blkid.h"
b56a23
 #endif
b56a23
 
b56a23
-/*
b56a23
- * Invoked by RPC service loop
b56a23
- */
b56a23
-void	cache_set_fds(fd_set *fdset);
b56a23
-int	cache_process_req(fd_set *readfds);
b56a23
-
b56a23
 enum nfsd_fsid {
b56a23
 	FSID_DEV = 0,
b56a23
 	FSID_NUM,
b56a23
@@ -63,7 +60,6 @@ enum nfsd_fsid {
b56a23
  * Record is terminated with newline.
b56a23
  *
b56a23
  */
b56a23
-static int cache_export_ent(char *buf, int buflen, char *domain, struct exportent *exp, char *path);
b56a23
 
b56a23
 #define INITIAL_MANAGED_GROUPS 100
b56a23
 
b56a23
@@ -81,6 +77,7 @@ static void auth_unix_ip(int f)
b56a23
 	char class[20];
b56a23
 	char ipaddr[INET6_ADDRSTRLEN + 1];
b56a23
 	char *client = NULL;
b56a23
+	struct addrinfo *ai = NULL;
b56a23
 	struct addrinfo *tmp = NULL;
b56a23
 	char buf[RPC_CHAN_BUF_SIZE], *bp;
b56a23
 	int blen;
b56a23
@@ -106,21 +103,26 @@ static void auth_unix_ip(int f)
b56a23
 
b56a23
 	auth_reload();
b56a23
 
b56a23
-	/* addr is a valid, interesting address, find the domain name... */
b56a23
-	if (!use_ipaddr) {
b56a23
-		struct addrinfo *ai = NULL;
b56a23
-
b56a23
-		ai = client_resolve(tmp->ai_addr);
b56a23
-		if (ai) {
b56a23
-			client = client_compose(ai);
b56a23
-			freeaddrinfo(ai);
b56a23
-		}
b56a23
+	/* addr is a valid address, find the domain name... */
b56a23
+	ai = client_resolve(tmp->ai_addr);
b56a23
+	if (ai) {
b56a23
+		client = client_compose(ai);
b56a23
+		freeaddrinfo(ai);
b56a23
 	}
b56a23
+	if (!client)
b56a23
+		xlog(D_AUTH, "failed authentication for IP %s", ipaddr);
b56a23
+	else if	(!use_ipaddr)
b56a23
+		xlog(D_AUTH, "successful authentication for IP %s as %s",
b56a23
+		     ipaddr, *client ? client : "DEFAULT");
b56a23
+	else
b56a23
+		xlog(D_AUTH, "successful authentication for IP %s",
b56a23
+			     ipaddr);
b56a23
+
b56a23
 	bp = buf; blen = sizeof(buf);
b56a23
 	qword_add(&bp, &blen, "nfsd");
b56a23
 	qword_add(&bp, &blen, ipaddr);
b56a23
-	qword_adduint(&bp, &blen, time(0) + DEFAULT_TTL);
b56a23
-	if (use_ipaddr) {
b56a23
+	qword_adduint(&bp, &blen, time(0) + default_ttl);
b56a23
+	if (use_ipaddr && client) {
b56a23
 		memmove(ipaddr + 1, ipaddr, strlen(ipaddr) + 1);
b56a23
 		ipaddr[0] = '$';
b56a23
 		qword_add(&bp, &blen, ipaddr);
b56a23
@@ -192,7 +194,7 @@ static void auth_unix_gid(int f)
b56a23
 
b56a23
 	bp = buf; blen = sizeof(buf);
b56a23
 	qword_adduint(&bp, &blen, uid);
b56a23
-	qword_adduint(&bp, &blen, time(0) + DEFAULT_TTL);
b56a23
+	qword_adduint(&bp, &blen, time(0) + default_ttl);
b56a23
 	if (rv >= 0) {
b56a23
 		qword_adduint(&bp, &blen, ngroups);
b56a23
 		for (i=0; i
b56a23
@@ -688,7 +690,6 @@ static void nfsd_fh(int f)
b56a23
 	char *found_path = NULL;
b56a23
 	nfs_export *exp;
b56a23
 	int i;
b56a23
-	int dev_missing = 0;
b56a23
 	char buf[RPC_CHAN_BUF_SIZE], *bp;
b56a23
 	int blen;
b56a23
 
b56a23
@@ -755,11 +756,6 @@ static void nfsd_fh(int f)
b56a23
 			if (!is_ipaddr_client(dom)
b56a23
 					&& !namelist_client_matches(exp, dom))
b56a23
 				continue;
b56a23
-			if (exp->m_export.e_mountpoint &&
b56a23
-			    !is_mountpoint(exp->m_export.e_mountpoint[0]?
b56a23
-					   exp->m_export.e_mountpoint:
b56a23
-					   exp->m_export.e_path))
b56a23
-				dev_missing ++;
b56a23
 
b56a23
 			if (!match_fsid(&parsed, exp, path))
b56a23
 				continue;
b56a23
@@ -794,7 +790,7 @@ static void nfsd_fh(int f)
b56a23
 	    !is_mountpoint(found->e_mountpoint[0]?
b56a23
 			   found->e_mountpoint:
b56a23
 			   found->e_path)) {
b56a23
-		/* Cannot export this yet 
b56a23
+		/* Cannot export this yet
b56a23
 		 * should log a warning, but need to rate limit
b56a23
 		   xlog(L_WARNING, "%s not exported as %d not a mountpoint",
b56a23
 		   found->e_path, found->e_mountpoint);
b56a23
@@ -802,16 +798,6 @@ static void nfsd_fh(int f)
b56a23
 		/* FIXME we need to make sure we re-visit this later */
b56a23
 		goto out;
b56a23
 	}
b56a23
-	if (!found && dev_missing) {
b56a23
-		/* The missing dev could be what we want, so just be
b56a23
-		 * quite rather than returning stale yet
b56a23
-		 */
b56a23
-		goto out;
b56a23
-	}
b56a23
-
b56a23
-	if (found)
b56a23
-		if (cache_export_ent(buf, sizeof(buf), dom, found, found_path) < 0)
b56a23
-			found = 0;
b56a23
 
b56a23
 	bp = buf; blen = sizeof(buf);
b56a23
 	qword_add(&bp, &blen, dom);
b56a23
@@ -831,6 +817,8 @@ static void nfsd_fh(int f)
b56a23
 	qword_addeol(&bp, &blen);
b56a23
 	if (blen <= 0 || write(f, buf, bp - buf) != bp - buf)
b56a23
 		xlog(L_ERROR, "nfsd_fh: error writing reply");
b56a23
+	if (!found)
b56a23
+		xlog(D_AUTH, "denied access to %s", *dom == '$' ? dom+1 : dom);
b56a23
 out:
b56a23
 	if (found_path)
b56a23
 		free(found_path);
b56a23
@@ -839,6 +827,7 @@ out:
b56a23
 	xlog(D_CALL, "nfsd_fh: found %p path %s", found, found ? found->e_path : NULL);
b56a23
 }
b56a23
 
b56a23
+#ifdef HAVE_JUNCTION_SUPPORT
b56a23
 static void write_fsloc(char **bp, int *blen, struct exportent *ep)
b56a23
 {
b56a23
 	struct servers *servers;
b56a23
@@ -861,7 +850,7 @@ static void write_fsloc(char **bp, int *blen, struct exportent *ep)
b56a23
 	qword_addint(bp, blen, servers->h_referral);
b56a23
 	release_replicas(servers);
b56a23
 }
b56a23
-
b56a23
+#endif
b56a23
 static void write_secinfo(char **bp, int *blen, struct exportent *ep, int flag_mask)
b56a23
 {
b56a23
 	struct sec_entry *p;
b56a23
@@ -890,7 +879,7 @@ static int dump_to_cache(int f, char *buf, int buflen, char *domain,
b56a23
 	time_t now = time(0);
b56a23
 
b56a23
 	if (ttl <= 1)
b56a23
-		ttl = DEFAULT_TTL;
b56a23
+		ttl = default_ttl;
b56a23
 
b56a23
 	qword_add(&bp, &blen, domain);
b56a23
 	qword_add(&bp, &blen, path);
b56a23
@@ -903,7 +892,10 @@ static int dump_to_cache(int f, char *buf, int buflen, char *domain,
b56a23
 		qword_addint(&bp, &blen, exp->e_anonuid);
b56a23
 		qword_addint(&bp, &blen, exp->e_anongid);
b56a23
 		qword_addint(&bp, &blen, exp->e_fsid);
b56a23
+
b56a23
+#ifdef HAVE_JUNCTION_SUPPORT
b56a23
 		write_fsloc(&bp, &blen, exp);
b56a23
+#endif
b56a23
 		write_secinfo(&bp, &blen, exp, flag_mask);
b56a23
 		if (exp->e_uuid == NULL || different_fs) {
b56a23
 			char u[16];
b56a23
@@ -917,8 +909,13 @@ static int dump_to_cache(int f, char *buf, int buflen, char *domain,
b56a23
 			qword_add(&bp, &blen, "uuid");
b56a23
 			qword_addhex(&bp, &blen, u, 16);
b56a23
 		}
b56a23
-	} else
b56a23
+		xlog(D_AUTH, "granted access to %s for %s",
b56a23
+		     path, *domain == '$' ? domain+1 : domain);
b56a23
+	} else {
b56a23
 		qword_adduint(&bp, &blen, now + ttl);
b56a23
+		xlog(D_AUTH, "denied access to %s for %s",
b56a23
+		     path, *domain == '$' ? domain+1 : domain);
b56a23
+	}
b56a23
 	qword_addeol(&bp, &blen);
b56a23
 	if (blen <= 0) return -1;
b56a23
 	if (write(f, buf, bp - buf) != bp - buf) return -1;
b56a23
@@ -1421,6 +1418,40 @@ int cache_process_req(fd_set *readfds)
b56a23
 	return cnt;
b56a23
 }
b56a23
 
b56a23
+/**
b56a23
+ * cache_process_loop - process incoming upcalls
b56a23
+ */
b56a23
+void cache_process_loop(void)
b56a23
+{
b56a23
+	fd_set	readfds;
b56a23
+	int	selret;
b56a23
+
b56a23
+	FD_ZERO(&readfds);
b56a23
+
b56a23
+	for (;;) {
b56a23
+
b56a23
+		cache_set_fds(&readfds);
b56a23
+		v4clients_set_fds(&readfds);
b56a23
+
b56a23
+		selret = select(FD_SETSIZE, &readfds,
b56a23
+				(void *) 0, (void *) 0, (struct timeval *) 0);
b56a23
+
b56a23
+
b56a23
+		switch (selret) {
b56a23
+		case -1:
b56a23
+			if (errno == EINTR || errno == ECONNREFUSED
b56a23
+			 || errno == ENETUNREACH || errno == EHOSTUNREACH)
b56a23
+				continue;
b56a23
+			xlog(L_ERROR, "my_svc_run() - select: %m");
b56a23
+			return;
b56a23
+
b56a23
+		default:
b56a23
+			cache_process_req(&readfds);
b56a23
+			v4clients_process(&readfds);
b56a23
+		}
b56a23
+	}
b56a23
+}
b56a23
+
b56a23
 
b56a23
 /*
b56a23
  * Give IP->domain and domain+path->options to kernel
b56a23
diff --git a/support/export/export.h b/support/export/export.h
b56a23
new file mode 100644
b56a23
index 00000000..8d5a0d30
b56a23
--- /dev/null
b56a23
+++ b/support/export/export.h
b56a23
@@ -0,0 +1,41 @@
b56a23
+/*
b56a23
+ * Copyright (C) 2021 Red Hat <nfs@redhat.com>
b56a23
+ *
b56a23
+ * support/export/export.h
b56a23
+ *
b56a23
+ * Declarations for export support
b56a23
+ */
b56a23
+
b56a23
+#ifndef EXPORT_H
b56a23
+#define EXPORT_H
b56a23
+
b56a23
+#include "nfslib.h"
b56a23
+#include "exportfs.h"
b56a23
+
b56a23
+unsigned int	auth_reload(void);
b56a23
+nfs_export *	auth_authenticate(const char *what,
b56a23
+					const struct sockaddr *caller,
b56a23
+					const char *path);
b56a23
+
b56a23
+void		cache_open(void);
b56a23
+void		cache_set_fds(fd_set *fdset);
b56a23
+int		cache_process_req(fd_set *readfds);
b56a23
+void		cache_process_loop(void);
b56a23
+
b56a23
+void		v4clients_init(void);
b56a23
+void		v4clients_set_fds(fd_set *fdset);
b56a23
+int		v4clients_process(fd_set *fdset);
b56a23
+
b56a23
+struct nfs_fh_len *
b56a23
+		cache_get_filehandle(nfs_export *exp, int len, char *p);
b56a23
+int		cache_export(nfs_export *exp, char *path);
b56a23
+
b56a23
+bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai);
b56a23
+bool namelist_client_matches(nfs_export *exp, char *dom);
b56a23
+bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai);
b56a23
+
b56a23
+static inline bool is_ipaddr_client(char *dom)
b56a23
+{
b56a23
+	return dom[0] == '$';
b56a23
+}
b56a23
+#endif /* EXPORT__H */
b56a23
diff --git a/support/export/v4clients.c b/support/export/v4clients.c
b56a23
new file mode 100644
b56a23
index 00000000..dd985463
b56a23
--- /dev/null
b56a23
+++ b/support/export/v4clients.c
b56a23
@@ -0,0 +1,227 @@
b56a23
+/*
b56a23
+ * support/export/v4clients.c
b56a23
+ *
b56a23
+ * Montior clients appearing in, and disappearing from, /proc/fs/nfsd/clients
b56a23
+ * and log relevant information.
b56a23
+ */
b56a23
+
b56a23
+#include <unistd.h>
b56a23
+#include <stdlib.h>
b56a23
+#include <sys/inotify.h>
b56a23
+#include <errno.h>
b56a23
+#include "export.h"
b56a23
+
b56a23
+/* search.h declares 'struct entry' and nfs_prot.h
b56a23
+ * does too.  Easiest fix is to trick search.h into
b56a23
+ * calling its struct "struct Entry".
b56a23
+ */
b56a23
+#define entry Entry
b56a23
+#include <search.h>
b56a23
+#undef entry
b56a23
+
b56a23
+static int clients_fd = -1;
b56a23
+
b56a23
+void v4clients_init(void)
b56a23
+{
b56a23
+	if (clients_fd >= 0)
b56a23
+		return;
b56a23
+	clients_fd = inotify_init1(IN_NONBLOCK);
b56a23
+	if (clients_fd < 0) {
b56a23
+		xlog_err("Unable to initialise v4clients watcher: %s\n",
b56a23
+			 strerror(errno));
b56a23
+		return;
b56a23
+	}
b56a23
+	if (inotify_add_watch(clients_fd, "/proc/fs/nfsd/clients",
b56a23
+			      IN_CREATE | IN_DELETE) < 0) {
b56a23
+		xlog_err("Unable to watch /proc/fs/nfsd/clients: %s\n",
b56a23
+			 strerror(errno));
b56a23
+		close(clients_fd);
b56a23
+		clients_fd = -1;
b56a23
+		return;
b56a23
+	}
b56a23
+}
b56a23
+
b56a23
+void v4clients_set_fds(fd_set *fdset)
b56a23
+{
b56a23
+	if (clients_fd >= 0)
b56a23
+		FD_SET(clients_fd, fdset);
b56a23
+}
b56a23
+
b56a23
+static void *tree_root;
b56a23
+static int have_unconfirmed;
b56a23
+
b56a23
+struct ent {
b56a23
+	unsigned long num;
b56a23
+	char *clientid;
b56a23
+	char *addr;
b56a23
+	int vers;
b56a23
+	int unconfirmed;
b56a23
+	int wid;
b56a23
+};
b56a23
+
b56a23
+static int ent_cmp(const void *av, const void *bv)
b56a23
+{
b56a23
+	const struct ent *a = av;
b56a23
+	const struct ent *b = bv;
b56a23
+
b56a23
+	if (a->num < b->num)
b56a23
+		return -1;
b56a23
+	if (a->num > b->num)
b56a23
+		return 1;
b56a23
+	return 0;
b56a23
+}
b56a23
+
b56a23
+static void free_ent(struct ent *ent)
b56a23
+{
b56a23
+	free(ent->clientid);
b56a23
+	free(ent->addr);
b56a23
+	free(ent);
b56a23
+}
b56a23
+
b56a23
+static char *dup_line(char *line)
b56a23
+{
b56a23
+	char *ret;
b56a23
+	char *e = strchr(line, '\n');
b56a23
+	if (!e)
b56a23
+		e = line + strlen(line);
b56a23
+	ret = malloc(e - line + 1);
b56a23
+	if (ret) {
b56a23
+		memcpy(ret, line, e - line);
b56a23
+		ret[e-line] = 0;
b56a23
+	}
b56a23
+	return ret;
b56a23
+}
b56a23
+
b56a23
+static void read_info(struct ent *key)
b56a23
+{
b56a23
+	char buf[2048];
b56a23
+	char *path;
b56a23
+	int was_unconfirmed = key->unconfirmed;
b56a23
+	FILE *f;
b56a23
+
b56a23
+	if (asprintf(&path, "/proc/fs/nfsd/clients/%lu/info", key->num) < 0)
b56a23
+		return;
b56a23
+
b56a23
+	f = fopen(path, "r");
b56a23
+	if (!f) {
b56a23
+		free(path);
b56a23
+		return;
b56a23
+	}
b56a23
+	if (key->wid < 0)
b56a23
+		key->wid = inotify_add_watch(clients_fd, path, IN_MODIFY);
b56a23
+
b56a23
+	while (fgets(buf, sizeof(buf), f)) {
b56a23
+		if (strncmp(buf, "clientid: ", 10) == 0) {
b56a23
+			free(key->clientid);
b56a23
+			key->clientid = dup_line(buf+10);
b56a23
+		}
b56a23
+		if (strncmp(buf, "address: ", 9) == 0) {
b56a23
+			free(key->addr);
b56a23
+			key->addr = dup_line(buf+9);
b56a23
+		}
b56a23
+		if (strncmp(buf, "minor version: ", 15) == 0)
b56a23
+			key->vers = atoi(buf+15);
b56a23
+		if (strncmp(buf, "status: ", 8) == 0 &&
b56a23
+		    strstr(buf, " unconfirmed") != NULL) {
b56a23
+			key->unconfirmed = 1;
b56a23
+			have_unconfirmed = 1;
b56a23
+		}
b56a23
+		if (strncmp(buf, "status: ", 8) == 0 &&
b56a23
+		    strstr(buf, " confirmed") != NULL)
b56a23
+			key->unconfirmed = 0;
b56a23
+	}
b56a23
+	fclose(f);
b56a23
+	free(path);
b56a23
+
b56a23
+	if (was_unconfirmed && !key->unconfirmed)
b56a23
+		xlog(L_NOTICE, "v4.%d client attached: %s from %s",
b56a23
+		     key->vers, key->clientid ?: "-none-",
b56a23
+		     key->addr ?: "-none-");
b56a23
+	if (!key->unconfirmed && key->wid >= 0) {
b56a23
+		inotify_rm_watch(clients_fd, key->wid);
b56a23
+		key->wid = -1;
b56a23
+	}
b56a23
+}
b56a23
+
b56a23
+static void add_id(int id)
b56a23
+{
b56a23
+	struct ent **ent;
b56a23
+	struct ent *key;
b56a23
+
b56a23
+	key = calloc(1, sizeof(*key));
b56a23
+	if (!key) {
b56a23
+		return;
b56a23
+	}
b56a23
+	key->num = id;
b56a23
+	key->wid = -1;
b56a23
+
b56a23
+	ent = tsearch(key, &tree_root, ent_cmp);
b56a23
+
b56a23
+	if (!ent || *ent != key)
b56a23
+		/* Already existed, or insertion failed */
b56a23
+		free_ent(key);
b56a23
+	else
b56a23
+		read_info(key);
b56a23
+}
b56a23
+
b56a23
+static void del_id(unsigned long id)
b56a23
+{
b56a23
+	struct ent key = {.num = id};
b56a23
+	struct ent **e, *ent;
b56a23
+
b56a23
+	e = tfind(&key, &tree_root, ent_cmp);
b56a23
+	if (!e || !*e)
b56a23
+		return;
b56a23
+	ent = *e;
b56a23
+	tdelete(ent, &tree_root, ent_cmp);
b56a23
+	if (!ent->unconfirmed)
b56a23
+		xlog(L_NOTICE, "v4.%d client detached: %s from %s",
b56a23
+		     ent->vers, ent->clientid, ent->addr);
b56a23
+	if (ent->wid >= 0)
b56a23
+		inotify_rm_watch(clients_fd, ent->wid);
b56a23
+	free_ent(ent);
b56a23
+}
b56a23
+
b56a23
+static void check_id(unsigned long id)
b56a23
+{
b56a23
+	struct ent key = {.num = id};
b56a23
+	struct ent **e, *ent;
b56a23
+
b56a23
+	e = tfind(&key, &tree_root, ent_cmp);
b56a23
+	if (!e || !*e)
b56a23
+		return;
b56a23
+	ent = *e;
b56a23
+	if (ent->unconfirmed)
b56a23
+		read_info(ent);
b56a23
+}
b56a23
+
b56a23
+int v4clients_process(fd_set *fdset)
b56a23
+{
b56a23
+	char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
b56a23
+	const struct inotify_event *ev;
b56a23
+	ssize_t len;
b56a23
+	char *ptr;
b56a23
+
b56a23
+	if (clients_fd < 0 ||
b56a23
+	    !FD_ISSET(clients_fd, fdset))
b56a23
+		return 0;
b56a23
+
b56a23
+	while ((len = read(clients_fd, buf, sizeof(buf))) > 0) {
b56a23
+		for (ptr = buf; ptr < buf + len;
b56a23
+		     ptr += sizeof(struct inotify_event) + ev->len) {
b56a23
+			int id;
b56a23
+			ev = (const struct inotify_event *)ptr;
b56a23
+
b56a23
+			id = atoi(ev->name);
b56a23
+			if (id <= 0)
b56a23
+				continue;
b56a23
+			if (ev->mask & IN_CREATE)
b56a23
+				add_id(id);
b56a23
+			if (ev->mask & IN_DELETE)
b56a23
+				del_id(id);
b56a23
+			if (ev->mask & IN_MODIFY)
b56a23
+				check_id(id);
b56a23
+		}
b56a23
+	}
b56a23
+	return 1;
b56a23
+}
b56a23
diff --git a/utils/mountd/v4root.c b/support/export/v4root.c
b56a23
similarity index 99%
b56a23
rename from utils/mountd/v4root.c
b56a23
rename to support/export/v4root.c
b56a23
index 8ec33fb0..4d33117f 100644
b56a23
--- a/utils/mountd/v4root.c
b56a23
+++ b/support/export/v4root.c
b56a23
@@ -47,7 +47,7 @@ static nfs_export pseudo_root = {
b56a23
 		.e_nsqgids = 0,
b56a23
 		.e_fsid = 0,
b56a23
 		.e_mountpoint = NULL,
b56a23
-		.e_ttl = DEFAULT_TTL,
b56a23
+		.e_ttl = 0,
b56a23
 	},
b56a23
 	.m_exported = 0,
b56a23
 	.m_xtabent = 1,
b56a23
@@ -86,6 +86,7 @@ v4root_create(char *path, nfs_export *export)
b56a23
 	struct exportent *curexp = &export->m_export;
b56a23
 
b56a23
 	dupexportent(&eep, &pseudo_root.m_export);
b56a23
+	eep.e_ttl = default_ttl;
b56a23
 	eep.e_hostname = curexp->e_hostname;
b56a23
 	strncpy(eep.e_path, path, sizeof(eep.e_path)-1);
b56a23
 	if (strcmp(path, "/") != 0)
b56a23
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
b56a23
index 4e0d9d13..bfae1957 100644
b56a23
--- a/support/include/exportfs.h
b56a23
+++ b/support/include/exportfs.h
b56a23
@@ -105,7 +105,8 @@ typedef struct mexport {
b56a23
 } nfs_export;
b56a23
 
b56a23
 #define HASH_TABLE_SIZE 1021
b56a23
-#define DEFAULT_TTL	(30 * 60)
b56a23
+
b56a23
+extern int default_ttl;
b56a23
 
b56a23
 typedef struct _exp_hash_entry {
b56a23
 	nfs_export * p_first;
b56a23
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
b56a23
index a7582cae..4dd2e5d3 100644
b56a23
--- a/support/nfs/exports.c
b56a23
+++ b/support/nfs/exports.c
b56a23
@@ -47,6 +47,8 @@ struct flav_info flav_map[] = {
b56a23
 
b56a23
 const int flav_map_size = sizeof(flav_map)/sizeof(flav_map[0]);
b56a23
 
b56a23
+int default_ttl = 30 * 60;
b56a23
+
b56a23
 static char	*efname = NULL;
b56a23
 static XFILE	*efp = NULL;
b56a23
 static int	first;
b56a23
@@ -100,7 +102,7 @@ static void init_exportent (struct exportent *ee, int fromkernel)
b56a23
 	ee->e_nsquids = 0;
b56a23
 	ee->e_nsqgids = 0;
b56a23
 	ee->e_uuid = NULL;
b56a23
-	ee->e_ttl = DEFAULT_TTL;
b56a23
+	ee->e_ttl = default_ttl;
b56a23
 }
b56a23
 
b56a23
 struct exportent *
b56a23
diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
b56a23
index 498d93a9..aa4630bb 100644
b56a23
--- a/systemd/nfs.conf.man
b56a23
+++ b/systemd/nfs.conf.man
b56a23
@@ -157,6 +157,8 @@ Recognized values:
b56a23
 .BR port ,
b56a23
 .BR threads ,
b56a23
 .BR reverse-lookup ,
b56a23
+.BR cache-use-upaddr ,
b56a23
+.BR ttl ,
b56a23
 .BR state-directory-path ,
b56a23
 .BR ha-callout .
b56a23
 
b56a23
@@ -166,6 +168,14 @@ section, are used to configure mountd.  See
b56a23
 .BR rpc.mountd (8)
b56a23
 for details.
b56a23
 
b56a23
+Note that setting 
b56a23
+.B "\[dq]debug = auth\[dq]"
b56a23
+for
b56a23
+.B mountd
b56a23
+is equivalent to providing the
b56a23
+.B \-\-log\-auth
b56a23
+option.
b56a23
+
b56a23
 The
b56a23
 .B state-directory-path
b56a23
 value in the
b56a23
diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am
b56a23
index 73eeb3f3..c41f06de 100644
b56a23
--- a/utils/mountd/Makefile.am
b56a23
+++ b/utils/mountd/Makefile.am
b56a23
@@ -13,8 +13,8 @@ KPREFIX		= @kprefix@
b56a23
 sbin_PROGRAMS	= mountd
b56a23
 
b56a23
 noinst_HEADERS = fsloc.h
b56a23
-mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \
b56a23
-		 svc_run.c fsloc.c v4root.c mountd.h
b56a23
+mountd_SOURCES = mountd.c mount_dispatch.c rmtab.c \
b56a23
+		 svc_run.c fsloc.c mountd.h
b56a23
 mountd_LDADD = ../../support/export/libexport.a \
b56a23
 	       ../../support/nfs/libnfs.la \
b56a23
 	       ../../support/misc/libmisc.a \
b56a23
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
b56a23
index 0b891121..2b342377 100644
b56a23
--- a/utils/mountd/mountd.c
b56a23
+++ b/utils/mountd/mountd.c
b56a23
@@ -30,6 +30,7 @@
b56a23
 #include "rpcmisc.h"
b56a23
 #include "pseudoflavors.h"
b56a23
 #include "nfslib.h"
b56a23
+#include "export.h"
b56a23
 
b56a23
 extern void my_svc_run(void);
b56a23
 
b56a23
@@ -73,8 +74,12 @@ static struct option longopts[] =
b56a23
 	{ "reverse-lookup", 0, 0, 'r' },
b56a23
 	{ "manage-gids", 0, 0, 'g' },
b56a23
 	{ "no-udp", 0, 0, 'u' },
b56a23
+	{ "log-auth", 0, 0, 'l'},
b56a23
+	{ "cache-use-ipaddr", 0, 0, 'i'},
b56a23
+	{ "ttl", 1, 0, 'T'},
b56a23
 	{ NULL, 0, 0, 0 }
b56a23
 };
b56a23
+static char shortopts[] = "o:nFd:p:P:hH:N:V:vurs:t:gliT:";
b56a23
 
b56a23
 #define NFSVERSBIT(vers)	(0x1 << (vers - 1))
b56a23
 #define NFSVERSBIT_ALL		(NFSVERSBIT(2) | NFSVERSBIT(3) | NFSVERSBIT(4))
b56a23
@@ -669,6 +674,7 @@ main(int argc, char **argv)
b56a23
 	int	port = 0;
b56a23
 	int	descriptors = 0;
b56a23
 	int	c;
b56a23
+	int	ttl;
b56a23
 	int	vers;
b56a23
 	struct sigaction sa;
b56a23
 	struct rlimit rlim;
b56a23
@@ -687,6 +693,8 @@ main(int argc, char **argv)
b56a23
 	num_threads = conf_get_num("mountd", "threads", num_threads);
b56a23
 	reverse_resolve = conf_get_bool("mountd", "reverse-lookup", reverse_resolve);
b56a23
 	ha_callout_prog = conf_get_str("mountd", "ha-callout");
b56a23
+	if (conf_get_bool("mountd", "cache-use-ipaddr", 0))
b56a23
+		use_ipaddr = 2;
b56a23
 
b56a23
 	s = conf_get_str("mountd", "state-directory-path");
b56a23
 	if (s && !state_setup_basedir(argv[0], s))
b56a23
@@ -710,10 +718,13 @@ main(int argc, char **argv)
b56a23
 			NFSCTL_VERUNSET(nfs_version, vers);
b56a23
 	}
b56a23
 
b56a23
+	ttl = conf_get_num("mountd", "ttl", default_ttl);
b56a23
+	if (ttl > 0)
b56a23
+		default_ttl = ttl;
b56a23
 
b56a23
 	/* Parse the command line options and arguments. */
b56a23
 	opterr = 0;
b56a23
-	while ((c = getopt_long(argc, argv, "o:nFd:p:P:hH:N:V:vurs:t:g", longopts, NULL)) != EOF)
b56a23
+	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != EOF)
b56a23
 		switch (c) {
b56a23
 		case 'g':
b56a23
 			manage_gids = 1;
b56a23
@@ -784,6 +795,21 @@ main(int argc, char **argv)
b56a23
 		case 'u':
b56a23
 			NFSCTL_UDPUNSET(_rpcprotobits);
b56a23
 			break;
b56a23
+		case 'l':
b56a23
+			xlog_sconfig("auth", 1);
b56a23
+			break;
b56a23
+		case 'i':
b56a23
+			use_ipaddr = 2;
b56a23
+			break;
b56a23
+		case 'T':
b56a23
+			ttl = atoi(optarg);
b56a23
+			if (ttl <= 0) {
b56a23
+				fprintf(stderr, "%s: bad ttl number of seconds: %s\n",
b56a23
+					argv[0], optarg);
b56a23
+				usage(argv[0], 1);
b56a23
+			}
b56a23
+			default_ttl = ttl;
b56a23
+			break;
b56a23
 		case 0:
b56a23
 			break;
b56a23
 		case '?':
b56a23
@@ -888,6 +914,8 @@ main(int argc, char **argv)
b56a23
 	if (num_threads > 1)
b56a23
 		fork_workers();
b56a23
 
b56a23
+	v4clients_init();
b56a23
+
b56a23
 	xlog(L_NOTICE, "Version " VERSION " starting");
b56a23
 	my_svc_run();
b56a23
 
b56a23
@@ -903,6 +931,7 @@ usage(const char *prog, int n)
b56a23
 {
b56a23
 	fprintf(stderr,
b56a23
 "Usage: %s [-F|--foreground] [-h|--help] [-v|--version] [-d kind|--debug kind]\n"
b56a23
+"	[-l|--log-auth] [-i|--cache-use-ipaddr] [-T|--ttl ttl]\n"
b56a23
 "	[-o num|--descriptors num]\n"
b56a23
 "	[-p|--port port] [-V version|--nfs-version version]\n"
b56a23
 "	[-N version|--no-nfs-version version] [-n|--no-tcp]\n"
b56a23
diff --git a/utils/mountd/mountd.h b/utils/mountd/mountd.h
b56a23
index f058f01d..d3077531 100644
b56a23
--- a/utils/mountd/mountd.h
b56a23
+++ b/utils/mountd/mountd.h
b56a23
@@ -60,9 +60,4 @@ bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai);
b56a23
 bool namelist_client_matches(nfs_export *exp, char *dom);
b56a23
 bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai);
b56a23
 
b56a23
-static inline bool is_ipaddr_client(char *dom)
b56a23
-{
b56a23
-	return dom[0] == '$';
b56a23
-}
b56a23
-
b56a23
 #endif /* MOUNTD_H */
b56a23
diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man
b56a23
index 8a7943f8..2a91e193 100644
b56a23
--- a/utils/mountd/mountd.man
b56a23
+++ b/utils/mountd/mountd.man
b56a23
@@ -13,24 +13,24 @@ The
b56a23
 .B rpc.mountd
b56a23
 daemon implements the server side of the NFS MOUNT protocol,
b56a23
 an NFS side protocol used by NFS version 2 [RFC1094] and NFS version 3 [RFC1813].
b56a23
+It also responds to requests from the Linux kernel to authenticate
b56a23
+clients and provides details of access permissions.
b56a23
 .PP
b56a23
-An NFS server maintains a table of local physical file systems
b56a23
-that are accessible to NFS clients.
b56a23
-Each file system in this table is referred to as an
b56a23
-.IR "exported file system" ,
b56a23
-or
b56a23
-.IR export ,
b56a23
-for short.
b56a23
-.PP
b56a23
-Each file system in the export table has an access control list.
b56a23
-.B rpc.mountd
b56a23
-uses these access control lists to determine
b56a23
-whether an NFS client is permitted to access a given file system.
b56a23
-For details on how to manage your NFS server's export table, see the
b56a23
-.BR exports (5)
b56a23
-and
b56a23
-.BR exportfs (8)
b56a23
-man pages.
b56a23
+The NFS server
b56a23
+.RI ( nfsd )
b56a23
+maintains a cache of authentication and authorization information which
b56a23
+is used to identify the source of each request, and then what access
b56a23
+permissions that source has to any local filesystem.  When required
b56a23
+information is not found in the cache, the server sends a request to
b56a23
+.B mountd
b56a23
+to fill in the missing information.  Mountd uses a table of information
b56a23
+stored in
b56a23
+.B /var/lib/nfs/etab
b56a23
+and maintained by
b56a23
+.BR exportfs (8),
b56a23
+possibly based on the contents of 
b56a23
+.BR exports (5),
b56a23
+to respond to each request.
b56a23
 .SS Mounting exported NFS File Systems
b56a23
 The NFS MOUNT protocol has several procedures.
b56a23
 The most important of these are
b56a23
@@ -78,11 +78,69 @@ A client may continue accessing an export even after invoking UMNT.
b56a23
 If the client reboots without sending a UMNT request, stale entries
b56a23
 remain for that client in
b56a23
 .IR /var/lib/nfs/rmtab .
b56a23
+.SS Mounting File Systems with NFSv4
b56a23
+Version 4 (and later) of NFS does not use a separate NFS MOUNT
b56a23
+protocol.  Instead mounting is performed using regular NFS requests
b56a23
+handled by the NFS server in the Linux kernel
b56a23
+.RI ( nfsd ).
b56a23
+Consequently
b56a23
+.I /var/lib/nfs/rmtab
b56a23
+is not updated to reflect any NFSv4 activity.
b56a23
 .SH OPTIONS
b56a23
 .TP
b56a23
 .B \-d kind " or " \-\-debug kind
b56a23
 Turn on debugging. Valid kinds are: all, auth, call, general and parse.
b56a23
 .TP
b56a23
+.BR \-l " or " \-\-log\-auth
b56a23
+Enable logging of responses to authentication and access requests from
b56a23
+nfsd.  Each response is then cached by the kernel for 30 minutes (or as set by
b56a23
+.B \-\-ttl
b56a23
+below), and will be refreshed after 15 minutes (half the ttl time) if
b56a23
+the relevant client remains active.
b56a23
+Note that
b56a23
+.B -l
b56a23
+is equivalent to
b56a23
+.B "-d auth"
b56a23
+and so can be enabled in
b56a23
+.B /etc/nfs.conf
b56a23
+with
b56a23
+.B "\[dq]debug = auth\[dq]"
b56a23
+in the
b56a23
+.B "[mountd]"
b56a23
+section.
b56a23
+.IP
b56a23
+.B rpc.mountd
b56a23
+will always log authentication responses to MOUNT requests when NFSv3 is
b56a23
+used, but to get similar logs for NFSv4, this option is required.
b56a23
+.TP
b56a23
+.BR \-i " or " \-\-cache\-use\-ipaddr
b56a23
+Normally each client IP address is matched against each host identifier
b56a23
+(name, wildcard, netgroup etc) found in
b56a23
+.B /etc/exports
b56a23
+and a combined identity is formed from all matching identifiers.
b56a23
+Often many clients will map to the same combined identity so performing
b56a23
+this mapping reduces the number of distinct access details that the
b56a23
+kernel needs to store.
b56a23
+Specifying the
b56a23
+.B \-i
b56a23
+option suppresses this mapping so that access to each filesystem is
b56a23
+requested and cached separately for each client IP address.  Doing this
b56a23
+can increase the burden of updating the cache slightly, but can make the
b56a23
+log messages produced by the
b56a23
+.B -l
b56a23
+option easier to read.
b56a23
+.TP
b56a23
+.B \-T " or " \-\-ttl
b56a23
+Provide a time-to-live (TTL) for cached information given to the kernel.
b56a23
+The kernel will normally request an update if the information is needed
b56a23
+after half of this time has expired.  Increasing the provided number,
b56a23
+which is in seconds, reduces the rate of cache update requests, and this
b56a23
+is particularly noticeable when these requests are logged with
b56a23
+.BR \-l .
b56a23
+However increasing also means that changes to hostname to address
b56a23
+mappings can take longer to be noticed.
b56a23
+The default TTL is 1800 (30 minutes).
b56a23
+.TP
b56a23
 .B \-F " or " \-\-foreground
b56a23
 Run in foreground (do not daemonize)
b56a23
 .TP
b56a23
@@ -213,9 +271,11 @@ Values recognized in the
b56a23
 .B [mountd]
b56a23
 section include
b56a23
 .BR manage-gids ,
b56a23
+.BR cache\-use\-ipaddr ,
b56a23
 .BR descriptors ,
b56a23
 .BR port ,
b56a23
 .BR threads ,
b56a23
+.BR ttl ,
b56a23
 .BR reverse-lookup ", and"
b56a23
 .BR state-directory-path ,
b56a23
 .B ha-callout
b56a23
@@ -265,5 +325,9 @@ table of clients accessing server's exports
b56a23
 RFC 1094 - "NFS: Network File System Protocol Specification"
b56a23
 .br
b56a23
 RFC 1813 - "NFS Version 3 Protocol Specification"
b56a23
+.br
b56a23
+RFC 7530 - "Network File System (NFS) Version 4 Protocol"
b56a23
+.br
b56a23
+RFC 8881 - "Network File System (NFS) Version 4 Minor Version 1 Protocol"
b56a23
 .SH AUTHOR
b56a23
 Olaf Kirch, H. J. Lu, G. Allan Morris III, and a host of others.
b56a23
diff --git a/utils/mountd/svc_run.c b/utils/mountd/svc_run.c
b56a23
index 41b96d7f..167b9757 100644
b56a23
--- a/utils/mountd/svc_run.c
b56a23
+++ b/utils/mountd/svc_run.c
b56a23
@@ -56,10 +56,9 @@
b56a23
 #ifdef HAVE_LIBTIRPC
b56a23
 #include <rpc/rpc_com.h>
b56a23
 #endif
b56a23
+#include "export.h"
b56a23
 
b56a23
 void my_svc_run(void);
b56a23
-void cache_set_fds(fd_set *fdset);
b56a23
-int cache_process_req(fd_set *readfds);
b56a23
 
b56a23
 #if defined(__GLIBC__) && LONG_MAX != INT_MAX
b56a23
 /* bug in glibc 2.3.6 and earlier, we need
b56a23
@@ -101,6 +100,7 @@ my_svc_run(void)
b56a23
 
b56a23
 		readfds = svc_fdset;
b56a23
 		cache_set_fds(&readfds);
b56a23
+		v4clients_set_fds(&readfds);
b56a23
 
b56a23
 		selret = select(FD_SETSIZE, &readfds,
b56a23
 				(void *) 0, (void *) 0, (struct timeval *) 0);
b56a23
@@ -116,6 +116,7 @@ my_svc_run(void)
b56a23
 
b56a23
 		default:
b56a23
 			selret -= cache_process_req(&readfds);
b56a23
+			selret -= v4clients_process(&readfds);
b56a23
 			if (selret)
b56a23
 				svc_getreqset(&readfds);
b56a23
 		}