walters / rpms / nfs-utils

Forked from rpms/nfs-utils 6 years ago
Clone
Blob Blame History Raw
diff --git a/aclocal/libcap.m4 b/aclocal/libcap.m4
index eabe507..68a624c 100644
--- a/aclocal/libcap.m4
+++ b/aclocal/libcap.m4
@@ -5,11 +5,19 @@ AC_DEFUN([AC_LIBCAP], [
   dnl look for prctl
   AC_CHECK_FUNC([prctl], , )
 
-  dnl look for the library; do not add to LIBS if found
-  AC_CHECK_LIB([cap], [cap_get_proc], [LIBCAP=-lcap], ,)
-  AC_SUBST(LIBCAP)
+  AC_ARG_ENABLE([caps],
+    [AS_HELP_STRING([--disable-caps], [Disable capabilities support])])
+
+  LIBCAP=
+
+  if test "x$enable_caps" != "xno" ; then
+    dnl look for the library; do not add to LIBS if found
+    AC_CHECK_LIB([cap], [cap_get_proc], [LIBCAP=-lcap], ,)
 
-  AC_CHECK_HEADERS([sys/capability.h], ,
-                   [AC_MSG_ERROR([libcap headers not found.])])
+    AC_CHECK_HEADERS([sys/capability.h], ,
+      [test "x$enable_caps" = "xyes" && AC_MSG_ERROR([libcap headers not found.])])
+  fi
+
+  AC_SUBST(LIBCAP)
 
 ])dnl
diff --git a/autogen.sh b/autogen.sh
old mode 100644
new mode 100755
diff --git a/configure.ac b/configure.ac
index b7520d8..d90a88f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,7 +81,7 @@ AC_ARG_ENABLE(nfsv41,
 	if test "$enable_nfsv41" = yes; then
 		AC_DEFINE(NFS41_SUPPORTED, 1, [Define this if you want NFSv41 support compiled in])
 	else
-		enable_nfsv4=
+		enable_nfsv41=
 	fi
 	AC_SUBST(enable_nfsv41)
 	AM_CONDITIONAL(CONFIG_NFSV41, [test "$enable_nfsv41" = "yes"])
@@ -425,6 +425,8 @@ AC_CONFIG_FILES([
 	tools/nlmtest/Makefile
 	tools/rpcdebug/Makefile
 	tools/rpcgen/Makefile
+	tools/mountstats/Makefile
+	tools/nfs-iostat/Makefile
 	utils/Makefile
 	utils/exportfs/Makefile
 	utils/gssd/Makefile
diff --git a/support/export/client.c b/support/export/client.c
index 6236561..dc01067 100644
--- a/support/export/client.c
+++ b/support/export/client.c
@@ -17,7 +17,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <netdb.h>
-#include "xmalloc.h"
+
 #include "misc.h"
 #include "nfslib.h"
 #include "exportfs.h"
@@ -28,58 +28,140 @@
 #if !defined(__GLIBC__) || __GLIBC__ < 2
 extern int	innetgr(char *netgr, char *host, char *, char *);
 #endif
-static void	client_init(nfs_client *clp, const char *hname,
-					struct hostent *hp);
-static int	client_checkaddr(nfs_client *clp, struct in_addr addr);
+
+static char	*add_name(char *old, const char *add);
 
 nfs_client	*clientlist[MCL_MAXTYPES] = { NULL, };
 
 
-/* if canonical is set, then we *know* this is already a canonical name
- * so hostname lookup is avoided.
- * This is used when reading /proc/fs/nfs/exports
+static void
+init_addrlist(nfs_client *clp, const struct addrinfo *ai)
+{
+	int i;
+
+	if (ai == NULL)
+		return;
+
+	for (i = 0; (ai != NULL) && (i < NFSCLNT_ADDRMAX); i++) {
+		set_addrlist(clp, i, ai->ai_addr);
+		ai = ai->ai_next;
+	}
+
+	clp->m_naddr = i;
+}
+
+static void
+client_free(nfs_client *clp)
+{
+	free(clp->m_hostname);
+	free(clp);
+}
+
+static int
+init_netmask(nfs_client *clp, const char *slash)
+{
+	struct sockaddr_in sin = {
+		.sin_family		= AF_INET,
+	};
+
+	if (strchr(slash + 1, '.') != NULL)
+		sin.sin_addr.s_addr = inet_addr(slash + 1);
+	else {
+		int prefixlen = atoi(slash + 1);
+		if (0 < prefixlen && prefixlen <= 32)
+			sin.sin_addr.s_addr =
+					htonl((uint32_t)~0 << (32 - prefixlen));
+		else
+			goto out_badprefix;
+	}
+
+	set_addrlist_in(clp, 1, &sin);
+	return 1;
+
+out_badprefix:
+	xlog(L_ERROR, "Invalid prefix `%s' for %s", slash + 1, clp->m_hostname);
+	return 0;
+}
+
+static int
+init_subnetwork(nfs_client *clp)
+{
+	struct sockaddr_in sin = {
+		.sin_family		= AF_INET,
+	};
+	static char slash32[] = "/32";
+	char *cp;
+
+	cp = strchr(clp->m_hostname, '/');
+	if (cp == NULL)
+		cp = slash32;
+
+	*cp = '\0';
+	sin.sin_addr.s_addr = inet_addr(clp->m_hostname);
+	set_addrlist_in(clp, 0, &sin);
+	*cp = '/';
+
+	return init_netmask(clp, cp);
+}
+
+static int
+client_init(nfs_client *clp, const char *hname, const struct addrinfo *ai)
+{
+	clp->m_hostname = strdup(hname);
+	if (clp->m_hostname == NULL)
+		return 0;
+
+	clp->m_exported = 0;
+	clp->m_count = 0;
+	clp->m_naddr = 0;
+
+	if (clp->m_type == MCL_SUBNETWORK)
+		return init_subnetwork(clp);
+
+	init_addrlist(clp, ai);
+	return 1;
+}
+
+static void
+client_add(nfs_client *clp)
+{
+	nfs_client **cpp;
+
+	cpp = &clientlist[clp->m_type];
+	while (*cpp != NULL)
+		cpp = &((*cpp)->m_next);
+	clp->m_next = NULL;
+	*cpp = clp;
+}
+
+/**
+ * client_lookup - look for @hname in our list of cached nfs_clients
+ * @hname: '\0'-terminated ASCII string containing hostname to look for
+ * @canonical: if set, @hname is known to be canonical DNS name
+ *
+ * Returns pointer to a matching or freshly created nfs_client.  NULL
+ * is returned if some problem occurs.
  */
 nfs_client *
 client_lookup(char *hname, int canonical)
 {
 	nfs_client	*clp = NULL;
 	int		htype;
-	struct hostent	*hp = NULL;
+	struct addrinfo	*ai = NULL;
 
 	htype = client_gettype(hname);
 
 	if (htype == MCL_FQDN && !canonical) {
-		struct hostent *hp2;
-		hp = gethostbyname(hname);
-		if (hp == NULL || hp->h_addrtype != AF_INET) {
-			xlog(L_ERROR, "%s has non-inet addr", hname);
-			return NULL;
+		ai = host_addrinfo(hname);
+		if (!ai) {
+			xlog(L_ERROR, "Failed to resolve %s", hname);
+			goto out;
 		}
-		/* make sure we have canonical name */
-		hp2 = hostent_dup(hp);
-		hp = gethostbyaddr(hp2->h_addr, hp2->h_length,
-				   hp2->h_addrtype);
-		if (hp) {
-			hp = hostent_dup(hp);
-			/* but now we might not have all addresses... */
-			if (hp2->h_addr_list[1]) {
-				struct hostent *hp3 =
-					gethostbyname(hp->h_name);
-				if (hp3) {
-					free(hp);
-					hp = hostent_dup(hp3);
-				}
-			}
-			free(hp2);
-		} else
-			hp = hp2;
-
-		hname = (char *) hp->h_name;
+		hname = ai->ai_canonname;
 
-		for (clp = clientlist[htype]; clp; clp = clp->m_next) {
-			if (client_check(clp, hp))
+		for (clp = clientlist[htype]; clp; clp = clp->m_next)
+			if (client_check(clp, ai))
 				break;
-		}
 	} else {
 		for (clp = clientlist[htype]; clp; clp = clp->m_next) {
 			if (strcasecmp(hname, clp->m_hostname)==0)
@@ -87,106 +169,60 @@ client_lookup(char *hname, int canonical)
 		}
 	}
 
-	if (!clp) {
-		clp = (nfs_client *) xmalloc(sizeof(*clp));
-		memset(clp, 0, sizeof(*clp));
+	if (clp == NULL) {
+		clp = calloc(1, sizeof(*clp));
+		if (clp == NULL)
+			goto out;
 		clp->m_type = htype;
-		client_init(clp, hname, NULL);
+		if (!client_init(clp, hname, NULL)) {
+			client_free(clp);
+			clp = NULL;
+			goto out;
+		}
 		client_add(clp);
 	}
 
-	if (htype == MCL_FQDN && clp->m_naddr == 0 && hp != NULL) {
-		char	**ap = hp->h_addr_list;
-		int	i;
-
-		for (i = 0; *ap && i < NFSCLNT_ADDRMAX; i++, ap++)
-			clp->m_addrlist[i] = *(struct in_addr *)*ap;
-		clp->m_naddr = i;
-	}
-
-	if (hp)
-		free (hp);
+	if (htype == MCL_FQDN && clp->m_naddr == 0)
+		init_addrlist(clp, ai);
 
+out:
+	freeaddrinfo(ai);
 	return clp;
 }
 
+/**
+ * client_dup - create a copy of an nfs_client
+ * @clp: pointer to nfs_client to copy
+ * @ai: pointer to addrinfo used to initialize the new client's addrlist
+ *
+ * Returns a dynamically allocated nfs_client if successful, or
+ * NULL if some problem occurs.  Caller must free the returned
+ * nfs_client with free(3).
+ */
 nfs_client *
-client_dup(nfs_client *clp, struct hostent *hp)
+client_dup(const nfs_client *clp, const struct addrinfo *ai)
 {
 	nfs_client		*new;
 
-	new = (nfs_client *) xmalloc(sizeof(*new));
+	new = (nfs_client *)malloc(sizeof(*new));
+	if (new == NULL)
+		return NULL;
 	memcpy(new, clp, sizeof(*new));
 	new->m_type = MCL_FQDN;
 	new->m_hostname = NULL;
 
-	client_init(new, (char *) hp->h_name, hp);
+	if (!client_init(new, ai->ai_canonname, ai)) {
+		client_free(new);
+		return NULL;
+	}
 	client_add(new);
 	return new;
 }
 
-static void
-client_init(nfs_client *clp, const char *hname, struct hostent *hp)
-{
-	xfree(clp->m_hostname);
-	if (hp)
-		clp->m_hostname = xstrdup(hp->h_name);
-	else
-		clp->m_hostname = xstrdup(hname);
-
-	clp->m_exported = 0;
-	clp->m_count = 0;
-
-	if (clp->m_type == MCL_SUBNETWORK) {
-		char	*cp = strchr(clp->m_hostname, '/');
-		static char slash32[] = "/32";
-
-		if(!cp) cp = slash32;
-		*cp = '\0';
-		clp->m_addrlist[0].s_addr = inet_addr(clp->m_hostname);
-		if (strchr(cp + 1, '.')) {
-			clp->m_addrlist[1].s_addr = inet_addr(cp+1);
-		}
-		else {
-			int netmask = atoi(cp + 1);
-			if (0 < netmask && netmask <= 32) {
-				clp->m_addrlist[1].s_addr =
-					htonl ((uint32_t) ~0 << (32 - netmask));
-			}
-			else {
-				xlog(L_FATAL, "invalid netmask `%s' for %s",
-				     cp + 1, clp->m_hostname);
-			}
-		}
-		*cp = '/';
-		clp->m_naddr = 0;
-	} else if (!hp) {
-		clp->m_naddr = 0;
-	} else {
-		char	**ap = hp->h_addr_list;
-		int	i;
-
-		for (i = 0; *ap && i < NFSCLNT_ADDRMAX; i++, ap++) {
-			clp->m_addrlist[i] = *(struct in_addr *)*ap;
-		}
-		clp->m_naddr = i;
-	}
-}
-
-void
-client_add(nfs_client *clp)
-{
-	nfs_client	**cpp;
-
-	if (clp->m_type < 0 || clp->m_type >= MCL_MAXTYPES)
-		xlog(L_FATAL, "unknown client type in client_add");
-	cpp = clientlist + clp->m_type;
-	while (*cpp)
-		cpp = &((*cpp)->m_next);
-	clp->m_next = NULL;
-	*cpp = clp;
-}
-
+/**
+ * client_release - drop a reference to an nfs_client record
+ *
+ */
 void
 client_release(nfs_client *clp)
 {
@@ -195,6 +231,10 @@ client_release(nfs_client *clp)
 	clp->m_count--;
 }
 
+/**
+ * client_freeall - deallocate all nfs_client records
+ *
+ */
 void
 client_freeall(void)
 {
@@ -205,57 +245,45 @@ client_freeall(void)
 		head = clientlist + i;
 		while (*head) {
 			*head = (clp = *head)->m_next;
-			xfree(clp->m_hostname);
-			xfree(clp);
-		}
-	}
-}
-
-nfs_client *
-client_find(struct hostent *hp)
-{
-	nfs_client	*clp;
-	int		i;
-
-	for (i = 0; i < MCL_MAXTYPES; i++) {
-		for (clp = clientlist[i]; clp; clp = clp->m_next) {
-			if (!client_check(clp, hp))
-				continue;
-#ifdef notdef
-			if (clp->m_type == MCL_FQDN)
-				return clp;
-			return client_dup(clp, hp);
-#else
-			return clp;
-#endif
+			client_free(clp);
 		}
 	}
-	return NULL;
 }
 
-struct hostent *
-client_resolve(struct in_addr addr)
+/**
+ * client_resolve - look up an IP address
+ * @sap: pointer to socket address to resolve
+ *
+ * Returns an addrinfo structure, or NULL if some problem occurred.
+ * Caller must free the result with freeaddrinfo(3).
+ */
+struct addrinfo *
+client_resolve(const struct sockaddr *sap)
 {
-	struct hostent *he = NULL;
+	struct addrinfo *ai = NULL;
 
 	if (clientlist[MCL_WILDCARD] || clientlist[MCL_NETGROUP])
-		he = get_reliable_hostbyaddr((const char*)&addr, sizeof(addr), AF_INET);
-	if (he == NULL)
-		he = get_hostent((const char*)&addr, sizeof(addr), AF_INET);
+		ai = host_reliable_addrinfo(sap);
+	if (ai == NULL)
+		ai = host_numeric_addrinfo(sap);
 
-	return he;
+	return ai;
 }
 
-/*
- * Find client name given an IP address
- * This is found by gathering all known names that match that IP address,
- * sorting them and joining them with '+'
+/**
+ * client_compose - Make a list of cached hostnames that match an IP address
+ * @ai: pointer to addrinfo containing IP address information to match
  *
+ * Gather all known client hostnames that match the IP address, and sort
+ * the result into a comma-separated list.
+ *
+ * Returns a '\0'-terminated ASCII string containing a comma-separated
+ * sorted list of client hostnames, or NULL if no client records matched
+ * the IP address or memory could not be allocated.  Caller must free the
+ * returned string with free(3).
  */
-static char *add_name(char *old, char *add);
-
 char *
-client_compose(struct hostent *he)
+client_compose(const struct addrinfo *ai)
 {
 	char *name = NULL;
 	int i;
@@ -263,7 +291,7 @@ client_compose(struct hostent *he)
 	for (i = 0 ; i < MCL_MAXTYPES; i++) {
 		nfs_client	*clp;
 		for (clp = clientlist[i]; clp ; clp = clp->m_next) {
-			if (!client_check(clp, he))
+			if (!client_check(clp, ai))
 				continue;
 			name = add_name(name, clp->m_hostname);
 		}
@@ -271,13 +299,19 @@ client_compose(struct hostent *he)
 	return name;
 }
 
+/**
+ * client_member - check if @name is contained in the list @client
+ * @client: '\0'-terminated ASCII string containing
+ *		comma-separated list of hostnames
+ * @name: '\0'-terminated ASCII string containing hostname to look for
+ *
+ * Returns 1 if @name was found in @client, otherwise zero is returned.
+ */
 int
-client_member(char *client, char *name)
+client_member(const char *client, const char *name)
 {
-	/* check if "client" (a ',' separated list of names)
-	 * contains 'name' as a member
-	 */
-	int l = strlen(name);
+	size_t l = strlen(name);
+
 	while (*client) {
 		if (strncmp(client, name, l) == 0 &&
 		    (client[l] == ',' || client[l] == '\0'))
@@ -290,9 +324,8 @@ client_member(char *client, char *name)
 	return 0;
 }
 
-
-int
-name_cmp(char *a, char *b)
+static int
+name_cmp(const char *a, const char *b)
 {
 	/* compare strings a and b, but only upto ',' in a */
 	while (*a && *b && *a != ',' && *a == *b)
@@ -305,9 +338,9 @@ name_cmp(char *a, char *b)
 }
 
 static char *
-add_name(char *old, char *add)
+add_name(char *old, const char *add)
 {
-	int len = strlen(add)+2;
+	size_t len = strlen(add) + 2;
 	char *new;
 	char *cp;
 	if (old) len += strlen(old);
@@ -339,105 +372,211 @@ add_name(char *old, char *add)
 	return new;
 }
 
+static _Bool
+addrs_match4(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	const struct sockaddr_in *si1 = (const struct sockaddr_in *)sa1;
+	const struct sockaddr_in *si2 = (const struct sockaddr_in *)sa2;
+
+	return si1->sin_addr.s_addr == si2->sin_addr.s_addr;
+}
+
+static _Bool
+addrs_match(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	if (sa1->sa_family == sa2->sa_family)
+		switch (sa1->sa_family) {
+		case AF_INET:
+			return addrs_match4(sa1, sa2);
+		}
+
+	return false;
+}
+
 /*
- * Match a host (given its hostent record) to a client record. This
- * is usually called from mountd.
+ * Check each address listed in @ai against each address
+ * stored in @clp.  Return 1 if a match is found, otherwise
+ * zero.
  */
-int
-client_check(nfs_client *clp, struct hostent *hp)
+static int
+check_fqdn(const nfs_client *clp, const struct addrinfo *ai)
 {
-	char	*hname = (char *) hp->h_name;
-	char	*cname = clp->m_hostname;
-	char	**ap;
+	int i;
 
-	switch (clp->m_type) {
-	case MCL_FQDN:
-	case MCL_SUBNETWORK:
-		for (ap = hp->h_addr_list; *ap; ap++) {
-			if (client_checkaddr(clp, *(struct in_addr *) *ap))
+	for (; ai; ai = ai->ai_next)
+		for (i = 0; i < clp->m_naddr; i++)
+			if (addrs_match(ai->ai_addr, get_addrlist(clp, i)))
 				return 1;
-		}
-		return 0;
-	case MCL_WILDCARD:
-		if (wildmat(hname, cname))
+
+	return 0;
+}
+
+static _Bool
+mask_match(const uint32_t a, const uint32_t b, const uint32_t m)
+{
+	return ((a ^ b) & m) == 0;
+}
+
+static int
+check_subnet_v4(const struct sockaddr_in *address,
+		const struct sockaddr_in *mask, const struct addrinfo *ai)
+{
+	for (; ai; ai = ai->ai_next) {
+		struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
+
+		if (sin->sin_family != AF_INET)
+			continue;
+
+		if (mask_match(address->sin_addr.s_addr,
+				sin->sin_addr.s_addr,
+				mask->sin_addr.s_addr))
 			return 1;
-		else {
-			for (ap = hp->h_aliases; *ap; ap++)
-				if (wildmat(*ap, cname))
-					return 1;
-		}
-		return 0;
-	case MCL_NETGROUP:
-#ifdef HAVE_INNETGR
-		{
-			char	*dot;
-			int	match, i;
-			struct hostent *nhp = NULL;
-			struct sockaddr_in addr;
-
-			/* First, try to match the hostname without
-			 * splitting off the domain */
-			if (innetgr(cname+1, hname, NULL, NULL))
-				return 1;
+	}
+	return 0;
+}
 
-			/* try the aliases as well */
-			for (i = 0; hp->h_aliases[i]; i++) {
-				if (innetgr(cname+1, hp->h_aliases[i], NULL, NULL))
-					return 1;
-			}
-
-			/* If hname is ip address convert to FQDN */
-			if (inet_aton(hname, &addr.sin_addr) &&
-			   (nhp = gethostbyaddr((const char *)&(addr.sin_addr),
-			    sizeof(addr.sin_addr), AF_INET))) {
-				hname = (char *)nhp->h_name;
-				if (innetgr(cname+1, hname, NULL, NULL))
-					return 1;
-			}
-
-			/* Okay, strip off the domain (if we have one) */
-			if ((dot = strchr(hname, '.')) == NULL)
-				return 0;
-
-			*dot = '\0';
-			match = innetgr(cname+1, hname, NULL, NULL);
-			*dot = '.';
-
-			return match;
-		}
-#else
-		return 0;
-#endif
-	case MCL_ANONYMOUS:
+/*
+ * Check each address listed in @ai against the subnetwork or
+ * host address stored in @clp.  Return 1 if an address in @hp
+ * matches the host address stored in @clp, otherwise zero.
+ */
+static int
+check_subnetwork(const nfs_client *clp, const struct addrinfo *ai)
+{
+	switch (get_addrlist(clp, 0)->sa_family) {
+	case AF_INET:
+		return check_subnet_v4(get_addrlist_in(clp, 0),
+				get_addrlist_in(clp, 1), ai);
+	}
+
+	return 0;
+}
+
+/*
+ * Check if a wildcard nfs_client record matches the canonical name
+ * or the aliases of a host.  Return 1 if a match is found, otherwise
+ * zero.
+ */
+static int
+check_wildcard(const nfs_client *clp, const struct addrinfo *ai)
+{
+	char *cname = clp->m_hostname;
+	char *hname = ai->ai_canonname;
+	struct hostent *hp;
+	char **ap;
+
+	if (wildmat(hname, cname))
 		return 1;
-	case MCL_GSS:
-		return 0;
-	default:
-		xlog(L_FATAL, "internal: bad client type %d", clp->m_type);
+
+	/* See if hname aliases listed in /etc/hosts or nis[+]
+	 * match the requested wildcard */
+	hp = gethostbyname(hname);
+	if (hp != NULL) {
+		for (ap = hp->h_aliases; *ap; ap++)
+			if (wildmat(*ap, cname))
+				return 1;
 	}
 
 	return 0;
 }
 
+/*
+ * Check if @ai's hostname or aliases fall in a given netgroup.
+ * Return 1 if @ai represents a host in the netgroup, otherwise
+ * zero.
+ */
+#ifdef HAVE_INNETGR
 static int
-client_checkaddr(nfs_client *clp, struct in_addr addr)
+check_netgroup(const nfs_client *clp, const struct addrinfo *ai)
 {
-	int	i;
+	const char *netgroup = clp->m_hostname + 1;
+	const char *hname = ai->ai_canonname;
+	struct addrinfo *tmp = NULL;
+	struct hostent *hp;
+	int i, match;
+	char *dot;
+
+	/* First, try to match the hostname without
+	 * splitting off the domain */
+	if (innetgr(netgroup, hname, NULL, NULL))
+		return 1;
 
-	switch (clp->m_type) {
-	case MCL_FQDN:
-		for (i = 0; i < clp->m_naddr; i++) {
-			if (clp->m_addrlist[i].s_addr == addr.s_addr)
+	/* See if hname aliases listed in /etc/hosts or nis[+]
+	 * match the requested netgroup */
+	hp = gethostbyname(hname);
+	if (hp != NULL) {
+		for (i = 0; hp->h_aliases[i]; i++)
+			if (innetgr(netgroup, hp->h_aliases[i], NULL, NULL))
 				return 1;
-		}
+	}
+
+	/* If hname is ip address convert to FQDN */
+	tmp = host_pton(hname);
+	if (tmp != NULL) {
+		freeaddrinfo(tmp);
+		if (innetgr(netgroup, hname, NULL, NULL))
+			return 1;
+	}
+
+	/* Okay, strip off the domain (if we have one) */
+	dot = strchr(hname, '.');
+	if (dot == NULL)
 		return 0;
+
+	*dot = '\0';
+	match = innetgr(netgroup, hname, NULL, NULL);
+	*dot = '.';
+
+	return match;
+}
+#else	/* !HAVE_INNETGR */
+static int
+check_netgroup(__attribute__((unused)) const nfs_client *clp,
+		__attribute__((unused)) const struct addrinfo *ai)
+{
+	return 0;
+}
+#endif	/* !HAVE_INNETGR */
+
+/**
+ * client_check - check if IP address information matches a cached nfs_client
+ * @clp: pointer to a cached nfs_client record
+ * @ai: pointer to addrinfo to compare it with
+ *
+ * Returns 1 if the address information matches the cached nfs_client,
+ * otherwise zero.
+ */
+int
+client_check(const nfs_client *clp, const struct addrinfo *ai)
+{
+	switch (clp->m_type) {
+	case MCL_FQDN:
+		return check_fqdn(clp, ai);
 	case MCL_SUBNETWORK:
-		return !((clp->m_addrlist[0].s_addr ^ addr.s_addr)
-			& clp->m_addrlist[1].s_addr);
+		return check_subnetwork(clp, ai);
+	case MCL_WILDCARD:
+		return check_wildcard(clp, ai);
+	case MCL_NETGROUP:
+		return check_netgroup(clp, ai);
+	case MCL_ANONYMOUS:
+		return 1;
+	case MCL_GSS:
+		return 0;
+	default:
+		xlog(D_GENERAL, "%s: unrecognized client type: %d",
+				__func__, clp->m_type);
 	}
+
 	return 0;
 }
 
+/**
+ * client_gettype - determine type of nfs_client given an identifier
+ * @ident: '\0'-terminated ASCII string containing a client identifier
+ *
+ * Returns the type of nfs_client record that would be used for
+ * this client.
+ */
 int
 client_gettype(char *ident)
 {
diff --git a/support/export/export.c b/support/export/export.c
index 2943466..f528603 100644
--- a/support/export/export.c
+++ b/support/export/export.c
@@ -24,9 +24,24 @@ static int export_hash(char *);
 
 static void	export_init(nfs_export *exp, nfs_client *clp,
 					struct exportent *nep);
-static int	export_check(nfs_export *, struct hostent *, char *);
+static void	export_add(nfs_export *exp);
+static int	export_check(const nfs_export *exp, const struct addrinfo *ai,
+				const char *path);
 static nfs_export *
-		export_allowed_internal(struct hostent *hp, char *path);
+		export_allowed_internal(const struct addrinfo *ai,
+				const char *path);
+
+static void
+export_free(nfs_export *exp)
+{
+	xfree(exp->m_export.e_squids);
+	xfree(exp->m_export.e_sqgids);
+	free(exp->m_export.e_mountpoint);
+	free(exp->m_export.e_fslocdata);
+
+	xfree(exp->m_export.e_hostname);
+	xfree(exp);
+}
 
 static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep)
 {
@@ -44,7 +59,12 @@ static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep)
 	}
 }
 
-int
+/**
+ * export_read - read entries from /etc/exports
+ * @fname: name of file to read from
+ *
+ */
+void
 export_read(char *fname)
 {
 	struct exportent	*eep;
@@ -59,11 +79,15 @@ export_read(char *fname)
 			warn_duplicated_exports(exp, eep);
 	}
 	endexportent();
-	return 0;
 }
 
-/*
- * Create an in-core export struct from an export entry.
+/**
+ * export_create - create an in-core nfs_export record from an export entry
+ * @xep: export entry to lookup
+ * @canonical: if set, e_hostname is known to be canonical DNS name
+ *
+ * Returns a freshly instantiated export record, or NULL if
+ * a problem occurred.
  */
 nfs_export *
 export_create(struct exportent *xep, int canonical)
@@ -105,8 +129,8 @@ export_init(nfs_export *exp, nfs_client *clp, struct exportent *nep)
  * original hostname from /etc/exports, while the in-core client struct
  * gets the newly found FQDN.
  */
-nfs_export *
-export_dup(nfs_export *exp, struct hostent *hp)
+static nfs_export *
+export_dup(nfs_export *exp, const struct addrinfo *ai)
 {
 	nfs_export		*new;
 	nfs_client		*clp;
@@ -116,7 +140,11 @@ export_dup(nfs_export *exp, struct hostent *hp)
 	dupexportent(&new->m_export, &exp->m_export);
 	if (exp->m_export.e_hostname)
 		new->m_export.e_hostname = xstrdup(exp->m_export.e_hostname);
-	clp = client_dup(exp->m_client, hp);
+	clp = client_dup(exp->m_client, ai);
+	if (clp == NULL) {
+		export_free(new);
+		return NULL;
+	}
 	clp->m_count++;
 	new->m_client = clp;
 	new->m_mayexport = exp->m_mayexport;
@@ -128,10 +156,8 @@ export_dup(nfs_export *exp, struct hostent *hp)
 
 	return new;
 }
-/*
- * Add export entry to hash table
- */
-void 
+
+static void
 export_add(nfs_export *exp)
 {
 	exp_hash_table *p_tbl;
@@ -159,19 +185,27 @@ export_add(nfs_export *exp)
 	}
 }
 
+/**
+ * export_find - find or create a suitable nfs_export for @ai and @path
+ * @ai: pointer to addrinfo for client
+ * @path: '\0'-terminated ASCII string containing export path
+ *
+ * Returns a pointer to nfs_export data matching @ai and @path,
+ * or NULL if an error occurs.
+ */
 nfs_export *
-export_find(struct hostent *hp, char *path)
+export_find(const struct addrinfo *ai, const char *path)
 {
 	nfs_export	*exp;
 	int		i;
 
 	for (i = 0; i < MCL_MAXTYPES; i++) {
 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
-			if (!export_check(exp, hp, path))
+			if (!export_check(exp, ai, path))
 				continue;
 			if (exp->m_client->m_type == MCL_FQDN)
 				return exp;
-			return export_dup(exp, hp);
+			return export_dup(exp, ai);
 		}
 	}
 
@@ -179,7 +213,7 @@ export_find(struct hostent *hp, char *path)
 }
 
 static nfs_export *
-export_allowed_internal (struct hostent *hp, char *path)
+export_allowed_internal(const struct addrinfo *ai, const char *path)
 {
 	nfs_export	*exp;
 	int		i;
@@ -187,7 +221,7 @@ export_allowed_internal (struct hostent *hp, char *path)
 	for (i = 0; i < MCL_MAXTYPES; i++) {
 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
 			if (!exp->m_mayexport ||
-			    !export_check(exp, hp, path))
+			    !export_check(exp, ai, path))
 				continue;
 			return exp;
 		}
@@ -196,8 +230,16 @@ export_allowed_internal (struct hostent *hp, char *path)
 	return NULL;
 }
 
+/**
+ * export_allowed - determine if this export is allowed
+ * @ai: pointer to addrinfo for client
+ * @path: '\0'-terminated ASCII string containing export path
+ *
+ * Returns a pointer to nfs_export data matching @ai and @path,
+ * or NULL if the export is not allowed.
+ */
 nfs_export *
-export_allowed(struct hostent *hp, char *path)
+export_allowed(const struct addrinfo *ai, const char *path)
 {
 	nfs_export		*exp;
 	char			epath[MAXPATHLEN+1];
@@ -210,7 +252,7 @@ export_allowed(struct hostent *hp, char *path)
 
 	/* Try the longest matching exported pathname. */
 	while (1) {
-		exp = export_allowed_internal (hp, epath);
+		exp = export_allowed_internal(ai, epath);
 		if (exp)
 			return exp;
 		/* We have to treat the root, "/", specially. */
@@ -223,11 +265,17 @@ export_allowed(struct hostent *hp, char *path)
 	return NULL;
 }
 
-/*
- * Search hash table for export entry. 
- */  
+/**
+ * export_lookup - search hash table for export entry
+ * @hname: '\0'-terminated ASCII string containing client hostname to look for
+ * @path: '\0'-terminated ASCII string containing export path to look for
+ * @canonical: if set, @hname is known to be canonical DNS name
+ *
+ * Returns a pointer to nfs_export record matching @hname and @path,
+ * or NULL if the export was not found.
+ */
 nfs_export *
-export_lookup(char *hname, char *path, int canonical) 
+export_lookup(char *hname, char *path, int canonical)
 {
 	nfs_client *clp;
 	nfs_export *exp;
@@ -251,14 +299,18 @@ export_lookup(char *hname, char *path, int canonical)
 }
 
 static int
-export_check(nfs_export *exp, struct hostent *hp, char *path)
+export_check(const nfs_export *exp, const struct addrinfo *ai, const char *path)
 {
 	if (strcmp(path, exp->m_export.e_path))
 		return 0;
 
-	return client_check(exp->m_client, hp);
+	return client_check(exp->m_client, ai);
 }
 
+/**
+ * export_freeall - deallocate all nfs_export records
+ *
+ */
 void
 export_freeall(void)
 {
@@ -269,22 +321,13 @@ export_freeall(void)
 		for (exp = exportlist[i].p_head; exp; exp = nxt) {
 			nxt = exp->m_next;
 			client_release(exp->m_client);
-			if (exp->m_export.e_squids)
-				xfree(exp->m_export.e_squids);
-			if (exp->m_export.e_sqgids)
-				xfree(exp->m_export.e_sqgids);
-			if (exp->m_export.e_mountpoint)
-				free(exp->m_export.e_mountpoint);
-			if (exp->m_export.e_fslocdata)
-				xfree(exp->m_export.e_fslocdata);
-			xfree(exp->m_export.e_hostname);
-			xfree(exp);
+			export_free(exp);
+		}
+		for (j = 0; j < HASH_TABLE_SIZE; j++) {
+			exportlist[i].entries[j].p_first = NULL;
+			exportlist[i].entries[j].p_last = NULL;
 		}
-      for(j = 0; j < HASH_TABLE_SIZE; j++) {
-        exportlist[i].entries[j].p_first = NULL;
-        exportlist[i].entries[j].p_last = NULL;
-      }
-      exportlist[i].p_head = NULL;
+		exportlist[i].p_head = NULL;
 	}
 	client_freeall();
 }
diff --git a/support/export/hostname.c b/support/export/hostname.c
index 8a23a89..232e040 100644
--- a/support/export/hostname.c
+++ b/support/export/hostname.c
@@ -1,315 +1,377 @@
 /*
- * support/export/hostname.c
+ * Copyright 2010 Oracle.  All rights reserved.
  *
- * Functions for hostname.
+ * This file is part of nfs-utils.
  *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
-/*
-#define TEST
-*/
-
 #include <string.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
 #include <stdlib.h>
-#include <xlog.h>
-#ifdef TEST
-#define xmalloc malloc
-#else
-#include "xmalloc.h"
-#include "misc.h"
-#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include "sockaddr.h"
+#include "exportfs.h"
 
-#define ALIGNMENT	sizeof (char *)
+#ifndef HAVE_DECL_AI_ADDRCONFIG
+#define AI_ADDRCONFIG	0
+#endif
 
-static int
-align (int len, int al)
+#ifdef HAVE_GETNAMEINFO
+static socklen_t
+sockaddr_size(const struct sockaddr *sap)
 {
-  int i;
-  i = len % al;
-  if (i)
-    len += al - i;
-  return len;
+	if (sap->sa_family != AF_INET)
+		return 0;
+	return (socklen_t)sizeof(struct sockaddr_in);
 }
+#endif	/* HAVE_GETNAMEINFO */
+
+/**
+ * host_ntop - generate presentation address given a sockaddr
+ * @sap: pointer to socket address
+ * @buf: working storage
+ * @buflen: size of @buf in bytes
+ *
+ * Returns a pointer to a @buf.
+ */
+#ifdef HAVE_GETNAMEINFO
+char *
+host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen)
+{
+	socklen_t salen = sockaddr_size(sap);
+	int error;
+
+	memset(buf, 0, buflen);
+
+	if (salen == 0) {
+		(void)strncpy(buf, "bad family", buflen - 1);
+		return buf;
+	}
+
+	error = getnameinfo(sap, salen, buf, (socklen_t)buflen,
+						NULL, 0, NI_NUMERICHOST);
+	if (error != 0) {
+		buf[0] = '\0';
+		(void)strncpy(buf, "bad address", buflen - 1);
+	}
 
-struct hostent *
-get_hostent (const char *addr, int len, int type)
+	return buf;
+}
+#else	/* !HAVE_GETNAMEINFO */
+char *
+host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen)
 {
-  struct hostent *cp;
-  int len_ent;
-  const char *name;
-  int len_name;
-  int num_aliases = 1;
-  int len_aliases = sizeof (char *);
-  int num_addr_list = 1;
-  int len_addr_list = sizeof (char *);
-  int pos;
-  struct in_addr *ipv4;
-
-  switch (type)
-    {
-    case AF_INET:
-      ipv4 = (struct in_addr *) addr;
-      name = inet_ntoa (*ipv4);
-      break;
-
-    default:
-      return NULL;
-    }
-
-  len_ent = align (sizeof (*cp), ALIGNMENT);
-  len_name = align (strlen (name) + 1, ALIGNMENT);
-
-  num_addr_list++;
-  len_addr_list += align (len, ALIGNMENT) + sizeof (char *);
-
-  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
-				   + len_addr_list);
-
-  cp->h_addrtype = type;
-  cp->h_length = len;
-  pos = len_ent;
-  cp->h_name = (char *) &(((char *) cp) [pos]);
-  strcpy (cp->h_name, name);
-
-  pos += len_name;
-  cp->h_aliases = (char **) &(((char *) cp) [pos]);
-  pos += num_aliases * sizeof (char *);
-  cp->h_aliases [0] = NULL;
-
-  pos = len_ent + len_name + len_aliases;
-  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
-  pos += num_addr_list * sizeof (char *);
-  cp->h_addr_list [0] = (char *) &(((char *) cp) [pos]);
-  memcpy (cp->h_addr_list [0], addr, cp->h_length);
-  pos += align (cp->h_length, ALIGNMENT);
-  cp->h_addr_list [1] = NULL;
-
-  return cp;
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap;
+
+	memset(buf, 0, buflen);
+
+	if (sin->sin_family != AF_INET)
+		(void)strncpy(buf, "bad family", buflen - 1);
+		return buf;
+	}
+
+	if (inet_ntop(AF_INET, &sin->sin_addr.s_addr, buf, buflen) != NULL)
+		return buf;
+
+	buf[0] = '\0';
+	(void)strncpy(buf, "bad address", buflen - 1);
+	return buf;
 }
+#endif	/* !HAVE_GETNAMEINFO */
 
-struct hostent *
-hostent_dup (struct hostent *hp)
+/**
+ * host_pton - return addrinfo for a given presentation address
+ * @paddr: pointer to a '\0'-terminated ASCII string containing an
+ *		IP presentation address
+ *
+ * Returns address info structure, or NULL if an error occurs.  Caller
+ * must free the returned structure with freeaddrinfo(3).
+ */
+__attribute_malloc__
+struct addrinfo *
+host_pton(const char *paddr)
 {
-  int len_ent = align (sizeof (*hp), ALIGNMENT);
-  int len_name = align (strlen (hp->h_name) + 1, ALIGNMENT);
-  int num_aliases = 1;
-  int len_aliases = sizeof (char *);
-  int num_addr_list = 1;
-  int len_addr_list = sizeof (char *);
-  int pos, i;
-  char **sp;
-  struct hostent *cp;
-
-  for (sp = hp->h_aliases; sp && *sp; sp++)
-    {
-      num_aliases++;
-      len_aliases += align (strlen (*sp) + 1, ALIGNMENT)
-		     + sizeof (char *);
-    }
-
-  for (sp = hp->h_addr_list; *sp; sp++)
-    {
-      num_addr_list++;
-      len_addr_list += align (hp->h_length, ALIGNMENT)
-		       + sizeof (char *);
-    }
-
-  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
-				   + len_addr_list);
-
-  *cp = *hp;
-  pos = len_ent;
-  cp->h_name = (char *) &(((char *) cp) [pos]);
-  strcpy (cp->h_name, hp->h_name);
-
-  pos += len_name;
-  cp->h_aliases = (char **) &(((char *) cp) [pos]);
-  pos += num_aliases * sizeof (char *);
-  for (sp = hp->h_aliases, i = 0; i < num_aliases; i++, sp++)
-    if (sp && *sp)
-      {
-	cp->h_aliases [i] = (char *) &(((char *) cp) [pos]);
-	strcpy (cp->h_aliases [i], *sp);
-	pos += align (strlen (*sp) + 1, ALIGNMENT);
-      }
-    else
-      cp->h_aliases [i] = NULL;
-
-  pos = len_ent + len_name + len_aliases;
-  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
-  pos += num_addr_list * sizeof (char *);
-  for (sp = hp->h_addr_list, i = 0; i < num_addr_list; i++, sp++)
-    if (*sp)
-      {
-	cp->h_addr_list [i] = (char *) &(((char *) cp) [pos]);
-	memcpy (cp->h_addr_list [i], *sp, hp->h_length);
-	pos += align (hp->h_length, ALIGNMENT);
-      }
-    else
-      cp->h_addr_list [i] = *sp;
-
-  return cp;
+	struct addrinfo *ai = NULL;
+	struct addrinfo hint = {
+		/* don't return duplicates */
+		.ai_protocol	= (int)IPPROTO_UDP,
+		.ai_flags	= AI_NUMERICHOST,
+		.ai_family	= AF_UNSPEC,
+	};
+	struct sockaddr_in sin;
+	int error;
+
+	/*
+	 * Although getaddrinfo(3) is easier to use and supports
+	 * IPv6, it recognizes incomplete addresses like "10.4"
+	 * as valid AF_INET addresses.  It also accepts presentation
+	 * addresses that end with a blank.
+	 *
+	 * inet_pton(3) is much stricter.  Use it to be certain we
+	 * have a real AF_INET presentation address, before invoking
+	 * getaddrinfo(3) to generate the full addrinfo list.
+	 */
+	if (inet_pton(AF_INET, paddr, &sin.sin_addr) == 0)
+		return NULL;
+
+	error = getaddrinfo(paddr, NULL, &hint, &ai);
+	switch (error) {
+	case 0:
+		return ai;
+	case EAI_NONAME:
+		if (paddr == NULL)
+			xlog(D_GENERAL, "%s: passed a NULL presentation address",
+				__func__);
+		break;
+	case EAI_SYSTEM:
+		xlog(D_GENERAL, "%s: failed to convert %s: (%d) %m",
+				__func__, paddr, errno);
+		break;
+	default:
+		xlog(D_GENERAL, "%s: failed to convert %s: %s",
+				__func__, paddr, gai_strerror(error));
+		break;
+	}
+
+	return NULL;
 }
 
-static int
-is_hostname(const char *sp)
+/**
+ * host_addrinfo - return addrinfo for a given hostname
+ * @hostname: pointer to a '\0'-terminated ASCII string containing a hostname
+ *
+ * Returns address info structure with ai_canonname filled in, or NULL
+ * if no information is available for @hostname.  Caller must free the
+ * returned structure with freeaddrinfo(3).
+ */
+__attribute_malloc__
+struct addrinfo *
+host_addrinfo(const char *hostname)
 {
-  if (*sp == '\0' || *sp == '@')
-    return 0;
-
-  for (; *sp; sp++)
-    {
-      if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
-	return 0;
-      if (*sp == '\\' && sp[1])
-	sp++;
-    }
-
-  return 1;
+	struct addrinfo *ai = NULL;
+	struct addrinfo hint = {
+		.ai_family	= AF_INET,
+		/* don't return duplicates */
+		.ai_protocol	= (int)IPPROTO_UDP,
+		.ai_flags	= AI_ADDRCONFIG | AI_CANONNAME,
+	};
+	int error;
+
+	error = getaddrinfo(hostname, NULL, &hint, &ai);
+	switch (error) {
+	case 0:
+		return ai;
+	case EAI_SYSTEM:
+		xlog(D_GENERAL, "%s: failed to resolve %s: (%d) %m",
+				__func__, hostname, errno);
+		break;
+	default:
+		xlog(D_GENERAL, "%s: failed to resolve %s: %s",
+				__func__, hostname, gai_strerror(error));
+		break;
+	}
+
+	return NULL;
 }
 
-int
-matchhostname (const char *h1, const char *h2)
+/**
+ * host_canonname - return canonical hostname bound to an address
+ * @sap: pointer to socket address to look up
+ *
+ * Discover the canonical hostname associated with the given socket
+ * address.  The host's reverse mapping is verified in the process.
+ *
+ * Returns a '\0'-terminated ASCII string containing a hostname, or
+ * NULL if no hostname can be found for @sap.  Caller must free
+ * the string.
+ */
+#ifdef HAVE_GETNAMEINFO
+__attribute_malloc__
+char *
+host_canonname(const struct sockaddr *sap)
 {
-  struct hostent *hp1, *hp2;
-  int status;
-
-  if (strcasecmp (h1, h2) == 0)
-    return 1;
-
-  if (!is_hostname (h1) || !is_hostname (h2))
-    return 0;
-
-  hp1 = gethostbyname (h1);
-  if (hp1 == NULL)
-    return 0;
-
-  hp1 = hostent_dup (hp1);
-
-  hp2 = gethostbyname (h2);
-  if (hp2)
-    {
-      if (strcasecmp (hp1->h_name, hp2->h_name) == 0)
-	status = 1;
-      else
-	{
-	  char **ap1, **ap2;
-
-	  status = 0;
-	  for (ap1 = hp1->h_addr_list; *ap1 && status == 0; ap1++)
-	    for (ap2 = hp2->h_addr_list; *ap2; ap2++)
-	      if (memcmp (*ap1, *ap2, sizeof (struct in_addr)) == 0)
-		{
-		  status = 1;
-		  break;
-		}
+	socklen_t salen = sockaddr_size(sap);
+	char buf[NI_MAXHOST];
+	int error;
+
+	if (salen == 0) {
+		xlog(D_GENERAL, "%s: unsupported address family %d",
+				__func__, sap->sa_family);
+		return NULL;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+							NULL, 0, NI_NAMEREQD);
+	switch (error) {
+	case 0:
+		break;
+	case EAI_SYSTEM:
+		xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m",
+				__func__, errno);
+		return NULL;
+	default:
+		(void)getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+							NULL, 0, NI_NUMERICHOST);
+		xlog(D_GENERAL, "%s: failed to resolve %s: %s",
+				__func__, buf, gai_strerror(error));
+		return NULL;
 	}
-    }
-  else
-    status = 0;
 
-  free (hp1);
-  return status;
+	return strdup(buf);
 }
+#else	/* !HAVE_GETNAMEINFO */
+__attribute_malloc__
+char *
+host_canonname(const struct sockaddr *sap)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap;
+	const struct in_addr *addr = &sin->sin_addr;
+	struct hostent *hp;
+
+	if (sap->sa_family != AF_INET)
+		return NULL;
+
+	hp = gethostbyaddr(addr, (socklen_t)sizeof(addr), AF_INET);
+	if (hp == NULL)
+		return NULL;
 
+	return strdup(hp->h_name);
+}
+#endif	/* !HAVE_GETNAMEINFO */
 
-/* Map IP to hostname, and then map back to addr to make sure it is a
- * reliable hostname
+/**
+ * host_reliable_addrinfo - return addrinfo for a given address
+ * @sap: pointer to socket address to look up
+ *
+ * Reverse and forward lookups are performed to ensure the address has
+ * proper forward and reverse mappings.
+ *
+ * Returns address info structure with ai_canonname filled in, or NULL
+ * if no information is available for @sap.  Caller must free the returned
+ * structure with freeaddrinfo(3).
  */
-struct hostent *
-get_reliable_hostbyaddr(const char *addr, int len, int type)
+__attribute_malloc__
+struct addrinfo *
+host_reliable_addrinfo(const struct sockaddr *sap)
 {
-	struct hostent *hp = NULL;
-
-	struct hostent *reverse;
-	struct hostent *forward;
-	char **sp;
+	struct addrinfo *ai;
+	char *hostname;
 
-	reverse = gethostbyaddr (addr, len, type);
-	if (!reverse)
+	hostname = host_canonname(sap);
+	if (hostname == NULL)
 		return NULL;
 
-	/* must make sure the hostent is authorative. */
+	ai = host_addrinfo(hostname);
 
-	reverse = hostent_dup (reverse);
-	forward = gethostbyname (reverse->h_name);
+	free(hostname);
+	return ai;
+}
 
-	if (forward) {
-		/* now make sure the "addr" is in the list */
-		for (sp = forward->h_addr_list ; *sp ; sp++) {
-			if (memcmp (*sp, addr, forward->h_length) == 0)
-				break;
-		}
+/**
+ * host_numeric_addrinfo - return addrinfo without doing DNS queries
+ * @sap: pointer to socket address
+ *
+ * Returns address info structure, or NULL if an error occurred.
+ * Caller must free the returned structure with freeaddrinfo(3).
+ */
+#ifdef HAVE_GETNAMEINFO
+__attribute_malloc__
+struct addrinfo *
+host_numeric_addrinfo(const struct sockaddr *sap)
+{
+	socklen_t salen = sockaddr_size(sap);
+	char buf[INET_ADDRSTRLEN];
+	struct addrinfo *ai;
+	int error;
+
+	if (salen == 0) {
+		xlog(D_GENERAL, "%s: unsupported address family %d",
+				__func__, sap->sa_family);
+		return NULL;
+	}
 
-		if (*sp) {
-			/* it's valid */
-			hp = hostent_dup (forward);
-		}
-		else {
-			/* it was a FAKE */
-			xlog (L_WARNING, "Fake hostname %s for %s - forward lookup doesn't match reverse",
-			      reverse->h_name, inet_ntoa(*(struct in_addr*)addr));
-		}
+	memset(buf, 0, sizeof(buf));
+	error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+						NULL, 0, NI_NUMERICHOST);
+	switch (error) {
+	case 0:
+		break;
+	case EAI_SYSTEM:
+		xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m",
+				__func__, errno);
+		return NULL;
+	default:
+		xlog(D_GENERAL, "%s: getnameinfo(3) failed: %s",
+				__func__, gai_strerror(error));
+		return NULL;
 	}
-	else {
-		/* never heard of it. misconfigured DNS? */
-		xlog (L_WARNING, "Fake hostname %s for %s - forward lookup doesn't exist",
-		      reverse->h_name, inet_ntoa(*(struct in_addr*)addr));
+
+	ai = host_pton(buf);
+
+	/*
+	 * getaddrinfo(AI_NUMERICHOST) never fills in ai_canonname
+	 */
+	if (ai != NULL) {
+		free(ai->ai_canonname);		/* just in case */
+		ai->ai_canonname = strdup(buf);
+		if (ai->ai_canonname == NULL) {
+			freeaddrinfo(ai);
+			ai = NULL;
+		}
 	}
 
-	free (reverse);
-	return hp;
+	return ai;
 }
+#else	/* !HAVE_GETNAMEINFO */
+__attribute_malloc__
+struct addrinfo *
+host_numeric_addrinfo(const struct sockaddr *sap)
+{
+	const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+	const struct in_addr *addr = &sin->sin_addr;
+	char buf[INET_ADDRSTRLEN];
+	struct addrinfo *ai;
 
+	if (sap->sa_family != AF_INET)
+		return NULL;
 
-#ifdef TEST
-void
-print_host (struct hostent *hp)
-{
-  char **sp;
-
-  if (hp)
-    {
-      printf ("official hostname: %s\n", hp->h_name);
-      printf ("aliases:\n");
-      for (sp = hp->h_aliases; *sp; sp++)
-	printf ("  %s\n", *sp);
-      printf ("IP addresses:\n");
-      for (sp = hp->h_addr_list; *sp; sp++)
-	printf ("  %s\n", inet_ntoa (*(struct in_addr *) *sp));
-    }
-  else
-    printf ("Not host information\n");
-}
+	memset(buf, 0, sizeof(buf));
+	if (inet_ntop(AF_INET, (char *)addr, buf,
+					(socklen_t)sizeof(buf)) == NULL)
+		return NULL;
 
-int
-main (int argc, char **argv)
-{
-  struct hostent *hp = gethostbyname (argv [1]);
-  struct hostent *cp;
-  struct in_addr addr;
-
-  print_host (hp);
-
-  if (hp)
-    {
-      cp = hostent_dup (hp);
-      print_host (cp);
-      free (cp);
-    }
-  printf ("127.0.0.1 == %s: %d\n", argv [1],
-	  matchhostname ("127.0.0.1", argv [1]));
-  addr.s_addr = inet_addr(argv [2]);
-  printf ("%s\n", inet_ntoa (addr));
-  cp = get_hostent ((const char *)&addr, sizeof(addr), AF_INET);
-  print_host (cp);
-  return 0;
+	ai = host_pton(buf);
+
+	/*
+	 * getaddrinfo(AI_NUMERICHOST) never fills in ai_canonname
+	 */
+	if (ai != NULL) {
+		ai->ai_canonname = strdup(buf);
+		if (ai->ai_canonname == NULL) {
+			freeaddrinfo(ai);
+			ai = NULL;
+		}
+	}
+
+	return ai;
 }
-#endif
+#endif	/* !HAVE_GETNAMEINFO */
diff --git a/support/export/nfsctl.c b/support/export/nfsctl.c
index e2877b9..3b9876a 100644
--- a/support/export/nfsctl.c
+++ b/support/export/nfsctl.c
@@ -66,7 +66,7 @@ str_tolower(char *s)
 static int
 cltsetup(struct nfsctl_client *cltarg, nfs_client *clp)
 {
-	int	i;
+	int i, j;
 
 	if (clp->m_type != MCL_FQDN) {
 		xlog(L_ERROR, "internal: can't export non-FQDN host");
@@ -76,10 +76,19 @@ cltsetup(struct nfsctl_client *cltarg, nfs_client *clp)
 	strncpy(cltarg->cl_ident, clp->m_hostname,
 		sizeof (cltarg->cl_ident) - 1);
 	str_tolower(cltarg->cl_ident);
-	cltarg->cl_naddr = clp->m_naddr;
-	for (i = 0; i < cltarg->cl_naddr && i < NFSCLNT_ADDRMAX; i++)
-		cltarg->cl_addrlist[i] = clp->m_addrlist[i];
 
+	j = 0;
+	for (i = 0; i < cltarg->cl_naddr && i < NFSCLNT_ADDRMAX; i++) {
+		const struct sockaddr_in *sin = get_addrlist_in(clp, i);
+		if (sin->sin_family == AF_INET)
+			cltarg->cl_addrlist[j++] = sin->sin_addr;
+	}
+	if (j == 0) {
+		xlog(L_ERROR, "internal: no supported addresses in nfs_client");
+		return 0;
+	}
+
+	cltarg->cl_naddr = j;
 	return 1;
 }
 
diff --git a/support/export/rmtab.c b/support/export/rmtab.c
index b49e1aa..31c0f50 100644
--- a/support/export/rmtab.c
+++ b/support/export/rmtab.c
@@ -19,39 +19,54 @@
 #include "xio.h"
 #include "xlog.h"
 
+/*
+ * See if the entry already exists.  If not,
+ * this was an instantiated wild card, and we
+ * must add it.
+ */
+static void
+rmtab_read_wildcard(struct rmtabent *rep)
+{
+	nfs_export *exp, *exp2;
+	struct addrinfo *ai;
+
+	ai = host_addrinfo(rep->r_client);
+	if (ai == NULL)
+		return;
+
+	exp = export_allowed(ai, rep->r_path);
+	freeaddrinfo(ai);
+	if (exp == NULL)
+		return;
+
+	exp2 = export_lookup(rep->r_client, exp->m_export.e_path, 0);
+	if (exp2 == NULL) {
+		struct exportent ee;
+
+		memset(&ee, 0, sizeof(ee));
+		dupexportent(&ee, &exp->m_export);
+
+		ee.e_hostname = rep->r_client;
+		exp2 = export_create(&ee, 0);
+		exp2->m_changed = exp->m_changed;
+	}
+	exp2->m_mayexport = 1;
+}
+
 int
 rmtab_read(void)
 {
 	struct rmtabent		*rep;
-	nfs_export		*exp = NULL;
 
 	setrmtabent("r");
 	while ((rep = getrmtabent(1, NULL)) != NULL) {
-		struct hostent		*hp = NULL;
 		int			htype;
-		
+
 		htype = client_gettype(rep->r_client);
-		if ((htype == MCL_FQDN || htype == MCL_SUBNETWORK)
-		    && (hp = gethostbyname (rep->r_client))
-		    && (hp = hostent_dup (hp),
-			exp = export_allowed (hp, rep->r_path))) {
-			/* see if the entry already exists, otherwise this was an instantiated
-			 * wild card, and we must add it
-			 */
-			nfs_export *exp2 = export_lookup(rep->r_client,
-							exp->m_export.e_path, 0);
-			if (!exp2) {
-				struct exportent ee;
-				dupexportent(&ee, &exp->m_export);
-				ee.e_hostname = rep->r_client;
-				exp2 = export_create(&ee, 0);
-				exp2->m_changed = exp->m_changed;
-			}
-			free (hp);
-			exp2->m_mayexport = 1;
-		} else if (hp) /* export_allowed failed */
-			free(hp);
+		if (htype == MCL_FQDN || htype == MCL_SUBNETWORK)
+			rmtab_read_wildcard(rep);
 	}
+
 	if (errno == EINVAL) {
 		/* Something goes wrong. We need to fix the rmtab
 		   file. */
diff --git a/support/include/exportfs.h b/support/include/exportfs.h
index 470b2ec..3cf1ee8 100644
--- a/support/include/exportfs.h
+++ b/support/include/exportfs.h
@@ -10,6 +10,8 @@
 #define EXPORTFS_H
 
 #include <netdb.h>
+
+#include "sockaddr.h"
 #include "nfslib.h"
 
 enum {
@@ -35,11 +37,56 @@ typedef struct mclient {
 	char *			m_hostname;
 	int			m_type;
 	int			m_naddr;
-	struct in_addr		m_addrlist[NFSCLNT_ADDRMAX];
+	union nfs_sockaddr	m_addrlist[NFSCLNT_ADDRMAX];
 	int			m_exported;	/* exported to nfsd */
 	int			m_count;
 } nfs_client;
 
+static inline const struct sockaddr *
+get_addrlist(const nfs_client *clp, const int i)
+{
+	return &clp->m_addrlist[i].sa;
+}
+
+static inline const struct sockaddr_in *
+get_addrlist_in(const nfs_client *clp, const int i)
+{
+	return &clp->m_addrlist[i].s4;
+}
+
+static inline const struct sockaddr_in6 *
+get_addrlist_in6(const nfs_client *clp, const int i)
+{
+	return &clp->m_addrlist[i].s6;
+}
+
+static inline void
+set_addrlist_in(nfs_client *clp, const int i, const struct sockaddr_in *sin)
+{
+	memcpy(&clp->m_addrlist[i].s4, sin, sizeof(*sin));
+}
+
+static inline void
+set_addrlist_in6(nfs_client *clp, const int i, const struct sockaddr_in6 *sin6)
+{
+	memcpy(&clp->m_addrlist[i].s6, sin6, sizeof(*sin6));
+}
+
+static inline void
+set_addrlist(nfs_client *clp, const int i, const struct sockaddr *sap)
+{
+	switch (sap->sa_family) {
+	case AF_INET:
+		memcpy(&clp->m_addrlist[i].s4, sap, sizeof(struct sockaddr_in));
+		break;
+#ifdef IPV6_SUPPORTED
+	case AF_INET6:
+		memcpy(&clp->m_addrlist[i].s6, sap, sizeof(struct sockaddr_in6));
+		break;
+#endif
+	}
+}
+
 typedef struct mexport {
 	struct mexport *	m_next;
 	struct mclient *	m_client;
@@ -69,26 +116,26 @@ extern exp_hash_table exportlist[MCL_MAXTYPES];
 extern nfs_client *		clientlist[MCL_MAXTYPES];
 
 nfs_client *			client_lookup(char *hname, int canonical);
-nfs_client *			client_find(struct hostent *);
-void				client_add(nfs_client *);
-nfs_client *			client_dup(nfs_client *, struct hostent *);
+nfs_client *			client_dup(const nfs_client *clp,
+						const struct addrinfo *ai);
 int				client_gettype(char *hname);
-int				client_check(nfs_client *, struct hostent *);
-int				client_match(nfs_client *, char *hname);
+int				client_check(const nfs_client *clp,
+						const struct addrinfo *ai);
 void				client_release(nfs_client *);
 void				client_freeall(void);
-char *				client_compose(struct hostent *he);
-struct hostent *		client_resolve(struct in_addr addr);
-int 				client_member(char *client, char *name);
+char *				client_compose(const struct addrinfo *ai);
+struct addrinfo *		client_resolve(const struct sockaddr *sap);
+int 				client_member(const char *client,
+						const char *name);
 
-int				export_read(char *fname);
-void			export_add(nfs_export *);
+void				export_read(char *fname);
 void				export_reset(nfs_export *);
 nfs_export *			export_lookup(char *hname, char *path, int caconical);
-nfs_export *			export_find(struct hostent *, char *path);
-nfs_export *			export_allowed(struct hostent *, char *path);
+nfs_export *			export_find(const struct addrinfo *ai,
+						const char *path);
+nfs_export *			export_allowed(const struct addrinfo *ai,
+						const char *path);
 nfs_export *			export_create(struct exportent *, int canonical);
-nfs_export *			export_dup(nfs_export *, struct hostent *);
 void				export_freeall(void);
 int				export_export(nfs_export *);
 int				export_unexport(nfs_export *);
@@ -101,6 +148,19 @@ void				xtab_append(nfs_export *);
 
 int				secinfo_addflavor(struct flav_info *, struct exportent *);
 
+char *				host_ntop(const struct sockaddr *sap,
+						char *buf, const size_t buflen);
+__attribute_malloc__
+struct addrinfo *		host_pton(const char *paddr);
+__attribute_malloc__
+struct addrinfo *		host_addrinfo(const char *hostname);
+__attribute_malloc__
+char *				host_canonname(const struct sockaddr *sap);
+__attribute_malloc__
+struct addrinfo *		host_reliable_addrinfo(const struct sockaddr *sap);
+__attribute_malloc__
+struct addrinfo *		host_numeric_addrinfo(const struct sockaddr *sap);
+
 int				rmtab_read(void);
 
 struct nfskey *			key_lookup(char *hname);
diff --git a/support/include/misc.h b/support/include/misc.h
index 9a1b25d..bc5ba23 100644
--- a/support/include/misc.h
+++ b/support/include/misc.h
@@ -15,13 +15,6 @@
 int	randomkey(unsigned char *keyout, int len);
 int	weakrandomkey(unsigned char *keyout, int len);
 
-int	matchhostname(const char *h1, const char *h2); 
-
-struct hostent;
-struct hostent	*hostent_dup(struct hostent *hp);
-struct hostent	*get_hostent (const char *addr, int len, int type);
-struct hostent *get_reliable_hostbyaddr(const char *addr, int len, int type);
-
 extern int is_mountpoint(char *path);
 
 #endif /* MISC_H */
diff --git a/support/include/nfslib.h b/support/include/nfslib.h
index 537a31e..e44cf8f 100644
--- a/support/include/nfslib.h
+++ b/support/include/nfslib.h
@@ -152,6 +152,8 @@ void qword_addhex(char **bpp, int *lp, char *buf, int blen);
 void qword_addint(char **bpp, int *lp, int n);
 void qword_adduint(char **bpp, int *lp, unsigned int n);
 void qword_addeol(char **bpp, int *lp);
+int qword_get_uint(char **bpp, unsigned int *anint);
+void qword_printuint(FILE *f, unsigned int num);
 
 void closeall(int min);
 
diff --git a/support/include/nfsrpc.h b/support/include/nfsrpc.h
index 4db35ab..6ebefca 100644
--- a/support/include/nfsrpc.h
+++ b/support/include/nfsrpc.h
@@ -160,4 +160,7 @@ extern int		nfs_rpc_ping(const struct sockaddr *sap,
 				const unsigned short protocol,
 				const struct timeval *timeout);
 
+/* create AUTH_SYS handle with no supplemental groups */
+extern AUTH *			 nfs_authsys_create(void);
+
 #endif	/* !__NFS_UTILS_NFSRPC_H */
diff --git a/support/nfs/cacheio.c b/support/nfs/cacheio.c
index bdf5d84..55fa45d 100644
--- a/support/nfs/cacheio.c
+++ b/support/nfs/cacheio.c
@@ -148,6 +148,11 @@ void qword_printint(FILE *f, int num)
 	fprintf(f, "%d ", num);
 }
 
+void qword_printuint(FILE *f, unsigned int num)
+{
+	fprintf(f, "%u ", num);
+}
+
 int qword_eol(FILE *f)
 {
 	int err;
@@ -236,6 +241,20 @@ int qword_get_int(char **bpp, int *anint)
 	return 0;
 }
 
+int qword_get_uint(char **bpp, unsigned int *anint)
+{
+	char buf[50];
+	char *ep;
+	unsigned int rv;
+	int len = qword_get(bpp, buf, 50);
+	if (len < 0) return -1;
+	if (len ==0) return -1;
+	rv = strtoul(buf, &ep, 0);
+	if (*ep) return -1;
+	*anint = rv;
+	return 0;
+}
+
 #define READLINE_BUFFER_INCREMENT 2048
 
 int readline(int fd, char **buf, int *lenp)
diff --git a/support/nfs/rmtab.c b/support/nfs/rmtab.c
index a28abf3..ca789a3 100644
--- a/support/nfs/rmtab.c
+++ b/support/nfs/rmtab.c
@@ -19,6 +19,18 @@
 #include <signal.h>
 #include "nfslib.h"
 
+/*
+ * Colons in incoming IPv6 presentation addresses have to
+ * replaced with another character, since rmtab already
+ * uses colons to delineate fields.
+ *
+ * Use a printable character, but one that would never be
+ * found in a presentation address or domain name
+ */
+#define IPV6_COLON	';'
+
+#define LINELEN		(2048)
+
 static FILE	*rmfp = NULL;
 
 int
@@ -56,7 +68,8 @@ struct rmtabent *
 fgetrmtabent(FILE *fp, int log, long *pos)
 {
 	static struct rmtabent	re;
-	char	buf[2048], *count, *host, *path;
+	char		*count, *host, *path, *c;
+	static char	buf[LINELEN];
 
 	errno = 0;
 	if (!fp)
@@ -84,10 +97,16 @@ fgetrmtabent(FILE *fp, int log, long *pos)
 		else
 			re.r_count = 1;
 	} while (0);
+
 	strncpy(re.r_client, host, sizeof (re.r_client) - 1);
 	re.r_client[sizeof (re.r_client) - 1] = '\0';
+	for (c = re.r_client; *c != '\0'; c++)
+		if (*c == IPV6_COLON)
+			*c = ':';
+
 	strncpy(re.r_path, path, sizeof (re.r_path) - 1);
 	re.r_path[sizeof (re.r_path) - 1] = '\0';
+
 	return &re;
 }
 
@@ -100,10 +119,27 @@ putrmtabent(struct rmtabent *rep, long *pos)
 void
 fputrmtabent(FILE *fp, struct rmtabent *rep, long *pos)
 {
+	static char	buf[LINELEN];
+	char		*c;
+
 	if (!fp || (pos && fseek (fp, *pos, SEEK_SET) != 0))
 		return;
-	fprintf(fp, "%s:%s:0x%.8x\n", rep->r_client, rep->r_path,
-		rep->r_count);
+
+	/*
+	 * To avoid confusing the token parser in fgetrmtabent(),
+	 * convert colons in incoming IPv6 presentation addresses
+	 * to semicolons.
+	 */
+	if (strlen(rep->r_client) > sizeof(buf)) {
+		xlog(L_ERROR, "client name too large");
+		return;
+	}
+	strncpy(buf, rep->r_client, sizeof(buf));
+	for (c = buf; *c != '\0'; c++)
+		if (*c == ':')
+			*c = IPV6_COLON;
+
+	(void)fprintf(fp, "%s:%s:0x%.8x\n", buf, rep->r_path, rep->r_count);
 }
 
 void
diff --git a/support/nfs/rpc_socket.c b/support/nfs/rpc_socket.c
index 0e20824..c14efe8 100644
--- a/support/nfs/rpc_socket.c
+++ b/support/nfs/rpc_socket.c
@@ -557,3 +557,24 @@ rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[])
 
 	return program;
 }
+
+/*
+ * AUTH_SYS doesn't allow more than 16 gids in the supplemental group list.
+ * If there are more than that, trying to determine which ones to include
+ * in the list is problematic. This function creates an auth handle that
+ * only has the primary gid in the supplemental gids list. It's intended to
+ * be used for protocols where credentials really don't matter much (the MNT
+ * protocol, for instance).
+ */
+AUTH *
+nfs_authsys_create(void)
+{
+	char machname[MAXHOSTNAMELEN + 1];
+	uid_t	uid = geteuid();
+	gid_t	gid = getegid();
+
+	if (gethostname(machname, sizeof(machname)) == -1)
+		return NULL;
+
+	return authunix_create(machname, uid, gid, 1, &gid);
+}
diff --git a/support/nsm/file.c b/support/nsm/file.c
index d469219..f4baeb9 100644
--- a/support/nsm/file.c
+++ b/support/nsm/file.c
@@ -67,7 +67,9 @@
 #endif
 
 #include <sys/types.h>
+#ifdef HAVE_SYS_CAPABILITY_H
 #include <sys/capability.h>
+#endif
 #include <sys/prctl.h>
 #include <sys/stat.h>
 
@@ -347,6 +349,7 @@ nsm_is_default_parentdir(void)
 static _Bool
 nsm_clear_capabilities(void)
 {
+#ifdef HAVE_SYS_CAPABILITY_H
 	cap_t caps;
 
 	caps = cap_from_text("cap_net_bind_service=ep");
@@ -362,6 +365,7 @@ nsm_clear_capabilities(void)
 	}
 
 	(void)cap_free(caps);
+#endif
 	return true;
 }
 
diff --git a/tests/t0001-statd-basic-mon-unmon.sh b/tests/t0001-statd-basic-mon-unmon.sh
old mode 100644
new mode 100755
diff --git a/tools/Makefile.am b/tools/Makefile.am
index db15346..f2ce282 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -6,6 +6,6 @@ if CONFIG_RPCGEN
 OPTDIRS += rpcgen
 endif
 
-SUBDIRS = locktest rpcdebug nlmtest $(OPTDIRS)
+SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat $(OPTDIRS)
 
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/tools/mountstats/Makefile.am b/tools/mountstats/Makefile.am
new file mode 100644
index 0000000..ca617a2
--- /dev/null
+++ b/tools/mountstats/Makefile.am
@@ -0,0 +1,13 @@
+## Process this file with automake to produce Makefile.in
+PYTHON_FILES =  mountstats.py
+
+man8_MANS	= mountstats.man
+
+EXTRA_DIST	= $(man8_MANS) $(PYTHON_FILES)
+
+all-local: $(PYTHON_FILES)
+
+install-data-hook:
+	$(INSTALL) --mode 755 mountstats.py $(DESTDIR)$(sbindir)/mountstats
+
+MAINTAINERCLEANFILES=Makefile.in
diff --git a/tools/mountstats/mountstats.man b/tools/mountstats/mountstats.man
new file mode 100644
index 0000000..0de31b7
--- /dev/null
+++ b/tools/mountstats/mountstats.man
@@ -0,0 +1,32 @@
+.\"
+.\" mountstats(8)
+.\"
+.TH mountstats 8 "15 Apr 2010"
+.SH NAME
+mountstats \- Displays NFS client per-mount statistics
+.SH SYNOPSIS
+.BI "mountstats ["<options> "] " <mount_point> " [ " <mount_point> "]" 
+.SH DESCRIPTION
+The
+.B mountstats
+command displays NFS client statisitics on each given
+.I <mount_point>
+.SH OPTIONS
+.TP
+.B " \-\-nfs
+display only the NFS statistics
+.TP
+.B " \-\-rpc 
+display only the RPC statistics
+.TP
+.B " \-\-version 
+display the version of this command
+.SH FILES
+.TP
+.B /proc/self/mountstats
+.SH SEE ALSO
+.BR iostat (8),
+.BR nfsiostat (8),
+.BR nfsstat(8)
+.SH AUTHOR
+Chuck Lever <chuck.lever@oracle.com>
diff --git a/tools/nfs-iostat/Makefile.am b/tools/nfs-iostat/Makefile.am
new file mode 100644
index 0000000..30f4054
--- /dev/null
+++ b/tools/nfs-iostat/Makefile.am
@@ -0,0 +1,13 @@
+## Process this file with automake to produce Makefile.in
+PYTHON_FILES =  nfs-iostat.py
+
+man8_MANS	= nfsiostat.man
+
+EXTRA_DIST	= $(man8_MANS) $(PYTHON_FILES)
+
+all-local: $(PYTHON_FILES)
+
+install-data-hook:
+	$(INSTALL) --mode 755 nfs-iostat.py $(DESTDIR)$(sbindir)/nfsiostat
+
+MAINTAINERCLEANFILES=Makefile.in
diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py
index 2d0b143..1207674 100644
--- a/tools/nfs-iostat/nfs-iostat.py
+++ b/tools/nfs-iostat/nfs-iostat.py
@@ -366,6 +366,12 @@ class DeviceData:
         sends = float(self.__rpc_data['rpcsends'])
         if sample_time == 0:
             sample_time = float(self.__nfs_data['age'])
+        #  sample_time could still be zero if the export was just mounted.
+        #  Set it to 1 to avoid divide by zero errors in this case since we'll
+        #  likely still have relevant mount statistics to show.
+        #
+        if sample_time == 0:
+            sample_time = 1;
         if sends != 0:
             backlog = (float(self.__rpc_data['backlogutil']) / sends) / sample_time
         else:
diff --git a/tools/nfs-iostat/nfsiostat.man b/tools/nfs-iostat/nfsiostat.man
new file mode 100644
index 0000000..99e04fb
--- /dev/null
+++ b/tools/nfs-iostat/nfsiostat.man
@@ -0,0 +1,71 @@
+.\"
+.\" nfsiostat(8)
+.\"
+.TH nfsiostat 8 "15 Apr 2010"
+.SH NAME
+nfsiostat \- Emulate iostat for NFS mount points using /proc/self/mountstats
+.SH SYNOPSIS
+.BI "nfsiostat [[" <interval> "] [" <count> "]] [" <options> "]["<mount_point> "]
+.SH DESCRIPTION
+The
+.B nfsiostat
+command displays NFS client per-mount statisitics. 
+.TP 
+<interval>
+specifies the amount of time in seconds between each report.
+The first report contains statistics for the time since each file
+system was mounted.  Each subsequent report contains statistics collected
+during the interval since the previous report.
+.TP
+<count>
+If the
+.I <count>
+parameter is
+specified, the value of 
+.I <count> 
+determines the number of reports generated at
+. <interval> 
+seconds apart. if the interval parameter is 
+specified without the
+.I <count> 
+parameter, the command generates reports continuously.
+.TP
+<options>
+Define below
+.TP
+<mount_point>
+If one or more
+.I <mount point> 
+names are specified, statistics for only these mount points will
+be displayed.  Otherwise, all NFS mount points on the client are listed.
+.SH OPTIONS
+.TP
+.B \-a " or " \-\-attr
+displays statistics related to the attribute cache
+.TP
+.B \-d " or " \-\-dir 
+displays statistics related to directory operations
+.TP
+.B \-h " or " \-\-help 
+shows help message and exit
+.TP
+.B \-l LIST or " \-\-list=LIST 
+only print stats for first LIST mount points
+.TP
+.B \-p " or " \-\-page
+displays statistics related to the page cache
+.TP
+.B \-s " or " \-\-sort
+Sort NFS mount points by ops/second
+.TP
+.B \-\-version
+show program's version number and exit
+.SH FILES
+.TP
+.B /proc/self/mountstats
+.SH SEE ALSO
+.BR iostat (8),
+.BR mountstats (8),
+.BR nfsstat(8)
+.SH AUTHOR
+Chuck Lever <chuck.lever@oracle.com>
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index 331e57e..edc1625 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c
@@ -15,6 +15,7 @@
 #include <sys/vfs.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
@@ -36,6 +37,7 @@ static void	dump(int verbose);
 static void	error(nfs_export *exp, int err);
 static void	usage(void);
 static void	validate_export(nfs_export *exp);
+static int	matchhostname(const char *hostname1, const char *hostname2);
 
 int
 main(int argc, char **argv)
@@ -232,7 +234,7 @@ exportfs(char *arg, char *options, int verbose)
 {
 	struct exportent *eep;
 	nfs_export	*exp;
-	struct hostent	*hp = NULL;
+	struct addrinfo	*ai = NULL;
 	char		*path;
 	char		*hname = arg;
 	int		htype;
@@ -245,32 +247,21 @@ exportfs(char *arg, char *options, int verbose)
 		return;
 	}
 
-	if ((htype = client_gettype(hname)) == MCL_FQDN &&
-	    (hp = gethostbyname(hname)) != NULL) {
-		struct hostent *hp2 = hostent_dup (hp);
-		hp = gethostbyaddr(hp2->h_addr, hp2->h_length,
-				   hp2->h_addrtype);
-		if (hp) {
-			free(hp2);
-			hp = hostent_dup(hp);
-		} else
-			hp = hp2;
-		exp = export_find(hp, path);
-		hname = hp->h_name;
-	} else {
+	if ((htype = client_gettype(hname)) == MCL_FQDN) {
+		ai = host_addrinfo(hname);
+		if (ai != NULL) {
+			exp = export_find(ai, path);
+			hname = ai->ai_canonname;
+		}
+	} else
 		exp = export_lookup(hname, path, 0);
-	}
 
 	if (!exp) {
 		if (!(eep = mkexportent(hname, path, options)) ||
-		    !(exp = export_create(eep, 0))) {
-			if (hp) free (hp);
-			return;
-		}
-	} else if (!updateexportent(&exp->m_export, options)) {
-		if (hp) free (hp);
-		return;
-	}
+		    !(exp = export_create(eep, 0)))
+			goto out;
+	} else if (!updateexportent(&exp->m_export, options))
+		goto out;
 
 	if (verbose)
 		printf("exporting %s:%s\n", exp->m_client->m_hostname, 
@@ -280,14 +271,16 @@ exportfs(char *arg, char *options, int verbose)
 	exp->m_changed = 1;
 	exp->m_warned = 0;
 	validate_export(exp);
-	if (hp) free (hp);
+
+out:
+	freeaddrinfo(ai);
 }
 
 static void
 unexportfs(char *arg, int verbose)
 {
 	nfs_export	*exp;
-	struct hostent	*hp = NULL;
+	struct addrinfo *ai = NULL;
 	char		*path;
 	char		*hname = arg;
 	int		htype;
@@ -302,10 +295,9 @@ unexportfs(char *arg, int verbose)
 	}
 
 	if ((htype = client_gettype(hname)) == MCL_FQDN) {
-		if ((hp = gethostbyname(hname)) != 0) {
-			hp = hostent_dup (hp);
-			hname = (char *) hp->h_name;
-		}
+		ai = host_addrinfo(hname);
+		if (ai)
+			hname = ai->ai_canonname;
 	}
 
 	for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
@@ -341,7 +333,7 @@ unexportfs(char *arg, int verbose)
 		exp->m_mayexport = 0;
 	}
 
-	if (hp) free (hp);
+	freeaddrinfo(ai);
 }
 
 static int can_test(void)
@@ -431,6 +423,82 @@ validate_export(nfs_export *exp)
 	}
 }
 
+static _Bool
+is_hostname(const char *sp)
+{
+	if (*sp == '\0' || *sp == '@')
+		return false;
+
+	for (; *sp != '\0'; sp++) {
+		if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
+			return false;
+		if (*sp == '\\' && sp[1] != '\0')
+			sp++;
+	}
+
+	return true;
+}
+
+static _Bool
+compare_sockaddrs4(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	const struct sockaddr_in *sin1 = (const struct sockaddr_in *)sa1;
+	const struct sockaddr_in *sin2 = (const struct sockaddr_in *)sa2;
+	return sin1->sin_addr.s_addr == sin2->sin_addr.s_addr;
+}
+
+static _Bool
+compare_sockaddrs(const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+	if (sa1->sa_family == sa2->sa_family)
+		switch (sa1->sa_family) {
+		case AF_INET:
+			return compare_sockaddrs4(sa1, sa2);
+		}
+
+	return false;
+}
+
+static int
+matchhostname(const char *hostname1, const char *hostname2)
+{
+	struct addrinfo *results1 = NULL, *results2 = NULL;
+	struct addrinfo *ai1, *ai2;
+	int result = 0;
+
+	if (strcasecmp(hostname1, hostname2) == 0)
+		return 1;
+
+	/*
+	 * Don't pass export wildcards or netgroup names to DNS
+	 */
+	if (!is_hostname(hostname1) || !is_hostname(hostname2))
+		return 0;
+
+	results1 = host_addrinfo(hostname1);
+	if (results1 == NULL)
+		goto out;
+	results2 = host_addrinfo(hostname2);
+	if (results2 == NULL)
+		goto out;
+
+	if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
+		result = 1;
+		goto out;
+	}
+
+	for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
+		for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
+			if (compare_sockaddrs(ai1->ai_addr, ai2->ai_addr)) {
+				result = 1;
+				break;
+			}
+
+out:
+	freeaddrinfo(results1);
+	freeaddrinfo(results2);
+	return result;
+}
 
 static char
 dumpopt(char c, char *fmt, ...)
diff --git a/utils/gssd/context.h b/utils/gssd/context.h
index be47f9c..c9cb0bd 100644
--- a/utils/gssd/context.h
+++ b/utils/gssd/context.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (c) 2004 The Regents of the University of Michigan.
+  Copyright (c) 2004,2008 The Regents of the University of Michigan.
   All rights reserved.
 
   Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,10 @@
 /* Hopefully big enough to hold any serialized context */
 #define MAX_CTX_LEN 4096
 
+/* New context format flag values */
+#define KRB5_CTX_FLAG_INITIATOR         0x00000001
+#define KRB5_CTX_FLAG_CFX               0x00000002
+#define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY   0x00000004
 
 int serialize_context_for_kernel(gss_ctx_id_t ctx, gss_buffer_desc *buf,
 				 gss_OID mech, int32_t *endtime);
diff --git a/utils/gssd/context_lucid.c b/utils/gssd/context_lucid.c
index 4a682ae..b87bf76 100644
--- a/utils/gssd/context_lucid.c
+++ b/utils/gssd/context_lucid.c
@@ -42,6 +42,7 @@
 #include <stdio.h>
 #include <syslog.h>
 #include <string.h>
+#include <errno.h>
 
 #include <gssapi/gssapi_krb5.h>
 
@@ -119,15 +120,13 @@ prepare_krb5_rfc1964_buffer(gss_krb5_lucid_context_v1_t *lctx,
 	 * Note that the rfc1964 version only supports DES enctypes.
 	 */
 	if (lctx->rfc1964_kd.ctx_key.type != 4) {
-		printerr(1, "prepare_krb5_rfc1964_buffer: "
-			    "overriding heimdal keytype (%d => %d)\n",
-			    lctx->rfc1964_kd.ctx_key.type, 4);
+		printerr(2, "%s: overriding heimdal keytype (%d => %d)\n",
+			 __FUNCTION__, lctx->rfc1964_kd.ctx_key.type, 4);
 		lctx->rfc1964_kd.ctx_key.type = 4;
 	}
 #endif
-	printerr(2, "prepare_krb5_rfc1964_buffer: serializing keys with "
-		 "enctype %d and length %d\n",
-		 lctx->rfc1964_kd.ctx_key.type,
+	printerr(2, "%s: serializing keys with enctype %d and length %d\n",
+		 __FUNCTION__, lctx->rfc1964_kd.ctx_key.type,
 		 lctx->rfc1964_kd.ctx_key.length);
 
 	/* derive the encryption key and copy it into buffer */
@@ -158,11 +157,100 @@ out_err:
 	return -1;
 }
 
+/* Flags for version 2 context flags */
+#define KRB5_CTX_FLAG_INITIATOR		0x00000001
+#define KRB5_CTX_FLAG_CFX		0x00000002
+#define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY	0x00000004
+
+/*
+ * Prepare a new-style buffer, as defined in rfc4121 (a.k.a. cfx),
+ * to send to the kernel for newer encryption types -- or for DES3.
+ *
+ * The new format is:
+ *
+ *	u32 flags;
+ *	#define KRB5_CTX_FLAG_INITIATOR		0x00000001
+ *	#define KRB5_CTX_FLAG_CFX		0x00000002
+ *	#define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY	0x00000004
+ *	s32 endtime;
+ *	u64 seq_send;
+ *	u32  enctype;			( encrption type of key )
+ *	raw key;			( raw key bytes (kernel will derive))
+ *
+ */
 static int
-prepare_krb5_rfc_cfx_buffer(gss_krb5_lucid_context_v1_t *lctx,
+prepare_krb5_rfc4121_buffer(gss_krb5_lucid_context_v1_t *lctx,
 	gss_buffer_desc *buf, int32_t *endtime)
 {
-	printerr(0, "ERROR: prepare_krb5_rfc_cfx_buffer: not implemented\n");
+	char *p, *end;
+	uint32_t v2_flags = 0;
+	uint32_t enctype;
+	uint32_t keysize;
+
+	if (!(buf->value = calloc(1, MAX_CTX_LEN)))
+		goto out_err;
+	p = buf->value;
+	end = buf->value + MAX_CTX_LEN;
+
+	/* Version 2 */
+	if (lctx->initiate)
+		v2_flags |= KRB5_CTX_FLAG_INITIATOR;
+	if (lctx->protocol != 0)
+		v2_flags |= KRB5_CTX_FLAG_CFX;
+	if (lctx->protocol != 0 && lctx->cfx_kd.have_acceptor_subkey == 1)
+		v2_flags |= KRB5_CTX_FLAG_ACCEPTOR_SUBKEY;
+
+	if (WRITE_BYTES(&p, end, v2_flags)) goto out_err;
+	if (WRITE_BYTES(&p, end, lctx->endtime)) goto out_err;
+	if (WRITE_BYTES(&p, end, lctx->send_seq)) goto out_err;
+
+	/* Protocol 0 here implies DES3 or RC4 */
+	printerr(2, "%s: protocol %d\n", __FUNCTION__, lctx->protocol);
+	if (lctx->protocol == 0) {
+		enctype = lctx->rfc1964_kd.ctx_key.type;
+		keysize = lctx->rfc1964_kd.ctx_key.length;
+	} else {
+		if (lctx->cfx_kd.have_acceptor_subkey) {
+			enctype = lctx->cfx_kd.acceptor_subkey.type;
+			keysize = lctx->cfx_kd.acceptor_subkey.length;
+		} else {
+			enctype = lctx->cfx_kd.ctx_key.type;
+			keysize = lctx->cfx_kd.ctx_key.length;
+		}
+	}
+	printerr(2, "%s: serializing key with enctype %d and size %d\n",
+		 __FUNCTION__, enctype, keysize);
+
+	if (WRITE_BYTES(&p, end, enctype)) goto out_err;
+
+	if (lctx->protocol == 0) {
+		if (write_bytes(&p, end, lctx->rfc1964_kd.ctx_key.data,
+				lctx->rfc1964_kd.ctx_key.length))
+			goto out_err;
+	} else {
+		if (lctx->cfx_kd.have_acceptor_subkey) {
+			if (write_bytes(&p, end,
+					lctx->cfx_kd.acceptor_subkey.data,
+					lctx->cfx_kd.acceptor_subkey.length))
+				goto out_err;
+		} else {
+			if (write_bytes(&p, end, lctx->cfx_kd.ctx_key.data,
+					lctx->cfx_kd.ctx_key.length))
+				goto out_err;
+		}
+	}
+
+	buf->length = p - (char *)buf->value;
+	return 0;
+
+out_err:
+	printerr(0, "ERROR: %s: failed serializing krb5 context for kernel\n",
+		 __FUNCTION__);
+	if (buf->value) {
+		free(buf->value);
+		buf->value = NULL;
+	}
+	buf->length = 0;
 	return -1;
 }
 
@@ -176,7 +264,7 @@ serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime)
 	gss_krb5_lucid_context_v1_t *lctx = 0;
 	int retcode = 0;
 
-	printerr(2, "DEBUG: serialize_krb5_ctx: lucid version!\n");
+	printerr(2, "DEBUG: %s: lucid version!\n", __FUNCTION__);
 	maj_stat = gss_export_lucid_sec_context(&min_stat, &ctx,
 						1, &return_ctx);
 	if (maj_stat != GSS_S_COMPLETE) {
@@ -198,11 +286,20 @@ serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime)
 		break;
 	}
 
-	/* Now lctx points to a lucid context that we can send down to kernel */
-	if (lctx->protocol == 0)
+	/*
+	 * Now lctx points to a lucid context that we can send down to kernel
+	 *
+	 * Note: we send down different information to the kernel depending
+	 * on the protocol version and the enctyption type.
+	 * For protocol version 0 with all enctypes besides DES3, we use
+	 * the original format.  For protocol version != 0 or DES3, we
+	 * send down the new style information.
+	 */
+
+	if (lctx->protocol == 0 && lctx->rfc1964_kd.ctx_key.type <= 4)
 		retcode = prepare_krb5_rfc1964_buffer(lctx, buf, endtime);
 	else
-		retcode = prepare_krb5_rfc_cfx_buffer(lctx, buf, endtime);
+		retcode = prepare_krb5_rfc4121_buffer(lctx, buf, endtime);
 
 	maj_stat = gss_free_lucid_sec_context(&min_stat, ctx, return_ctx);
 	if (maj_stat != GSS_S_COMPLETE) {
@@ -212,8 +309,8 @@ serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime)
 	}
 
 	if (retcode) {
-		printerr(1, "serialize_krb5_ctx: prepare_krb5_*_buffer "
-			 "failed (retcode = %d)\n", retcode);
+		printerr(1, "%s: prepare_krb5_*_buffer failed (retcode = %d)\n",
+			 __FUNCTION__, retcode);
 		goto out_err;
 	}
 
@@ -223,4 +320,7 @@ out_err:
 	printerr(0, "ERROR: failed serializing krb5 context for kernel\n");
 	return -1;
 }
+
+
+
 #endif /* HAVE_LUCID_CONTEXT_SUPPORT */
diff --git a/utils/gssd/context_mit.c b/utils/gssd/context_mit.c
index 709a903..f9cbb02 100644
--- a/utils/gssd/context_mit.c
+++ b/utils/gssd/context_mit.c
@@ -1,5 +1,5 @@
 /*
-  Copyright (c) 2004 The Regents of the University of Michigan.
+  Copyright (c) 2004-2006 The Regents of the University of Michigan.
   All rights reserved.
 
   Redistribution and use in source and binary forms, with or without
@@ -38,6 +38,7 @@
 #include <stdio.h>
 #include <syslog.h>
 #include <string.h>
+#include <errno.h>
 #include <gssapi/gssapi.h>
 #include <rpc/rpc.h>
 #include <rpc/auth_gss.h>
@@ -52,8 +53,7 @@
 /* XXX argggg, there's gotta be a better way than just duplicating this
  * whole struct.  Unfortunately, this is in a "private" header file,
  * so this is our best choice at this point :-/
- *
- * XXX Does this match the Heimdal definition?  */
+ */
 
 typedef struct _krb5_gss_ctx_id_rec {
    unsigned int initiate : 1;   /* nonzero if initiating, zero if accepting */
@@ -156,50 +156,120 @@ serialize_krb5_ctx(gss_ctx_id_t ctx, gss_buffer_desc *buf, int32_t *endtime)
 {
 	krb5_gss_ctx_id_t kctx = ((gss_union_ctx_id_t)ctx)->internal_ctx_id;
 	char *p, *end;
-	static int constant_one = 1;
 	static int constant_zero = 0;
+	static int constant_one = 1;
+	static int constant_two = 2;
 	uint32_t word_seq_send;
+	u_int64_t seq_send_64bit;
+	uint32_t v2_flags = 0;
 
 	if (!(buf->value = calloc(1, MAX_CTX_LEN)))
 		goto out_err;
 	p = buf->value;
 	end = buf->value + MAX_CTX_LEN;
 
-	if (kctx->initiate) {
-		if (WRITE_BYTES(&p, end, constant_one)) goto out_err;
-	}
-	else {
-		if (WRITE_BYTES(&p, end, constant_zero)) goto out_err;
-	}
-	if (kctx->seed_init) {
-		if (WRITE_BYTES(&p, end, constant_one)) goto out_err;
-	}
-	else {
-		if (WRITE_BYTES(&p, end, constant_zero)) goto out_err;
-	}
-	if (write_bytes(&p, end, &kctx->seed, sizeof(kctx->seed)))
+	switch (kctx->enc->enctype) {
+	case ENCTYPE_DES_CBC_CRC:
+	case ENCTYPE_DES_CBC_MD4:
+	case ENCTYPE_DES_CBC_MD5:
+	case ENCTYPE_DES_CBC_RAW:
+		/* Old format of context to the kernel */
+		if (kctx->initiate) {
+			if (WRITE_BYTES(&p, end, constant_one)) goto out_err;
+		}
+		else {
+			if (WRITE_BYTES(&p, end, constant_zero)) goto out_err;
+		}
+		if (kctx->seed_init) {
+			if (WRITE_BYTES(&p, end, constant_one)) goto out_err;
+		}
+		else {
+			if (WRITE_BYTES(&p, end, constant_zero)) goto out_err;
+		}
+		if (write_bytes(&p, end, &kctx->seed, sizeof(kctx->seed)))
+			goto out_err;
+		if (WRITE_BYTES(&p, end, kctx->signalg)) goto out_err;
+		if (WRITE_BYTES(&p, end, kctx->sealalg)) goto out_err;
+		if (WRITE_BYTES(&p, end, kctx->endtime)) goto out_err;
+		word_seq_send = kctx->seq_send;
+		if (WRITE_BYTES(&p, end, word_seq_send)) goto out_err;
+		if (write_oid(&p, end, kctx->mech_used)) goto out_err;
+
+		printerr(2, "serialize_krb5_ctx: serializing keys with "
+			 "enctype %d and length %d\n",
+			 kctx->enc->enctype, kctx->enc->length);
+
+		if (write_keyblock(&p, end, kctx->enc)) goto out_err;
+		if (write_keyblock(&p, end, kctx->seq)) goto out_err;
+		break;
+	case ENCTYPE_DES3_CBC_RAW:
+	case ENCTYPE_DES3_CBC_SHA1:
+	case ENCTYPE_ARCFOUR_HMAC:
+	case ENCTYPE_ARCFOUR_HMAC_EXP:
+	case ENCTYPE_AES128_CTS_HMAC_SHA1_96:
+	case ENCTYPE_AES256_CTS_HMAC_SHA1_96:
+		/* New format of context to the kernel */
+		/* u32 flags;
+		 * #define KRB5_CTX_FLAG_INITIATOR        0x00000001
+		 * #define KRB5_CTX_FLAG_CFX              0x00000002
+		 * #define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY  0x00000004
+		 * s32 endtime;
+		 * u64 seq_send;
+		 * u32  enctype;
+		 * rawkey data
+		 */
+
+		if (kctx->initiate)
+			v2_flags |= KRB5_CTX_FLAG_INITIATOR;
+		if (kctx->proto == 1)
+			v2_flags |= KRB5_CTX_FLAG_CFX;
+		if (kctx->have_acceptor_subkey)
+			v2_flags |= KRB5_CTX_FLAG_ACCEPTOR_SUBKEY;
+		if (WRITE_BYTES(&p, end, v2_flags)) goto out_err;
+		if (WRITE_BYTES(&p, end, kctx->endtime)) goto out_err;
+
+		seq_send_64bit = kctx->seq_send;
+		if (WRITE_BYTES(&p, end, seq_send_64bit)) goto out_err;
+
+		if (kctx->have_acceptor_subkey) {
+			if (WRITE_BYTES(&p, end, kctx->acceptor_subkey->enctype))
+				goto out_err;
+			printerr(2, "serialize_krb5_ctx: serializing subkey "
+				 "with enctype %d and size %d\n",
+				 kctx->acceptor_subkey->enctype,
+				 kctx->acceptor_subkey->length);
+
+			if (write_bytes(&p, end,
+					kctx->acceptor_subkey->contents,
+					kctx->acceptor_subkey->length))
+				goto out_err;
+		} else {
+			if (WRITE_BYTES(&p, end, kctx->enc->enctype))
+				goto out_err;
+			printerr(2, "serialize_krb5_ctx: serializing key "
+				 "with enctype %d and size %d\n",
+				 kctx->enc->enctype, kctx->enc->length);
+
+			if (write_bytes(&p, end, kctx->enc->contents,
+					kctx->enc->length))
+				goto out_err;
+		}
+		break;
+	default:
+		printerr(0, "ERROR: serialize_krb5_ctx: unsupported encryption "
+			 "algorithm %d\n", kctx->enc->enctype);
 		goto out_err;
-	if (WRITE_BYTES(&p, end, kctx->signalg)) goto out_err;
-	if (WRITE_BYTES(&p, end, kctx->sealalg)) goto out_err;
-	if (WRITE_BYTES(&p, end, kctx->endtime)) goto out_err;
-	if (endtime)
-		*endtime = kctx->endtime;
-	word_seq_send = kctx->seq_send;
-	if (WRITE_BYTES(&p, end, word_seq_send)) goto out_err;
-	if (write_oid(&p, end, kctx->mech_used)) goto out_err;
-
-	printerr(2, "serialize_krb5_ctx: serializing keys with "
-		 "enctype %d and length %d\n",
-		 kctx->enc->enctype, kctx->enc->length);
-
-	if (write_keyblock(&p, end, kctx->enc)) goto out_err;
-	if (write_keyblock(&p, end, kctx->seq)) goto out_err;
+	}
 
 	buf->length = p - (char *)buf->value;
 	return 0;
+
 out_err:
 	printerr(0, "ERROR: failed serializing krb5 context for kernel\n");
-	if (buf->value) free(buf->value);
+	if (buf->value) {
+		free(buf->value);
+	}
+	buf->value = NULL;
 	buf->length = 0;
 	return -1;
 }
diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
index be4fb11..a55418b 100644
--- a/utils/gssd/gssd_proc.c
+++ b/utils/gssd/gssd_proc.c
@@ -600,6 +600,67 @@ update_client_list(void)
 	return retval;
 }
 
+/* Encryption types supported by the kernel rpcsec_gss code */
+int num_krb5_enctypes = 0;
+krb5_enctype *krb5_enctypes = NULL;
+
+/*
+ * Parse the supported encryption type information
+ */
+static int
+parse_enctypes(char *enctypes)
+{
+	int n = 0;
+	char *curr, *comma;
+	int i;
+	static char *cached_types;
+
+	if (cached_types && strcmp(cached_types, enctypes) == 0)
+		return 0;
+	free(cached_types);
+
+	if (krb5_enctypes != NULL) {
+		free(krb5_enctypes);
+		krb5_enctypes = NULL;
+		num_krb5_enctypes = 0;
+	}
+
+	/* count the number of commas */
+	for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) {
+		comma = strchr(curr, ',');
+		if (comma != NULL)
+			n++;
+		else
+			break;
+	}
+	/* If no more commas and we're not at the end, there's one more value */
+	if (*curr != '\0')
+		n++;
+
+	/* Empty string, return an error */
+	if (n == 0)
+		return ENOENT;
+
+	/* Allocate space for enctypes array */
+	if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) {
+		return ENOMEM;
+	}
+
+	/* Now parse each value into the array */
+	for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) {
+		krb5_enctypes[i++] = atoi(curr);
+		comma = strchr(curr, ',');
+		if (comma == NULL)
+			break;
+	}
+
+	num_krb5_enctypes = n;
+	if ((cached_types = malloc(strlen(enctypes)+1)))
+		strcpy(cached_types, enctypes);
+
+	return 0;
+}
+
 static int
 do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
 	    gss_buffer_desc *context_token)
@@ -1133,6 +1194,7 @@ handle_gssd_upcall(struct clnt_info *clp)
 	char			*mech = NULL;
 	char			*target = NULL;
 	char			*service = NULL;
+	char			*enctypes = NULL;
 
 	printerr(1, "handling gssd upcall (%s)\n", clp->dirname);
 
@@ -1176,6 +1238,23 @@ handle_gssd_upcall(struct clnt_info *clp)
 		goto out;
 	}
 
+	/* read supported encryption types if supplied */
+	if ((p = strstr(lbuf, "enctypes=")) != NULL) {
+		enctypes = malloc(lbuflen);
+		if (!enctypes)
+			goto out;
+		if (sscanf(p, "enctypes=%s", enctypes) != 1) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				    "failed to parse target name "
+				    "in upcall string '%s'\n", lbuf);
+			goto out;
+		}
+		if (parse_enctypes(enctypes) != 0) {
+			printerr(0, "WARNING: handle_gssd_upcall: "
+				"parsing encryption types failed: errno %d\n", errno);
+		}
+	}
+
 	/* read target name */
 	if ((p = strstr(lbuf, "target=")) != NULL) {
 		target = malloc(lbuflen);
@@ -1222,6 +1301,7 @@ handle_gssd_upcall(struct clnt_info *clp)
 out:
 	free(lbuf);
 	free(mech);
+	free(enctypes);
 	free(target);
 	free(service);
 	return;	
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index 1295f57..dccbeb6 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -292,61 +292,6 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d)
 	return err;
 }
 
-
-#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
-/*
- * this routine obtains a credentials handle via gss_acquire_cred()
- * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
- * types negotiated.
- *
- * XXX Should call some function to determine the enctypes supported
- * by the kernel. (Only need to do that once!)
- *
- * Returns:
- *	0 => all went well
- *     -1 => there was an error
- */
-
-int
-limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
-{
-	u_int maj_stat, min_stat;
-	gss_cred_id_t credh;
-	gss_OID_set_desc  desired_mechs;
-	krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
-				    ENCTYPE_DES_CBC_MD5,
-				    ENCTYPE_DES_CBC_MD4 };
-	int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
-
-	/* We only care about getting a krb5 cred */
-	desired_mechs.count = 1;
-	desired_mechs.elements = &krb5oid;
-
-	maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
-				    &desired_mechs, GSS_C_INITIATE,
-				    &credh, NULL, NULL);
-
-	if (maj_stat != GSS_S_COMPLETE) {
-		if (get_verbosity() > 0)
-			pgsserr("gss_acquire_cred",
-				maj_stat, min_stat, &krb5oid);
-		return -1;
-	}
-
-	maj_stat = gss_set_allowable_enctypes(&min_stat, credh, &krb5oid,
-					     num_enctypes, &enctypes);
-	if (maj_stat != GSS_S_COMPLETE) {
-		pgsserr("gss_set_allowable_enctypes",
-			maj_stat, min_stat, &krb5oid);
-		gss_release_cred(&min_stat, &credh);
-		return -1;
-	}
-	sec->cred = credh;
-
-	return 0;
-}
-#endif	/* HAVE_SET_ALLOWABLE_ENCTYPES */
-
 /*
  * Obtain credentials via a key in the keytab given
  * a keytab handle and a gssd_k5_kt_princ structure.
@@ -1304,3 +1249,68 @@ gssd_k5_get_default_realm(char **def_realm)
 
 	krb5_free_context(context);
 }
+
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+/*
+ * this routine obtains a credentials handle via gss_acquire_cred()
+ * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
+ * types negotiated.
+ *
+ * XXX Should call some function to determine the enctypes supported
+ * by the kernel. (Only need to do that once!)
+ *
+ * Returns:
+ *	0 => all went well
+ *     -1 => there was an error
+ */
+
+int
+limit_krb5_enctypes(struct rpc_gss_sec *sec, uid_t uid)
+{
+	u_int maj_stat, min_stat;
+	gss_cred_id_t credh;
+	gss_OID_set_desc  desired_mechs;
+	krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
+				    ENCTYPE_DES_CBC_MD5,
+				    ENCTYPE_DES_CBC_MD4 };
+	int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
+	extern int num_krb5_enctypes;
+	extern krb5_enctype *krb5_enctypes;
+
+	/* We only care about getting a krb5 cred */
+	desired_mechs.count = 1;
+	desired_mechs.elements = &krb5oid;
+
+	maj_stat = gss_acquire_cred(&min_stat, NULL, 0,
+				    &desired_mechs, GSS_C_INITIATE,
+				    &credh, NULL, NULL);
+
+	if (maj_stat != GSS_S_COMPLETE) {
+		if (get_verbosity() > 0)
+			pgsserr("gss_acquire_cred",
+				maj_stat, min_stat, &krb5oid);
+		return -1;
+	}
+
+	/*
+	 * If we failed for any reason to produce global
+	 * list of supported enctypes, use local default here.
+	 */
+	if (krb5_enctypes == NULL)
+		maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+					&krb5oid, num_enctypes, enctypes);
+	else
+		maj_stat = gss_set_allowable_enctypes(&min_stat, credh,
+					&krb5oid, num_krb5_enctypes, krb5_enctypes);
+
+	if (maj_stat != GSS_S_COMPLETE) {
+		pgsserr("gss_set_allowable_enctypes",
+			maj_stat, min_stat, &krb5oid);
+		gss_release_cred(&min_stat, &credh);
+		return -1;
+	}
+	sec->cred = credh;
+
+	return 0;
+}
+#endif	/* HAVE_SET_ALLOWABLE_ENCTYPES */
diff --git a/utils/mount/network.c b/utils/mount/network.c
index 8dc183a..ffb18ab 100644
--- a/utils/mount/network.c
+++ b/utils/mount/network.c
@@ -857,7 +857,14 @@ int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen,
 		return 0;
 	}
 
-	client->cl_auth = authunix_create_default();
+	client->cl_auth = nfs_authsys_create();
+	if (client->cl_auth == NULL) {
+		if (verbose)
+			nfs_error(_("%s: Failed to create RPC auth handle"),
+				progname);
+		CLNT_DESTROY(client);
+		return 0;
+	}
 
 	res = CLNT_CALL(client, MOUNTPROC_UMNT,
 			(xdrproc_t)xdr_dirpath, (caddr_t)argp,
@@ -957,8 +964,10 @@ CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock)
 	}
 	if (clnt) {
 		/* try to mount hostname:dirname */
-		clnt->cl_auth = authunix_create_default();
-		return clnt;
+		clnt->cl_auth = nfs_authsys_create();
+		if (clnt->cl_auth)
+			return clnt;
+		CLNT_DESTROY(clnt);
 	}
 	return NULL;
 }
@@ -1203,6 +1212,8 @@ nfs_nfs_program(struct mount_options *options, unsigned long *program)
 			return 1;
 		}
 	case PO_BAD_VALUE:
+		nfs_error(_("%s: invalid value for 'nfsprog=' option"),
+				progname);
 		return 0;
 	}
 
@@ -1242,9 +1253,12 @@ nfs_nfs_version(struct mount_options *options, unsigned long *version)
 			}
 			return 0;
 		case PO_NOT_FOUND:
-			nfs_error(_("%s: option parsing error\n"),
+			nfs_error(_("%s: parsing error on 'vers=' option\n"),
 					progname);
+			return 0;
 		case PO_BAD_VALUE:
+			nfs_error(_("%s: invalid value for 'vers=' option"),
+					progname);
 			return 0;
 		}
 	case 4: /* nfsvers */
@@ -1256,9 +1270,12 @@ nfs_nfs_version(struct mount_options *options, unsigned long *version)
 			}
 			return 0;
 		case PO_NOT_FOUND:
-			nfs_error(_("%s: option parsing error\n"),
+			nfs_error(_("%s: parsing error on 'nfsvers=' option\n"),
 					progname);
+			return 0;
 		case PO_BAD_VALUE:
+			nfs_error(_("%s: invalid value for 'nfsvers=' option"),
+					progname);
 			return 0;
 		}
 	}
@@ -1294,6 +1311,8 @@ nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
 		if (option != NULL) {
 			if (!nfs_get_proto(option, &family, protocol)) {
 				errno = EPROTONOSUPPORT;
+				nfs_error(_("%s: Failed to find '%s' protocol"), 
+					progname, option);
 				return 0;
 			}
 			return 1;
@@ -1327,6 +1346,8 @@ nfs_nfs_port(struct mount_options *options, unsigned long *port)
 			return 1;
 		}
 	case PO_BAD_VALUE:
+		nfs_error(_("%s: invalid value for 'port=' option"),
+				progname);
 		return 0;
 	}
 
@@ -1380,8 +1401,13 @@ int nfs_nfs_proto_family(struct mount_options *options,
 	case 2: /* proto */
 		option = po_get(options, "proto");
 		if (option != NULL &&
-		    !nfs_get_proto(option, &tmp_family, &protocol))
-			goto out_err;
+		    !nfs_get_proto(option, &tmp_family, &protocol)) {
+
+			nfs_error(_("%s: Failed to find '%s' protocol"), 
+				progname, option);
+			errno = EPROTONOSUPPORT;
+			return 0;
+		}
 	}
 
 	if (!nfs_verify_family(tmp_family))
@@ -1414,6 +1440,8 @@ nfs_mount_program(struct mount_options *options, unsigned long *program)
 			return 1;
 		}
 	case PO_BAD_VALUE:
+		nfs_error(_("%s: invalid value for 'mountprog=' option"),
+				progname);
 		return 0;
 	}
 
@@ -1443,6 +1471,8 @@ nfs_mount_version(struct mount_options *options, unsigned long *version)
 			return 1;
 		}
 	case PO_BAD_VALUE:
+		nfs_error(_("%s: invalid value for 'mountvers=' option"),
+				progname);
 		return 0;
 	}
 
@@ -1469,6 +1499,8 @@ nfs_mount_protocol(struct mount_options *options, unsigned long *protocol)
 	if (option != NULL) {
 		if (!nfs_get_proto(option, &family, protocol)) {
 			errno = EPROTONOSUPPORT;
+			nfs_error(_("%s: Failed to find '%s' protocol"), 
+				progname, option);
 			return 0;
 		}
 		return 1;
@@ -1501,6 +1533,8 @@ nfs_mount_port(struct mount_options *options, unsigned long *port)
 			return 1;
 		}
 	case PO_BAD_VALUE:
+		nfs_error(_("%s: invalid value for 'mountport=' option"),
+				progname);
 		return 0;
 	}
 
@@ -1526,8 +1560,12 @@ int nfs_mount_proto_family(struct mount_options *options,
 
 	option = po_get(options, "mountproto");
 	if (option != NULL) {
-		if (!nfs_get_proto(option, &tmp_family, &protocol))
+		if (!nfs_get_proto(option, &tmp_family, &protocol)) {
+			nfs_error(_("%s: Failed to find '%s' protocol"), 
+				progname, option);
+			errno = EPROTONOSUPPORT;
 			goto out_err;
+		}
 		if (!nfs_verify_family(tmp_family))
 			goto out_err;
 		*family = tmp_family;
diff --git a/utils/mount/nfsumount.c b/utils/mount/nfsumount.c
index 9d798a2..1514340 100644
--- a/utils/mount/nfsumount.c
+++ b/utils/mount/nfsumount.c
@@ -179,10 +179,8 @@ static int nfs_umount_do_umnt(struct mount_options *options,
 	struct pmap nfs_pmap, mnt_pmap;
 	sa_family_t family;
 
-	if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap)) {
-		nfs_error(_("%s: bad mount options"), progname);
+	if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap))
 		return EX_FAIL;
-	}
 
 	/* Skip UMNT call for vers=4 mounts */
 	if (nfs_pmap.pm_vers == 4)
diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c
index 9b8c38f..0241400 100644
--- a/utils/mount/stropts.c
+++ b/utils/mount/stropts.c
@@ -538,7 +538,10 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options)
 
 	if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap,
 					mnt_saddr, &mnt_pmap)) {
-		errno = EINVAL;
+		if (rpc_createerr.cf_stat == RPC_UNKNOWNPROTO)
+			errno = EPROTONOSUPPORT;
+		else
+			errno = EINVAL;
 		return 0;
 	}
 
@@ -586,18 +589,21 @@ static int nfs_do_mount_v3v2(struct nfsmount_info *mi,
 		errno = ENOMEM;
 		return result;
 	}
-
+	errno = 0;
 	if (!nfs_append_addr_option(sap, salen, options)) {
-		errno = EINVAL;
+		if (errno == 0)
+			errno = EINVAL;
 		goto out_fail;
 	}
 
 	if (!nfs_fix_mounthost_option(options, mi->hostname)) {
-		errno = EINVAL;
+		if (errno == 0)
+			errno = EINVAL;
 		goto out_fail;
 	}
 	if (!mi->fake && !nfs_verify_lock_option(options)) {
-		errno = EINVAL;
+		if (errno == 0)
+			errno = EINVAL;
 		goto out_fail;
 	}
 
@@ -799,6 +805,7 @@ static int nfs_is_permanent_error(int error)
 	case ESTALE:
 	case ETIMEDOUT:
 	case ECONNREFUSED:
+	case EHOSTUNREACH:
 		return 0;	/* temporary */
 	default:
 		return 1;	/* permanent */
diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c
index 13eba70..04487e5 100644
--- a/utils/mountd/auth.c
+++ b/utils/mountd/auth.c
@@ -110,13 +110,15 @@ auth_reload()
 	return counter;
 }
 
-static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp, enum auth_error *error)
+static char *
+get_client_hostname(struct sockaddr_in *caller, struct addrinfo *ai,
+		enum auth_error *error)
 {
 	char *n;
 
 	if (use_ipaddr)
 		return strdup(inet_ntoa(caller->sin_addr));
-	n = client_compose(hp);
+	n = client_compose(ai);
 	*error = unknown_host;
 	if (!n)
 		return NULL;
@@ -128,8 +130,8 @@ static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp,
 
 /* return static nfs_export with details filled in */
 static nfs_export *
-auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
-			   char *path, struct hostent *hp,
+auth_authenticate_newcache(struct sockaddr_in *caller,
+			   char *path, struct addrinfo *ai,
 			   enum auth_error *error)
 {
 	nfs_export *exp;
@@ -137,12 +139,12 @@ auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
 
 	free(my_client.m_hostname);
 
-	my_client.m_hostname = get_client_hostname(caller, hp, error);
+	my_client.m_hostname = get_client_hostname(caller, ai, error);
 	if (my_client.m_hostname == NULL)
 		return NULL;
 
 	my_client.m_naddr = 1;
-	my_client.m_addrlist[0] = caller->sin_addr;
+	set_addrlist_in(&my_client, 0, caller);
 	my_exp.m_client = &my_client;
 
 	exp = NULL;
@@ -152,7 +154,7 @@ auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
 				continue;
 			if (!use_ipaddr && !client_member(my_client.m_hostname, exp->m_client->m_hostname))
 				continue;
-			if (use_ipaddr && !client_check(exp->m_client, hp))
+			if (use_ipaddr && !client_check(exp->m_client, ai))
 				continue;
 			break;
 		}
@@ -166,18 +168,19 @@ auth_authenticate_newcache(char *what, struct sockaddr_in *caller,
 }
 
 static nfs_export *
-auth_authenticate_internal(char *what, struct sockaddr_in *caller,
-			   char *path, struct hostent *hp,
+auth_authenticate_internal(struct sockaddr_in *caller,
+			   char *path, struct addrinfo *ai,
 			   enum auth_error *error)
 {
 	nfs_export *exp;
 
 	if (new_cache) {
-		exp = auth_authenticate_newcache(what, caller, path, hp, error);
+		exp = auth_authenticate_newcache(caller, path, ai, error);
 		if (!exp)
 			return NULL;
 	} else {
-		if (!(exp = export_find(hp, path))) {
+		exp = export_find(ai, path);
+		if (exp == NULL) {
 			*error = no_entry;
 			return NULL;
 		}
@@ -202,7 +205,7 @@ auth_authenticate(char *what, struct sockaddr_in *caller, char *path)
 	nfs_export	*exp = NULL;
 	char		epath[MAXPATHLEN+1];
 	char		*p = NULL;
-	struct hostent	*hp = NULL;
+	struct addrinfo *ai = NULL;
 	struct in_addr	addr = caller->sin_addr;
 	enum auth_error	error = bad_path;
 
@@ -216,14 +219,14 @@ auth_authenticate(char *what, struct sockaddr_in *caller, char *path)
 	epath[sizeof (epath) - 1] = '\0';
 	auth_fixpath(epath); /* strip duplicate '/' etc */
 
-	hp = client_resolve(caller->sin_addr);
-	if (!hp)
+	ai = client_resolve((struct sockaddr *)caller);
+	if (ai == NULL)
 		return exp;
 
 	/* Try the longest matching exported pathname. */
 	while (1) {
-		exp = auth_authenticate_internal(what, caller, epath,
-						 hp, &error);
+		exp = auth_authenticate_internal(caller, epath,
+						 ai, &error);
 		if (exp || (error != not_exported && error != no_entry))
 			break;
 		/* We have to treat the root, "/", specially. */
@@ -246,31 +249,30 @@ auth_authenticate(char *what, struct sockaddr_in *caller, char *path)
 
 	case no_entry:
 		xlog(L_WARNING, "refused %s request from %s for %s (%s): no export entry",
-		     what, hp->h_name, path, epath);
+		     what, ai->ai_canonname, path, epath);
 		break;
 
 	case not_exported:
 		xlog(L_WARNING, "refused %s request from %s for %s (%s): not exported",
-		     what, hp->h_name, path, epath);
+		     what, ai->ai_canonname, path, epath);
 		break;
 
 	case illegal_port:
 		xlog(L_WARNING, "refused %s request from %s for %s (%s): illegal port %d",
-		     what, hp->h_name, path, epath, ntohs(caller->sin_port));
+		     what, ai->ai_canonname, path, epath, ntohs(caller->sin_port));
 		break;
 
 	case success:
 		xlog(L_NOTICE, "authenticated %s request from %s:%d for %s (%s)",
-		     what, hp->h_name, ntohs(caller->sin_port), path, epath);
+		     what, ai->ai_canonname, ntohs(caller->sin_port), path, epath);
 		break;
 	default:
 		xlog(L_NOTICE, "%s request from %s:%d for %s (%s) gave %d",
-		     what, hp->h_name, ntohs(caller->sin_port), path, epath, error);
+		     what, ai->ai_canonname, ntohs(caller->sin_port),
+			path, epath, error);
 	}
 
-	if (hp)
-		free (hp);
-
+	freeaddrinfo(ai);
 	return exp;
 }
 
diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c
index d63e10a..9e1b164 100644
--- a/utils/mountd/cache.c
+++ b/utils/mountd/cache.c
@@ -77,8 +77,8 @@ void auth_unix_ip(FILE *f)
 	char class[20];
 	char ipaddr[20];
 	char *client = NULL;
-	struct in_addr addr;
-	struct hostent *he = NULL;
+	struct addrinfo *tmp = NULL;
+	struct addrinfo *ai = NULL;
 	if (readline(fileno(f), &lbuf, &lbuflen) != 1)
 		return;
 
@@ -93,17 +93,20 @@ void auth_unix_ip(FILE *f)
 	if (qword_get(&cp, ipaddr, 20) <= 0)
 		return;
 
-	if (inet_aton(ipaddr, &addr)==0)
+	tmp = host_pton(ipaddr);
+	if (tmp == NULL)
 		return;
 
 	auth_reload();
 
 	/* addr is a valid, interesting address, find the domain name... */
 	if (!use_ipaddr) {
-		he = client_resolve(addr);
-		client = client_compose(he);
+		ai = client_resolve(tmp->ai_addr);
+		client = client_compose(ai);
+		freeaddrinfo(ai);
 	}
-	
+	freeaddrinfo(tmp);
+
 	qword_print(f, "nfsd");
 	qword_print(f, ipaddr);
 	qword_printint(f, time(0)+30*60);
@@ -114,8 +117,7 @@ void auth_unix_ip(FILE *f)
 	qword_eol(f);
 	xlog(D_CALL, "auth_unix_ip: client %p '%s'", client, client?client: "DEFAULT");
 
-	if (client) free(client);
-	free(he);
+	free(client);
 }
 
 void auth_unix_gid(FILE *f)
@@ -125,7 +127,7 @@ void auth_unix_gid(FILE *f)
 	 * reply is
 	 *  uid expiry count list of group ids
 	 */
-	int uid;
+	uid_t uid;
 	struct passwd *pw;
 	gid_t glist[100], *groups = glist;
 	int ngroups = 100;
@@ -136,7 +138,7 @@ void auth_unix_gid(FILE *f)
 		return;
 
 	cp = lbuf;
-	if (qword_get_int(&cp, &uid) != 0)
+	if (qword_get_uint(&cp, &uid) != 0)
 		return;
 
 	pw = getpwuid(uid);
@@ -153,14 +155,14 @@ void auth_unix_gid(FILE *f)
 						  groups, &ngroups);
 		}
 	}
-	qword_printint(f, uid);
-	qword_printint(f, time(0)+30*60);
+	qword_printuint(f, uid);
+	qword_printuint(f, time(0)+30*60);
 	if (rv >= 0) {
-		qword_printint(f, ngroups);
+		qword_printuint(f, ngroups);
 		for (i=0; i<ngroups; i++)
-			qword_printint(f, groups[i]);
+			qword_printuint(f, groups[i]);
 	} else
-		qword_printint(f, 0);
+		qword_printuint(f, 0);
 	qword_eol(f);
 
 	if (groups != glist)
@@ -170,13 +172,16 @@ void auth_unix_gid(FILE *f)
 #if USE_BLKID
 static const char *get_uuid_blkdev(char *path)
 {
+	/* We set *safe if we know that we need the
+	 * fsid from statfs too.
+	 */
 	static blkid_cache cache = NULL;
 	struct stat stb;
 	char *devname;
 	blkid_tag_iterate iter;
 	blkid_dev dev;
 	const char *type;
-	const char *val = NULL;
+	const char *val, *uuid = NULL;
 
 	if (cache == NULL)
 		blkid_get_cache(&cache, NULL);
@@ -193,42 +198,29 @@ static const char *get_uuid_blkdev(char *path)
 	iter = blkid_tag_iterate_begin(dev);
 	if (!iter)
 		return NULL;
-	while (blkid_tag_next(iter, &type, &val) == 0)
+	while (blkid_tag_next(iter, &type, &val) == 0) {
 		if (strcmp(type, "UUID") == 0)
+			uuid = val;
+		if (strcmp(type, "TYPE") == 0 &&
+		    strcmp(val, "btrfs") == 0) {
+			uuid = NULL;
 			break;
+		}
+	}
 	blkid_tag_iterate_end(iter);
-	return val;
+	return uuid;
 }
 #else
 #define get_uuid_blkdev(path) (NULL)
 #endif
 
-int get_uuid(char *path, char *uuid, int uuidlen, char *u)
+int get_uuid(const char *val, int uuidlen, char *u)
 {
 	/* extract hex digits from uuidstr and compose a uuid
 	 * of the given length (max 16), xoring bytes to make
-	 * a smaller uuid.  Then compare with uuid
+	 * a smaller uuid.
 	 */
 	int i = 0;
-	const char *val = NULL;
-	char fsid_val[17];
-
-	if (path) {
-		val = get_uuid_blkdev(path);
-		if (!val) {
-			struct statfs64 st;
-
-			if (statfs64(path, &st))
-				return 0;
-			if (!st.f_fsid.__val[0] && !st.f_fsid.__val[1])
-				return 0;
-			snprintf(fsid_val, 17, "%08x%08x",
-				 st.f_fsid.__val[0], st.f_fsid.__val[1]);
-			val = fsid_val;
-		}
-	} else {
-		val = uuid;
-	}
 	
 	memset(u, 0, uuidlen);
 	for ( ; *val ; val++) {
@@ -252,6 +244,60 @@ int get_uuid(char *path, char *uuid, int uuidlen, char *u)
 	return 1;
 }
 
+int uuid_by_path(char *path, int type, int uuidlen, char *uuid)
+{
+	/* get a uuid for the filesystem found at 'path'.
+	 * There are several possible ways of generating the
+	 * uuids (types).
+	 * Type 0 is used for new filehandles, while other types
+	 * may be used to interpret old filehandle - to ensure smooth
+	 * forward migration.
+	 * We return 1 if a uuid was found (and it might be worth 
+	 * trying the next type) or 0 if no more uuid types can be
+	 * extracted.
+	 */
+
+	/* Possible sources of uuid are
+	 * - blkid uuid
+	 * - statfs64 uuid
+	 *
+	 * On some filesystems (e.g. vfat) the statfs64 uuid is simply an
+	 * encoding of the device that the filesystem is mounted from, so
+	 * it we be very bad to use that (as device numbers change).  blkid
+	 * must be preferred.
+	 * On other filesystems (e.g. btrfs) the statfs64 uuid contains
+	 * important info that the blkid uuid cannot contain:  This happens
+	 * when multiple subvolumes are exported (they have the same
+	 * blkid uuid but different statfs64 uuids).
+	 * We rely on get_uuid_blkdev *knowing* which is which and not returning
+	 * a uuid for filesystems where the statfs64 uuid is better.
+	 *
+	 */
+	struct statfs64 st;
+	char fsid_val[17];
+	const char *blkid_val;
+	const char *val;
+
+	blkid_val = get_uuid_blkdev(path);
+
+	if (statfs64(path, &st) == 0 &&
+	    (st.f_fsid.__val[0] || st.f_fsid.__val[1]))
+		snprintf(fsid_val, 17, "%08x%08x",
+			 st.f_fsid.__val[0], st.f_fsid.__val[1]);
+	else
+		fsid_val[0] = 0;
+
+	if (blkid_val && (type--) == 0)
+		val = blkid_val;
+	else if (fsid_val[0] && (type--) == 0)
+		val = fsid_val;
+	else
+		return 0;
+
+	get_uuid(val, uuidlen, uuid);
+	return 1;
+}
+
 /* Iterate through /etc/mtab, finding mountpoints
  * at or below a given path
  */
@@ -294,8 +340,7 @@ void nfsd_fh(FILE *f)
 	unsigned int fsidnum=0;
 	char fsid[32];
 	struct exportent *found = NULL;
-	struct hostent *he = NULL;
-	struct in_addr addr;
+	struct addrinfo *ai = NULL;
 	char *found_path = NULL;
 	nfs_export *exp;
 	int i;
@@ -398,6 +443,7 @@ void nfsd_fh(FILE *f)
 			struct stat stb;
 			char u[16];
 			char *path;
+			int type;
 
 			if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
 				static nfs_export *prev = NULL;
@@ -461,22 +507,29 @@ void nfsd_fh(FILE *f)
 					continue;
 			check_uuid:
 				if (exp->m_export.e_uuid)
-					get_uuid(NULL, exp->m_export.e_uuid,
+					get_uuid(exp->m_export.e_uuid,
 						 uuidlen, u);
-				else if (get_uuid(path, NULL, uuidlen, u) == 0)
-					continue;
+				else
+					for (type = 0;
+					     uuid_by_path(path, type, uuidlen, u);
+					     type++)
+						if (memcmp(u, fhuuid, uuidlen) != 0)
+							break;
 
 				if (memcmp(u, fhuuid, uuidlen) != 0)
 					continue;
 				break;
 			}
 			if (use_ipaddr) {
-				if (he == NULL) {
-					if (!inet_aton(dom, &addr))
+				if (ai == NULL) {
+					struct addrinfo *tmp;
+					tmp = host_pton(dom);
+					if (tmp == NULL)
 						goto out;
-					he = client_resolve(addr);
+					ai = client_resolve(tmp->ai_addr);
+					freeaddrinfo(tmp);
 				}
-				if (!client_check(exp->m_client, he))
+				if (!client_check(exp->m_client, ai))
 					continue;
 			}
 			/* It's a match !! */
@@ -534,8 +587,7 @@ void nfsd_fh(FILE *f)
  out:
 	if (found_path)
 		free(found_path);
-	if (he)
-		free(he);
+	freeaddrinfo(ai);
 	free(dom);
 	xlog(D_CALL, "nfsd_fh: found %p path %s", found, found ? found->e_path : NULL);
 	return;		
@@ -600,13 +652,13 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex
 		write_secinfo(f, exp, flag_mask);
  		if (exp->e_uuid == NULL || different_fs) {
  			char u[16];
- 			if (get_uuid(path, NULL, 16, u)) {
+ 			if (uuid_by_path(path, 0, 16, u)) {
  				qword_print(f, "uuid");
  				qword_printhex(f, u, 16);
  			}
  		} else {
  			char u[16];
- 			get_uuid(NULL, exp->e_uuid, 16, u);
+ 			get_uuid(exp->e_uuid, 16, u);
  			qword_print(f, "uuid");
  			qword_printhex(f, u, 16);
  		}
@@ -614,12 +666,12 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex
 	return qword_eol(f);
 }
 
-static int is_subdirectory(char *subpath, char *path)
+static int is_subdirectory(char *child, char *parent)
 {
-	int l = strlen(path);
+	int l = strlen(parent);
 
-	return strcmp(subpath, path) == 0
-		|| (strncmp(subpath, path, l) == 0 && path[l] == '/');
+	return strcmp(child, parent) == 0
+		|| (strncmp(child, parent, l) == 0 && child[l] == '/');
 }
 
 static int path_matches(nfs_export *exp, char *path)
@@ -629,19 +681,22 @@ static int path_matches(nfs_export *exp, char *path)
 	return strcmp(path, exp->m_export.e_path) == 0;
 }
 
-static int client_matches(nfs_export *exp, char *dom, struct hostent *he)
+static int
+client_matches(nfs_export *exp, char *dom, struct addrinfo *ai)
 {
 	if (use_ipaddr)
-		return client_check(exp->m_client, he);
+		return client_check(exp->m_client, ai);
 	return client_member(dom, exp->m_client->m_hostname);
 }
 
-static int export_matches(nfs_export *exp, char *dom, char *path, struct hostent *he)
+static int
+export_matches(nfs_export *exp, char *dom, char *path, struct addrinfo *ai)
 {
-	return path_matches(exp, path) && client_matches(exp, dom, he);
+	return path_matches(exp, path) && client_matches(exp, dom, ai);
 }
 
-static nfs_export *lookup_export(char *dom, char *path, struct hostent *he)
+static nfs_export *
+lookup_export(char *dom, char *path, struct addrinfo *ai)
 {
 	nfs_export *exp;
 	nfs_export *found = NULL;
@@ -650,7 +705,7 @@ static nfs_export *lookup_export(char *dom, char *path, struct hostent *he)
 
 	for (i=0 ; i < MCL_MAXTYPES; i++) {
 		for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
-			if (!export_matches(exp, dom, path, he))
+			if (!export_matches(exp, dom, path, ai))
 				continue;
 			if (!found) {
 				found = exp;
@@ -698,9 +753,7 @@ void nfsd_export(FILE *f)
 	char *cp;
 	char *dom, *path;
 	nfs_export *found = NULL;
-	struct in_addr addr;
-	struct hostent *he = NULL;
-
+	struct addrinfo *ai = NULL;
 
 	if (readline(fileno(f), &lbuf, &lbuflen) != 1)
 		return;
@@ -722,12 +775,16 @@ void nfsd_export(FILE *f)
 	auth_reload();
 
 	if (use_ipaddr) {
-		if (!inet_aton(dom, &addr))
+		struct addrinfo *tmp;
+		tmp = host_pton(dom);
+		if (tmp == NULL)
+			goto out;
+		ai = client_resolve(tmp->ai_addr);
+		freeaddrinfo(tmp);
 			goto out;
-		he = client_resolve(addr);
 	}
 
-	found = lookup_export(dom, path, he);
+	found = lookup_export(dom, path, ai);
 
 	if (found) {
 		if (dump_to_cache(f, dom, path, &found->m_export) < 0) {
@@ -743,7 +800,7 @@ void nfsd_export(FILE *f)
 	xlog(D_CALL, "nfsd_export: found %p path %s", found, path ? path : NULL);
 	if (dom) free(dom);
 	if (path) free(path);
-	if (he) free(he);
+	freeaddrinfo(ai);
 }
 
 
@@ -863,6 +920,7 @@ int cache_export_ent(char *domain, struct exportent *exp, char *path)
 
 int cache_export(nfs_export *exp, char *path)
 {
+	char buf[INET_ADDRSTRLEN];
 	int err;
 	FILE *f;
 
@@ -870,8 +928,10 @@ int cache_export(nfs_export *exp, char *path)
 	if (!f)
 		return -1;
 
+
 	qword_print(f, "nfsd");
-	qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
+	qword_print(f, 
+		host_ntop(get_addrlist(exp->m_client, 0), buf, sizeof(buf)));
 	qword_printint(f, time(0)+30*60);
 	qword_print(f, exp->m_client->m_hostname);
 	err = qword_eol(f);
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index a0a1f2d..6571454 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -80,10 +80,10 @@ static int nfs_version = -1;
 static void
 unregister_services (void)
 {
-	if (nfs_version & 0x1)
+	if (nfs_version & (0x1 << 1)) {
 		pmap_unset (MOUNTPROG, MOUNTVERS);
-	if (nfs_version & (0x1 << 1))
 		pmap_unset (MOUNTPROG, MOUNTVERS_POSIX);
+	}
 	if (nfs_version & (0x1 << 2))
 		pmap_unset (MOUNTPROG, MOUNTVERS_NFSV3);
 }
@@ -536,22 +536,21 @@ static void free_exportlist(exports *elist)
 
 static void prune_clients(nfs_export *exp, struct exportnode *e)
 {
-	struct hostent 	*hp;
+	struct addrinfo *ai = NULL;
 	struct groupnode *c, **cp;
 
 	cp = &e->ex_groups;
 	while ((c = *cp) != NULL) {
 		if (client_gettype(c->gr_name) == MCL_FQDN
-				&& (hp = gethostbyname(c->gr_name))) {
-			hp = hostent_dup(hp);
-			if (client_check(exp->m_client, hp)) {
+		    && (ai = host_addrinfo(c->gr_name))) {
+			if (client_check(exp->m_client, ai)) {
 				*cp = c->gr_next;
 				xfree(c->gr_name);
 				xfree(c);
-				xfree (hp);
+				freeaddrinfo(ai);
 				continue;
 			}
-			xfree (hp);
+			freeaddrinfo(ai);
 		}
 		cp = &(c->gr_next);
 	}
@@ -712,8 +711,10 @@ main(int argc, char **argv)
 			usage(argv [0], 1);
 		}
 
-	/* No more arguments allowed. */
-	if (optind != argc || !(nfs_version & 0x7))
+	/* No more arguments allowed.
+	 * Require at least one valid version (2, 3, or 4)
+	 */
+	if (optind != argc || !(nfs_version & 0xE))
 		usage(argv [0], 1);
 
 	if (chdir(state_dir)) {
@@ -761,12 +762,12 @@ main(int argc, char **argv)
 	if (new_cache)
 		cache_open();
 
-	if (nfs_version & 0x1)
+	if (nfs_version & (0x1 << 1)) {
 		rpc_init("mountd", MOUNTPROG, MOUNTVERS,
 			 mount_dispatch, port);
-	if (nfs_version & (0x1 << 1))
 		rpc_init("mountd", MOUNTPROG, MOUNTVERS_POSIX,
 			 mount_dispatch, port);
+	}
 	if (nfs_version & (0x1 << 2))
 		rpc_init("mountd", MOUNTPROG, MOUNTVERS_NFSV3,
 			 mount_dispatch, port);
diff --git a/utils/mountd/rmtab.c b/utils/mountd/rmtab.c
index 19b22ee..ba0fcf6 100644
--- a/utils/mountd/rmtab.c
+++ b/utils/mountd/rmtab.c
@@ -133,8 +133,7 @@ mountlist_del(char *hname, const char *path)
 void
 mountlist_del_all(struct sockaddr_in *sin)
 {
-	struct in_addr	addr = sin->sin_addr;
-	struct hostent	*hp;
+	char		*hostname;
 	struct rmtabent	*rep;
 	nfs_export	*exp;
 	FILE		*fp;
@@ -142,11 +141,13 @@ mountlist_del_all(struct sockaddr_in *sin)
 
 	if ((lockid = xflock(_PATH_RMTABLCK, "w")) < 0)
 		return;
-	if (!(hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) {
-		xlog(L_ERROR, "can't get hostname of %s", inet_ntoa(addr));
+	hostname = host_canonname((struct sockaddr *)sin);
+	if (hostname == NULL) {
+		char buf[INET_ADDRSTRLEN];
+		xlog(L_ERROR, "can't get hostname of %s",
+			host_ntop((struct sockaddr *)sin, buf, sizeof(buf)));
 		goto out_unlock;
 	}
-	hp = hostent_dup (hp);
 
 	if (!setrmtabent("r"))
 		goto out_free;
@@ -155,7 +156,7 @@ mountlist_del_all(struct sockaddr_in *sin)
 		goto out_close;
 
 	while ((rep = getrmtabent(1, NULL)) != NULL) {
-		if (strcmp(rep->r_client, hp->h_name) == 0 &&
+		if (strcmp(rep->r_client, hostname) == 0 &&
 		    (exp = auth_authenticate("umountall", sin, rep->r_path)))
 			continue;
 		fputrmtabent(fp, rep, NULL);
@@ -168,7 +169,7 @@ mountlist_del_all(struct sockaddr_in *sin)
 out_close:
 	endrmtabent();	/* close & unlink */
 out_free:
-	free (hp);
+	free(hostname);
 out_unlock:
 	xfunlock(lockid);
 }
diff --git a/utils/showmount/showmount.c b/utils/showmount/showmount.c
index f567093..394f528 100644
--- a/utils/showmount/showmount.c
+++ b/utils/showmount/showmount.c
@@ -194,7 +194,13 @@ int main(int argc, char **argv)
 	}
 
 	mclient = nfs_get_mount_client(hostname, mount_vers_tbl[vers]);
-	mclient->cl_auth = authunix_create_default();
+	mclient->cl_auth = nfs_authsys_create();
+	if (mclient->cl_auth == NULL) {
+		fprintf(stderr, "%s: unable to create RPC auth handle.\n",
+				program_name);
+		clnt_destroy(mclient);
+		exit(1);
+	}
 	total_timeout.tv_sec = TOTAL_TIMEOUT;
 	total_timeout.tv_usec = 0;
 
diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c
index 3259a3e..437e37a 100644
--- a/utils/statd/sm-notify.c
+++ b/utils/statd/sm-notify.c
@@ -54,7 +54,7 @@ struct nsm_host {
 	uint32_t		xid;
 };
 
-static char		nsm_hostname[256];
+static char		nsm_hostname[SM_MAXSTRLEN + 1];
 static int		nsm_state;
 static int		nsm_family = AF_INET;
 static int		opt_debug = 0;
@@ -412,12 +412,33 @@ usage:		fprintf(stderr,
 		}
 	}
 
-	if (opt_srcaddr) {
-		strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
-	} else
-	if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) {
-		xlog(L_ERROR, "Failed to obtain name of local host: %m");
-		exit(1);
+	if (opt_srcaddr != NULL) {
+		struct addrinfo *ai = NULL;
+		struct addrinfo hint = {
+			.ai_family	= AF_UNSPEC,
+			.ai_flags	= AI_NUMERICHOST,
+		};
+
+		if (getaddrinfo(opt_srcaddr, NULL, &hint, &ai))
+			/* not a presentation address - use it */
+			strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname));
+		else {
+			/* was a presentation address - look it up in
+			 * /etc/hosts, so it can be used for my_name */
+			int error;
+
+			freeaddrinfo(ai);
+			hint.ai_flags = AI_CANONNAME;
+			error = getaddrinfo(opt_srcaddr, NULL, &hint, &ai);
+			if (error != 0) {
+				xlog(L_ERROR, "Bind address %s is unusable: %s",
+						opt_srcaddr, gai_strerror(error));
+				exit(1);
+			}
+			strncpy(nsm_hostname, ai->ai_canonname,
+							sizeof(nsm_hostname));
+			freeaddrinfo(ai);
+		}
 	}
 
 	(void)nsm_retire_monitored_hosts();
@@ -535,6 +556,8 @@ notify(const int sock)
 static int
 notify_host(int sock, struct nsm_host *host)
 {
+	const char *my_name = (opt_srcaddr != NULL ?
+					nsm_hostname : host->my_name);
 	struct sockaddr *sap;
 	socklen_t salen;
 
@@ -580,8 +603,8 @@ notify_host(int sock, struct nsm_host *host)
 		host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS);
 	else
 		host->xid = nsm_xmit_notify(sock, sap, salen,
-				SM_PROG, nsm_hostname, nsm_state);
-	
+					SM_PROG, my_name, nsm_state);
+
 	return 0;
 }
 
@@ -611,15 +634,28 @@ recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr)
 }
 
 /*
- * Successful NOTIFY call. Server returns void, so nothing
- * we need to do here.
+ * Successful NOTIFY call. Server returns void.
+ *
+ * Try sending another SM_NOTIFY with an unqualified "my_name"
+ * argument.  Reuse the port number.  If "my_name" is already
+ * unqualified, we're done.
  */
 static void
 recv_notify_reply(struct nsm_host *host)
 {
-	xlog(D_GENERAL, "Host %s notified successfully", host->name);
+	char *dot = strchr(host->my_name, '.');
 
-	smn_forget_host(host);
+	if (dot != NULL) {
+		*dot = '\0';
+		host->send_next = time(NULL);
+		host->xid = 0;
+		if (host->timeout >= NSM_MAX_TIMEOUT / 4)
+			host->timeout = NSM_MAX_TIMEOUT / 4;
+		insert_host(host);
+	} else {
+		xlog(D_GENERAL, "Host %s notified successfully", host->name);
+		smn_forget_host(host);
+	}
 }
 
 /*
diff --git a/utils/statd/sm-notify.man b/utils/statd/sm-notify.man
index 163713e..7a1cbfa 100644
--- a/utils/statd/sm-notify.man
+++ b/utils/statd/sm-notify.man
@@ -97,11 +97,9 @@ It uses the
 string as the destination.
 To identify which host has rebooted, the
 .B sm-notify
-command normally sends the results of
-.BR gethostname (3)
-as the
+command normally sends
 .I my_name
-string.
+string recorded when that remote was monitored.
 The remote
 .B rpc.statd
 matches incoming SM_NOTIFY requests using this string,
@@ -202,15 +200,22 @@ argument to use when sending SM_NOTIFY requests.
 If this option is not specified,
 .B sm-notify
 uses a wildcard address as the transport bind address,
-and uses the results of
-.BR gethostname (3)
-as the
+and uses the
+.I my_name
+recorded when the remote was monitored as the
 .I mon_name
-argument.
+argument when sending SM_NOTIFY requests.
 .IP
 The
 .I ipaddr
 form can be expressed as either an IPv4 or an IPv6 presentation address.
+If the
+.I ipaddr
+form is used, the
+.B sm-notify
+command converts this address to a hostname for use as the
+.I mon_name
+argument when sending SM_NOTIFY requests.
 .IP
 This option can be useful in multi-homed configurations where
 the remote requires notification from a specific network address.
@@ -252,13 +257,6 @@ consistent
 The hostname the client uses to mount the server should match the server's
 .I mon_name
 in SM_NOTIFY requests it sends
-.IP
-The use of network addresses as a
-.I mon_name
-or a
-.I my_name
-string should be avoided when
-interoperating with non-Linux NFS implementations.
 .PP
 Unmounting an NFS file system does not necessarily stop
 either the NFS client or server from monitoring each other.
diff --git a/utils/statd/statd.man b/utils/statd/statd.man
index ffc5e95..ca00e24 100644
--- a/utils/statd/statd.man
+++ b/utils/statd/statd.man
@@ -100,11 +100,9 @@ It uses the
 string as the destination.
 To identify which host has rebooted, the
 .B sm-notify
-command normally sends the results of
-.BR gethostname (3)
-as the
+command sends the
 .I my_name
-string.
+string recorded when that remote was monitored.
 The remote
 .B rpc.statd
 matches incoming SM_NOTIFY requests using this string,
@@ -292,7 +290,6 @@ man pages.
 .SH ADDITIONAL NOTES
 Lock recovery after a reboot is critical to maintaining data integrity
 and preventing unnecessary application hangs.
-.PP
 To help
 .B rpc.statd
 match SM_NOTIFY requests to NLM requests, a number of best practices
@@ -309,13 +306,6 @@ consistent
 The hostname the client uses to mount the server should match the server's
 .I mon_name
 in SM_NOTIFY requests it sends
-.IP
-The use of network addresses as a
-.I mon_name
-or a
-.I my_name
-string should be avoided when
-interoperating with non-Linux NFS implementations.
 .PP
 Unmounting an NFS file system does not necessarily stop
 either the NFS client or server from monitoring each other.