diff --git a/nfs-utils-1.2.1-compile.patch b/nfs-utils-1.2.1-compile.patch deleted file mode 100644 index 0af5b06..0000000 --- a/nfs-utils-1.2.1-compile.patch +++ /dev/null @@ -1,33 +0,0 @@ -diff -up nfs-utils-1.2.1/utils/exportfs/exportfs.c.orig nfs-utils-1.2.1/utils/exportfs/exportfs.c ---- nfs-utils-1.2.1/utils/exportfs/exportfs.c.orig 2009-11-04 06:13:56.000000000 -0500 -+++ nfs-utils-1.2.1/utils/exportfs/exportfs.c 2010-01-12 07:59:03.730815650 -0500 -@@ -13,6 +13,7 @@ - #endif - - #include -+#include - #include - #include - #include -diff -up nfs-utils-1.2.1/utils/mount/mount.c.orig nfs-utils-1.2.1/utils/mount/mount.c ---- nfs-utils-1.2.1/utils/mount/mount.c.orig 2010-01-12 07:58:57.697003286 -0500 -+++ nfs-utils-1.2.1/utils/mount/mount.c 2010-01-12 08:00:45.274357659 -0500 -@@ -24,6 +24,7 @@ - - #include - #include -+#include - #include - #include - #include -diff -up nfs-utils-1.2.1/utils/mount/network.c.orig nfs-utils-1.2.1/utils/mount/network.c ---- nfs-utils-1.2.1/utils/mount/network.c.orig 2010-01-12 07:58:57.698003139 -0500 -+++ nfs-utils-1.2.1/utils/mount/network.c 2010-01-12 07:59:44.041815690 -0500 -@@ -37,6 +37,7 @@ - #include - #include - #include -+#include - #include - #include - #include diff --git a/nfs-utils-1.2.1-mount-config.patch b/nfs-utils-1.2.1-mount-config.patch deleted file mode 100644 index cb533fa..0000000 --- a/nfs-utils-1.2.1-mount-config.patch +++ /dev/null @@ -1,53 +0,0 @@ -commit d63f9e0ccb836d592593a9816ccc00a51c903328 -Author: Steve Dickson -Date: Wed Jan 20 15:05:46 2010 -0500 - - mount.nfs: Configuration file parser ignoring options - - When the protocol version is set on the command line, - none of the variables set in the configuration file - are passed down to the kernel due to a bug in the - parsing routine. - - Tested-by: Jeff Layton - Signed-off-by: Steve Dickson - -diff --git a/utils/mount/configfile.c b/utils/mount/configfile.c -index 28b722c..1dd4159 100644 ---- a/utils/mount/configfile.c -+++ b/utils/mount/configfile.c -@@ -194,13 +194,29 @@ void free_all(void) - static char *versions[] = {"v2", "v3", "v4", "vers", "nfsvers", NULL}; - int inline check_vers(char *mopt, char *field) - { -- int i; -+ int i, found=0; - -- if (strncmp("mountvers", field, strlen("mountvers")) != 0) { -- for (i=0; versions[i]; i++) -- if (strcasestr(mopt, versions[i]) != NULL) -- return 1; -+ /* -+ * First check to see if the config setting is one -+ * of the many version settings -+ */ -+ for (i=0; versions[i]; i++) { -+ if (strcasestr(field, versions[i]) != NULL) { -+ found++; -+ break; -+ } - } -+ if (!found) -+ return 0; -+ /* -+ * It appears the version is being set, now see -+ * if the version appears on the command -+ */ -+ for (i=0; versions[i]; i++) { -+ if (strcasestr(mopt, versions[i]) != NULL) -+ return 1; -+ } -+ - return 0; - } - diff --git a/nfs-utils-1.2.2-rc8.patch b/nfs-utils-1.2.2-rc8.patch deleted file mode 100644 index c2d6d0e..0000000 --- a/nfs-utils-1.2.2-rc8.patch +++ /dev/null @@ -1,12162 +0,0 @@ -diff --git a/.gitignore b/.gitignore -index 632609e..4bff9e3 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -55,10 +55,15 @@ support/export/mount.h - support/export/mount_clnt.c - support/export/mount_xdr.c - support/include/mount.h --utils/statd/sm_inter.h --utils/statd/sm_inter_clnt.c --utils/statd/sm_inter_svc.c --utils/statd/sm_inter_xdr.c -+support/nsm/sm_inter.h -+support/nsm/sm_inter_clnt.c -+support/nsm/sm_inter_svc.c -+support/nsm/sm_inter_xdr.c -+support/include/sm_inter.h -+tests/nsm_client/nlm_sm_inter.h -+tests/nsm_client/nlm_sm_inter_clnt.c -+tests/nsm_client/nlm_sm_inter_svc.c -+tests/nsm_client/nlm_sm_inter_xdr.c - # cscope database files - cscope.* - # generic editor backup et al -diff --git a/Makefile.am b/Makefile.am -index b3a6e91..ae7cd16 100644 ---- a/Makefile.am -+++ b/Makefile.am -@@ -2,7 +2,7 @@ - - AUTOMAKE_OPTIONS = foreign - --SUBDIRS = tools support utils linux-nfs -+SUBDIRS = tools support utils linux-nfs tests - - MAINTAINERCLEANFILES = Makefile.in - -diff --git a/aclocal/ipv6.m4 b/aclocal/ipv6.m4 -index 2490f3d..5ee8fb6 100644 ---- a/aclocal/ipv6.m4 -+++ b/aclocal/ipv6.m4 -@@ -15,8 +15,8 @@ AC_DEFUN([AC_IPV6], [ - fi - - dnl IPv6-enabled networking functions required for IPv6 -- AC_CHECK_FUNCS([getnameinfo bindresvport_sa], , -- [AC_MSG_ERROR([Missing functions needed for IPv6.])]) -+ AC_CHECK_FUNCS([getifaddrs getnameinfo bindresvport_sa], , -+ [AC_MSG_ERROR([Missing library functions needed for IPv6.])]) - - dnl Need to detect presence of IPv6 networking at run time via - dnl getaddrinfo(3); old versions of glibc do not support ADDRCONFIG -diff --git a/aclocal/libcap.m4 b/aclocal/libcap.m4 -new file mode 100644 -index 0000000..eabe507 ---- /dev/null -+++ b/aclocal/libcap.m4 -@@ -0,0 +1,15 @@ -+dnl Checks for libcap.so -+dnl -+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_CHECK_HEADERS([sys/capability.h], , -+ [AC_MSG_ERROR([libcap headers not found.])]) -+ -+])dnl -diff --git a/configure.ac b/configure.ac -index 3ad415c..1dc4249 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -166,6 +166,9 @@ fi - dnl Check for TI-RPC library and headers - AC_LIBTIRPC - -+dnl Check for -lcap -+AC_LIBCAP -+ - # Check whether user wants TCP wrappers support - AC_TCP_WRAPPERS - -@@ -327,7 +330,7 @@ AC_FUNC_STAT - AC_FUNC_VPRINTF - AC_CHECK_FUNCS([alarm atexit dup2 fdatasync ftruncate getcwd \ - gethostbyaddr gethostbyname gethostname getmntent \ -- getnameinfo getrpcbyname \ -+ getnameinfo getrpcbyname getifaddrs \ - gettimeofday hasmntopt inet_ntoa innetgr memset mkdir pathconf \ - realpath rmdir select socket strcasecmp strchr strdup \ - strerror strrchr strtol strtoul sigprocmask]) -@@ -402,6 +405,7 @@ AC_CONFIG_FILES([ - support/include/Makefile - support/misc/Makefile - support/nfs/Makefile -+ support/nsm/Makefile - tools/Makefile - tools/locktest/Makefile - tools/nlmtest/Makefile -@@ -416,6 +420,8 @@ AC_CONFIG_FILES([ - utils/nfsd/Makefile - utils/nfsstat/Makefile - utils/showmount/Makefile -- utils/statd/Makefile]) -+ utils/statd/Makefile -+ tests/Makefile -+ tests/nsm_client/Makefile]) - AC_OUTPUT - -diff --git a/support/Makefile.am b/support/Makefile.am -index aa4d692..cb37733 100644 ---- a/support/Makefile.am -+++ b/support/Makefile.am -@@ -1,6 +1,6 @@ - ## Process this file with automake to produce Makefile.in - --SUBDIRS = export include misc nfs -+SUBDIRS = export include misc nfs nsm - - MAINTAINERCLEANFILES = Makefile.in - -diff --git a/support/export/client.c b/support/export/client.c -index 5fcf355..6236561 100644 ---- a/support/export/client.c -+++ b/support/export/client.c -@@ -297,7 +297,7 @@ name_cmp(char *a, char *b) - /* compare strings a and b, but only upto ',' in a */ - while (*a && *b && *a != ',' && *a == *b) - a++, b++; -- if (!*b && (!*a || !a == ',') ) -+ if (!*b && (!*a || *a == ',')) - return 0; - if (!*b) return 1; - if (!*a || *a == ',') return -1; -diff --git a/support/export/export.c b/support/export/export.c -index e5e6cb0..2943466 100644 ---- a/support/export/export.c -+++ b/support/export/export.c -@@ -28,6 +28,22 @@ static int export_check(nfs_export *, struct hostent *, char *); - static nfs_export * - export_allowed_internal(struct hostent *hp, char *path); - -+static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep) -+{ -+ if (exp->m_export.e_flags != eep->e_flags) { -+ xlog(L_ERROR, "incompatible duplicated export entries:"); -+ xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname, -+ eep->e_path, eep->e_flags); -+ xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname, -+ exp->m_export.e_path, exp->m_export.e_flags); -+ } else { -+ xlog(L_ERROR, "duplicated export entries:"); -+ xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path); -+ xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname, -+ exp->m_export.e_path); -+ } -+} -+ - int - export_read(char *fname) - { -@@ -36,27 +52,13 @@ export_read(char *fname) - - setexportent(fname, "r"); - while ((eep = getexportent(0,1)) != NULL) { -- exp = export_lookup(eep->e_hostname, eep->e_path, 0); -- if (!exp) -- export_create(eep,0); -- else { -- if (exp->m_export.e_flags != eep->e_flags) { -- xlog(L_ERROR, "incompatible duplicated export entries:"); -- xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname, -- eep->e_path, eep->e_flags); -- xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname, -- exp->m_export.e_path, exp->m_export.e_flags); -- } -- else { -- xlog(L_ERROR, "duplicated export entries:"); -- xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path); -- xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname, -- exp->m_export.e_path); -- } -- } -+ exp = export_lookup(eep->e_hostname, eep->e_path, 0); -+ if (!exp) -+ export_create(eep, 0); -+ else -+ warn_duplicated_exports(exp, eep); - } - endexportent(); -- - return 0; - } - -diff --git a/support/export/xtab.c b/support/export/xtab.c -index 3b1dcce..2a43193 100644 ---- a/support/export/xtab.c -+++ b/support/export/xtab.c -@@ -19,7 +19,9 @@ - #include "exportfs.h" - #include "xio.h" - #include "xlog.h" -+#include "v4root.h" - -+int v4root_needed; - static void cond_rename(char *newfile, char *oldfile); - - static int -@@ -36,6 +38,8 @@ xtab_read(char *xtab, char *lockfn, int is_export) - if ((lockid = xflock(lockfn, "r")) < 0) - return 0; - setexportent(xtab, "r"); -+ if (is_export == 1) -+ v4root_needed = 1; - while ((xp = getexportent(is_export==0, 0)) != NULL) { - if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) && - !(exp = export_create(xp, is_export!=1))) { -@@ -48,6 +52,8 @@ xtab_read(char *xtab, char *lockfn, int is_export) - case 1: - exp->m_xtabent = 1; - exp->m_mayexport = 1; -+ if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0) -+ v4root_needed = 0; - break; - case 2: - exp->m_exported = -1;/* may be exported */ -diff --git a/support/include/Makefile.am b/support/include/Makefile.am -index f5a77ec..4b33ee9 100644 ---- a/support/include/Makefile.am -+++ b/support/include/Makefile.am -@@ -9,6 +9,8 @@ noinst_HEADERS = \ - nfs_mntent.h \ - nfs_paths.h \ - nfslib.h \ -+ nfsrpc.h \ -+ nsm.h \ - rpcmisc.h \ - tcpwrapper.h \ - xio.h \ -diff --git a/support/include/exportfs.h b/support/include/exportfs.h -index a5cf482..470b2ec 100644 ---- a/support/include/exportfs.h -+++ b/support/include/exportfs.h -@@ -99,10 +99,19 @@ int xtab_mount_write(void); - int xtab_export_write(void); - void xtab_append(nfs_export *); - -+int secinfo_addflavor(struct flav_info *, struct exportent *); -+ - int rmtab_read(void); - - struct nfskey * key_lookup(char *hname); - -+struct export_features { -+ unsigned int flags; -+ unsigned int secinfo_flags; -+}; -+ -+struct export_features *get_export_features(void); -+ - /* Record export error. */ - extern int export_errno; - -diff --git a/support/include/ha-callout.h b/support/include/ha-callout.h -index efb70fb..1164336 100644 ---- a/support/include/ha-callout.h -+++ b/support/include/ha-callout.h -@@ -53,11 +53,7 @@ ha_callout(char *event, char *arg1, char *arg2, int arg3) - default: pid = waitpid(pid, &ret, 0); - } - sigaction(SIGCHLD, &oldact, &newact); --#ifdef dprintf -- dprintf(N_DEBUG, "ha callout returned %d\n", WEXITSTATUS(ret)); --#else - xlog(D_GENERAL, "ha callout returned %d\n", WEXITSTATUS(ret)); --#endif - } - - #endif -diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h -index f7a99ba..1547a87 100644 ---- a/support/include/nfs/export.h -+++ b/support/include/nfs/export.h -@@ -24,6 +24,17 @@ - #define NFSEXP_FSID 0x2000 - #define NFSEXP_CROSSMOUNT 0x4000 - #define NFSEXP_NOACL 0x8000 /* reserved for possible ACL related use */ --#define NFSEXP_ALLFLAGS 0xFFFF -+#define NFSEXP_V4ROOT 0x10000 -+/* -+ * All flags supported by the kernel before addition of the -+ * export_features interface: -+ */ -+#define NFSEXP_OLDFLAGS 0x7E3F -+/* -+ * Flags that can vary per flavor, for kernels before addition of the -+ * export_features interface: -+ */ -+#define NFSEXP_OLD_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \ -+ | NFSEXP_ALLSQUASH) - - #endif /* _NSF_EXPORT_H */ -diff --git a/support/include/nfsrpc.h b/support/include/nfsrpc.h -index dff6af7..4db35ab 100644 ---- a/support/include/nfsrpc.h -+++ b/support/include/nfsrpc.h -@@ -59,16 +59,6 @@ static inline void nfs_clear_rpc_createerr(void) - } - - /* -- * Extract port value from a socket address -- */ --extern uint16_t nfs_get_port(const struct sockaddr *); -- --/* -- * Set port value in a socket address -- */ --extern void nfs_set_port(struct sockaddr *, const uint16_t); -- --/* - * Look up an RPC program name in /etc/rpc - */ - extern rpcprog_t nfs_getrpcbyname(const rpcprog_t, const char *table[]); -@@ -90,6 +80,18 @@ extern CLIENT *nfs_get_priv_rpcclient( const struct sockaddr *, - struct timeval *); - - /* -+ * Convert a netid to a protocol number and protocol family -+ */ -+extern int nfs_get_proto(const char *netid, sa_family_t *family, -+ unsigned long *protocol); -+ -+/* -+ * Convert a protocol family and protocol name to a netid -+ */ -+extern char *nfs_get_netid(const sa_family_t family, -+ const unsigned long protocol); -+ -+/* - * Convert a socket address to a universal address - */ - extern char *nfs_sockaddr2universal(const struct sockaddr *); -@@ -158,4 +160,4 @@ extern int nfs_rpc_ping(const struct sockaddr *sap, - const unsigned short protocol, - const struct timeval *timeout); - --#endif /* __NFS_UTILS_NFSRPC_H */ -+#endif /* !__NFS_UTILS_NFSRPC_H */ -diff --git a/support/include/nsm.h b/support/include/nsm.h -new file mode 100644 -index 0000000..fb4d823 ---- /dev/null -+++ b/support/include/nsm.h -@@ -0,0 +1,93 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+/* -+ * NSM for Linux. -+ */ -+ -+#ifndef NFS_UTILS_SUPPORT_NSM_H -+#define NFS_UTILS_SUPPORT_NSM_H -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "sm_inter.h" -+ -+typedef unsigned int -+ (*nsm_populate_t)(const char *hostname, -+ const struct sockaddr *sap, -+ const struct mon *mon, -+ const time_t timestamp); -+ -+/* file.c */ -+ -+extern _Bool nsm_setup_pathnames(const char *progname, -+ const char *parentdir); -+extern _Bool nsm_is_default_parentdir(void); -+extern _Bool nsm_drop_privileges(const int pidfd); -+ -+extern int nsm_get_state(_Bool update); -+extern void nsm_update_kernel_state(const int state); -+ -+extern unsigned int -+ nsm_retire_monitored_hosts(void); -+extern unsigned int -+ nsm_load_monitor_list(nsm_populate_t func); -+extern unsigned int -+ nsm_load_notify_list(nsm_populate_t func); -+ -+extern _Bool nsm_insert_monitored_host(const char *hostname, -+ const struct sockaddr *sap, const struct mon *m); -+extern void nsm_delete_monitored_host(const char *hostname, -+ const char *mon_name, const char *my_name); -+extern void nsm_delete_notified_host(const char *hostname, -+ const char *mon_name, const char *my_name); -+extern size_t nsm_priv_to_hex(const char *priv, char *buf, -+ const size_t buflen); -+ -+/* rpc.c */ -+ -+#define NSM_MAXMSGSIZE (2048u) -+ -+extern uint32_t nsm_xmit_getport(const int sock, -+ const struct sockaddr_in *sin, -+ const unsigned long program, -+ const unsigned long version); -+extern uint32_t nsm_xmit_getaddr(const int sock, -+ const struct sockaddr_in6 *sin6, -+ const rpcprog_t program, const rpcvers_t version); -+extern uint32_t nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap, -+ const rpcprog_t program, const rpcvers_t version); -+extern uint32_t nsm_xmit_notify(const int sock, const struct sockaddr *sap, -+ const socklen_t salen, const rpcprog_t program, -+ const char *mon_name, const int state); -+extern uint32_t nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap, -+ const socklen_t salen, const struct mon *m, -+ const int state); -+extern uint32_t nsm_parse_reply(XDR *xdrs); -+extern unsigned long -+ nsm_recv_getport(XDR *xdrs); -+extern uint16_t nsm_recv_getaddr(XDR *xdrs); -+extern uint16_t nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs); -+ -+#endif /* !NFS_UTILS_SUPPORT_NSM_H */ -diff --git a/support/include/rpcmisc.h b/support/include/rpcmisc.h -index f551a85..1b8f411 100644 ---- a/support/include/rpcmisc.h -+++ b/support/include/rpcmisc.h -@@ -41,7 +41,12 @@ struct rpc_dtable { - (xdrproc_t)xdr_##res_type, sizeof(res_type), \ - } - -- -+void nfs_svc_unregister(const rpcprog_t program, -+ const rpcvers_t version); -+unsigned int nfs_svc_create(char *name, const rpcprog_t program, -+ const rpcvers_t version, -+ void (*dispatch)(struct svc_req *, SVCXPRT *), -+ const uint16_t port); - void rpc_init(char *name, int prog, int vers, - void (*dispatch)(struct svc_req *, SVCXPRT *), - int defport); -diff --git a/support/include/sockaddr.h b/support/include/sockaddr.h -new file mode 100644 -index 0000000..732514b ---- /dev/null -+++ b/support/include/sockaddr.h -@@ -0,0 +1,237 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+#ifndef NFS_UTILS_SOCKADDR_H -+#define NFS_UTILS_SOCKADDR_H -+ -+#include -+#include -+#include -+ -+/* -+ * This type is for defining buffers that contain network socket -+ * addresses. -+ * -+ * Casting a "struct sockaddr *" to the address of a "struct -+ * sockaddr_storage" breaks C aliasing rules. The "union -+ * nfs_sockaddr" type follows C aliasing rules yet specifically -+ * allows converting pointers to it between "struct sockaddr *" -+ * and a few other network sockaddr-related pointer types. -+ * -+ * Note that this union is much smaller than a sockaddr_storage. -+ * It should be used only for AF_INET or AF_INET6 socket addresses. -+ * An AF_LOCAL sockaddr_un, for example, will clearly not fit into -+ * a buffer of this type. -+ */ -+union nfs_sockaddr { -+ struct sockaddr sa; -+ struct sockaddr_in s4; -+ struct sockaddr_in6 s6; -+}; -+ -+#if SIZEOF_SOCKLEN_T - 0 == 0 -+#define socklen_t unsigned int -+#endif -+ -+#define SIZEOF_SOCKADDR_UNKNOWN (socklen_t)0 -+#define SIZEOF_SOCKADDR_IN (socklen_t)sizeof(struct sockaddr_in) -+ -+#ifdef IPV6_SUPPORTED -+#define SIZEOF_SOCKADDR_IN6 (socklen_t)sizeof(struct sockaddr_in6) -+#else /* !IPV6_SUPPORTED */ -+#define SIZEOF_SOCKADDR_IN6 SIZEOF_SOCKADDR_UNKNOWN -+#endif /* !IPV6_SUPPORTED */ -+ -+/** -+ * nfs_sockaddr_length - return the size in bytes of a socket address -+ * @sap: pointer to socket address -+ * -+ * Returns the size in bytes of @sap, or zero if the family is -+ * not recognized. -+ */ -+static inline socklen_t -+nfs_sockaddr_length(const struct sockaddr *sap) -+{ -+ switch (sap->sa_family) { -+ case AF_INET: -+ return SIZEOF_SOCKADDR_IN; -+ case AF_INET6: -+ return SIZEOF_SOCKADDR_IN6; -+ } -+ return SIZEOF_SOCKADDR_UNKNOWN; -+} -+ -+static inline uint16_t -+get_port4(const struct sockaddr *sap) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ return ntohs(sin->sin_port); -+} -+ -+#ifdef IPV6_SUPPORTED -+static inline uint16_t -+get_port6(const struct sockaddr *sap) -+{ -+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; -+ return ntohs(sin6->sin6_port); -+} -+#else /* !IPV6_SUPPORTED */ -+static inline uint16_t -+get_port6(__attribute__ ((unused)) const struct sockaddr *sap) -+{ -+ return 0; -+} -+#endif /* !IPV6_SUPPORTED */ -+ -+/** -+ * nfs_get_port - extract port value from a socket address -+ * @sap: pointer to socket address -+ * -+ * Returns port value in host byte order, or zero if the -+ * socket address contains an unrecognized family. -+ */ -+static inline uint16_t -+nfs_get_port(const struct sockaddr *sap) -+{ -+ switch (sap->sa_family) { -+ case AF_INET: -+ return get_port4(sap); -+ case AF_INET6: -+ return get_port6(sap); -+ } -+ return 0; -+} -+ -+static inline void -+set_port4(struct sockaddr *sap, const uint16_t port) -+{ -+ struct sockaddr_in *sin = (struct sockaddr_in *)sap; -+ sin->sin_port = htons(port); -+} -+ -+#ifdef IPV6_SUPPORTED -+static inline void -+set_port6(struct sockaddr *sap, const uint16_t port) -+{ -+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; -+ sin6->sin6_port = htons(port); -+} -+#else /* !IPV6_SUPPORTED */ -+static inline void -+set_port6(__attribute__ ((unused)) struct sockaddr *sap, -+ __attribute__ ((unused)) const uint16_t port) -+{ -+} -+#endif /* !IPV6_SUPPORTED */ -+ -+/** -+ * nfs_set_port - set port value in a socket address -+ * @sap: pointer to socket address -+ * @port: port value to set -+ * -+ */ -+static inline void -+nfs_set_port(struct sockaddr *sap, const uint16_t port) -+{ -+ switch (sap->sa_family) { -+ case AF_INET: -+ set_port4(sap, port); -+ break; -+ case AF_INET6: -+ set_port6(sap, port); -+ break; -+ } -+} -+ -+/** -+ * nfs_is_v4_loopback - test to see if socket address is AF_INET loopback -+ * @sap: pointer to socket address -+ * -+ * Returns true if the socket address is the standard IPv4 loopback -+ * address; otherwise false is returned. -+ */ -+static inline _Bool -+nfs_is_v4_loopback(const struct sockaddr *sap) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ -+ if (sin->sin_family != AF_INET) -+ return false; -+ if (sin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) -+ return false; -+ return true; -+} -+ -+static inline _Bool -+compare_sockaddr4(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; -+} -+ -+#ifdef IPV6_SUPPORTED -+static inline _Bool -+compare_sockaddr6(const struct sockaddr *sa1, const struct sockaddr *sa2) -+{ -+ const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)sa1; -+ const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)sa2; -+ -+ if ((IN6_IS_ADDR_LINKLOCAL((char *)&sin1->sin6_addr) && -+ IN6_IS_ADDR_LINKLOCAL((char *)&sin2->sin6_addr)) || -+ (IN6_IS_ADDR_SITELOCAL((char *)&sin1->sin6_addr) && -+ IN6_IS_ADDR_SITELOCAL((char *)&sin2->sin6_addr))) -+ if (sin1->sin6_scope_id != sin2->sin6_scope_id) -+ return false; -+ -+ return IN6_ARE_ADDR_EQUAL((char *)&sin1->sin6_addr, -+ (char *)&sin2->sin6_addr); -+} -+#else /* !IPV6_SUPPORTED */ -+static inline _Bool -+compare_sockaddr6(__attribute__ ((unused)) const struct sockaddr *sa1, -+ __attribute__ ((unused)) const struct sockaddr *sa2) -+{ -+ return false; -+} -+#endif /* !IPV6_SUPPORTED */ -+ -+/** -+ * nfs_compare_sockaddr - compare two socket addresses for equality -+ * @sa1: pointer to a socket address -+ * @sa2: pointer to a socket address -+ * -+ * Returns true if the two socket addresses contain equivalent -+ * network addresses; otherwise false is returned. -+ */ -+static inline _Bool -+nfs_compare_sockaddr(const struct sockaddr *sa1, const struct sockaddr *sa2) -+{ -+ if (sa1->sa_family == sa2->sa_family) -+ switch (sa1->sa_family) { -+ case AF_INET: -+ return compare_sockaddr4(sa1, sa2); -+ case AF_INET6: -+ return compare_sockaddr6(sa1, sa2); -+ } -+ -+ return false; -+} -+ -+#endif /* !NFS_UTILS_SOCKADDR_H */ -diff --git a/support/include/tcpwrapper.h b/support/include/tcpwrapper.h -index 98cf806..f735106 100644 ---- a/support/include/tcpwrapper.h -+++ b/support/include/tcpwrapper.h -@@ -5,14 +5,8 @@ - #include - #include - --extern int verboselog; -- --extern int allow_severity; --extern int deny_severity; -- --extern int good_client(char *daemon, struct sockaddr_in *addr); --extern int from_local (struct sockaddr_in *addr); --extern int check_default(char *daemon, struct sockaddr_in *addr, -- u_long proc, u_long prog); -+extern int from_local(const struct sockaddr *sap); -+extern int check_default(char *name, struct sockaddr *sap, -+ const unsigned long program); - - #endif /* TCP_WRAPPER_H */ -diff --git a/support/include/v4root.h b/support/include/v4root.h -new file mode 100644 -index 0000000..706c15c ---- /dev/null -+++ b/support/include/v4root.h -@@ -0,0 +1,15 @@ -+/* -+ * Copyright (C) 2009 Red Hat -+ * support/include/v4root.h -+ * -+ * Support routines for dynamic pseudo roots. -+ * -+ */ -+ -+#ifndef V4ROOT_H -+#define V4ROOT_H -+ -+extern int v4root_needed; -+extern void v4root_set(void); -+ -+#endif /* V4ROOT_H */ -diff --git a/support/misc/from_local.c b/support/misc/from_local.c -index 89ccc4a..e2de969 100644 ---- a/support/misc/from_local.c -+++ b/support/misc/from_local.c -@@ -37,32 +37,100 @@ - static char sccsid[] = "@(#) from_local.c 1.3 96/05/31 15:52:57"; - #endif - --#ifdef TEST --#undef perror -+#ifdef HAVE_CONFIG_H -+#include - #endif - - #include - #include -+#include - #include - #include - #include - #include - #include - #include --#include - #include - #include - -+#include "sockaddr.h" -+#include "tcpwrapper.h" -+#include "xlog.h" -+ - #ifndef TRUE - #define TRUE 1 - #define FALSE 0 - #endif - -- /* -- * With virtual hosting, each hardware network interface can have multiple -- * network addresses. On such machines the number of machine addresses can -- * be surprisingly large. -- */ -+#ifdef HAVE_GETIFADDRS -+ -+#include -+#include -+ -+/** -+ * from_local - determine whether request comes from the local system -+ * @sap: pointer to socket address to check -+ * -+ * With virtual hosting, each hardware network interface can have -+ * multiple network addresses. On such machines the number of machine -+ * addresses can be surprisingly large. -+ * -+ * We also expect the local network configuration to change over time, -+ * so call getifaddrs(3) more than once, but not too often. -+ * -+ * Returns TRUE if the sockaddr contains an address of one of the local -+ * network interfaces. Otherwise FALSE is returned. -+ */ -+int -+from_local(const struct sockaddr *sap) -+{ -+ static struct ifaddrs *ifaddr = NULL; -+ static time_t last_update = 0; -+ struct ifaddrs *ifa; -+ unsigned int count; -+ time_t now; -+ -+ if (time(&now) == ((time_t)-1)) { -+ xlog(L_ERROR, "%s: time(2): %m", __func__); -+ -+ /* If we don't know what time it is, use the -+ * existing ifaddr list, if one exists */ -+ now = last_update; -+ if (ifaddr == NULL) -+ now++; -+ } -+ if (now != last_update) { -+ xlog(D_GENERAL, "%s: updating local if addr list", __func__); -+ -+ if (ifaddr) -+ freeifaddrs(ifaddr); -+ -+ if (getifaddrs(&ifaddr) == -1) { -+ xlog(L_ERROR, "%s: getifaddrs(3): %m", __func__); -+ return FALSE; -+ } -+ -+ last_update = now; -+ } -+ -+ count = 0; -+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { -+ if ((ifa->ifa_flags & IFF_UP) && -+ nfs_compare_sockaddr(sap, ifa->ifa_addr)) { -+ xlog(D_GENERAL, "%s: incoming address matches " -+ "local interface address", __func__); -+ return TRUE; -+ } else -+ count++; -+ } -+ -+ xlog(D_GENERAL, "%s: checked %u local if addrs; " -+ "incoming address not found", __func__, count); -+ return FALSE; -+} -+ -+#else /* !HAVE_GETIFADDRS */ -+ - static int num_local; - static int num_addrs; - static struct in_addr *addrs; -@@ -81,7 +149,7 @@ static int grow_addrs(void) - new_num = (addrs == 0) ? 1 : num_addrs + num_addrs; - new_addrs = (struct in_addr *) malloc(sizeof(*addrs) * new_num); - if (new_addrs == 0) { -- perror("portmap: out of memory"); -+ xlog_warn("%s: out of memory", __func__); - return (0); - } else { - if (addrs != 0) { -@@ -112,13 +180,13 @@ find_local(void) - */ - - if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { -- perror("socket"); -+ xlog_warn("%s: socket(2): %m", __func__); - return (0); - } - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { -- perror("SIOCGIFCONF"); -+ xlog_warn("%s: ioctl(SIOCGIFCONF): %m", __func__); - (void) close(sock); - return (0); - } -@@ -130,10 +198,10 @@ find_local(void) - if (ifr->ifr_addr.sa_family == AF_INET) { /* IP net interface */ - ifreq = *ifr; - if (ioctl(sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) { -- perror("SIOCGIFFLAGS"); -+ xlog_warn("%s: ioctl(SIOCGIFFLAGS): %m", __func__); - } else if (ifreq.ifr_flags & IFF_UP) { /* active interface */ - if (ioctl(sock, SIOCGIFADDR, (char *) &ifreq) < 0) { -- perror("SIOCGIFADDR"); -+ xlog_warn("%s: ioctl(SIOCGIFADDR): %m", __func__); - } else { - if (num_local >= num_addrs) - if (grow_addrs() == 0) -@@ -153,14 +221,28 @@ find_local(void) - return (num_local); - } - --/* from_local - determine whether request comes from the local system */ -+/** -+ * from_local - determine whether request comes from the local system -+ * @sap: pointer to socket address to check -+ * -+ * With virtual hosting, each hardware network interface can have -+ * multiple network addresses. On such machines the number of machine -+ * addresses can be surprisingly large. -+ * -+ * Returns TRUE if the sockaddr contains an address of one of the local -+ * network interfaces. Otherwise FALSE is returned. -+ */ - int --from_local(struct sockaddr_in *addr) -+from_local(const struct sockaddr *sap) - { -+ const struct sockaddr_in *addr = (const struct sockaddr_in *)sap; - int i; - -+ if (sap->sa_family != AF_INET) -+ return (FALSE); -+ - if (addrs == 0 && find_local() == 0) -- syslog(LOG_ERR, "cannot find any active local network interfaces"); -+ xlog(L_ERROR, "Cannot find any active local network interfaces"); - - for (i = 0; i < num_local; i++) { - if (memcmp((char *) &(addr->sin_addr), (char *) &(addrs[i]), -@@ -172,9 +254,8 @@ from_local(struct sockaddr_in *addr) - - #ifdef TEST - --main() -+int main(void) - { -- char *inet_ntoa(); - int i; - - find_local(); -@@ -182,4 +263,6 @@ main() - printf("%s\n", inet_ntoa(addrs[i])); - } - --#endif -+#endif /* TEST */ -+ -+#endif /* !HAVE_GETIFADDRS */ -diff --git a/support/misc/tcpwrapper.c b/support/misc/tcpwrapper.c -index 1da6020..06b0a46 100644 ---- a/support/misc/tcpwrapper.c -+++ b/support/misc/tcpwrapper.c -@@ -34,13 +34,12 @@ - #ifdef HAVE_CONFIG_H - #include - #endif -+ - #ifdef HAVE_LIBWRAP --#include - #include - #include - #include - #include --#include - #include - #include - #include -@@ -49,108 +48,146 @@ - #include - #include - -+#include "sockaddr.h" -+#include "tcpwrapper.h" - #include "xlog.h" - - #ifdef SYSV40 - #include - #include --#endif -+#endif /* SYSV40 */ - --static void logit(int severity, struct sockaddr_in *addr, -- u_long procnum, u_long prognum, char *text); --static int check_files(void); -+#define ALLOW 1 -+#define DENY 0 - --/* -- * These need to exist since they are externed -- * public header files. -- */ --int verboselog = 0; --int allow_severity = LOG_INFO; --int deny_severity = LOG_WARNING; -+#ifdef IPV6_SUPPORTED -+static void -+present_address(const struct sockaddr *sap, char *buf, const size_t buflen) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; -+ socklen_t len = (socklen_t)buflen; -+ -+ switch (sap->sa_family) { -+ case AF_INET: -+ if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0) -+ return; -+ case AF_INET6: -+ if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len) != 0) -+ return; -+ } - --#define log_bad_host(addr, proc, prog) \ -- logit(deny_severity, addr, proc, prog, "request from unauthorized host") -+ memset(buf, 0, buflen); -+ strncpy(buf, "unrecognized caller", buflen); -+} -+#else /* !IPV6_SUPPORTED */ -+static void -+present_address(const struct sockaddr *sap, char *buf, const size_t buflen) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ socklen_t len = (socklen_t)buflen; - --#define ALLOW 1 --#define DENY 0 -+ if (sap->sa_family == AF_INET) -+ if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0) -+ return; -+ -+ memset(buf, 0, buflen); -+ strncpy(buf, "unrecognized caller", (size_t)buflen); -+} -+#endif /* !IPV6_SUPPORTED */ - - typedef struct _haccess_t { -- TAILQ_ENTRY(_haccess_t) list; -- int access; -- struct in_addr addr; -+ TAILQ_ENTRY(_haccess_t) list; -+ int allowed; -+ union nfs_sockaddr address; - } haccess_t; - - #define HASH_TABLE_SIZE 1021 - typedef struct _hash_head { - TAILQ_HEAD(host_list, _haccess_t) h_head; - } hash_head; --hash_head haccess_tbl[HASH_TABLE_SIZE]; --static haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long); --static void haccess_add(struct sockaddr_in *addr, u_long, int); - --inline unsigned int strtoint(char *str) -+static hash_head haccess_tbl[HASH_TABLE_SIZE]; -+ -+static unsigned long -+strtoint(const char *str) - { -- unsigned int n = 0; -- int len = strlen(str); -- int i; -+ unsigned long i, n = 0; -+ size_t len = strlen(str); - -- for (i=0; i < len; i++) -- n+=((int)str[i])*i; -+ for (i = 0; i < len; i++) -+ n += (unsigned char)str[i] * i; - - return n; - } --static inline int hashint(unsigned int num) -+ -+static unsigned int -+hashint(const unsigned long num) - { -- return num % HASH_TABLE_SIZE; -+ return (unsigned int)(num % HASH_TABLE_SIZE); - } --#define HASH(_addr, _prog) \ -- hashint((strtoint((_addr))+(_prog))) - --void haccess_add(struct sockaddr_in *addr, u_long prog, int access) -+static unsigned int -+HASH(const char *addr, const unsigned long program) -+{ -+ return hashint(strtoint(addr) + program); -+} -+ -+static void -+haccess_add(const struct sockaddr *sap, const char *address, -+ const unsigned long program, const int allowed) - { - hash_head *head; -- haccess_t *hptr; -- int hash; -+ haccess_t *hptr; -+ unsigned int hash; - - hptr = (haccess_t *)malloc(sizeof(haccess_t)); - if (hptr == NULL) - return; - -- hash = HASH(inet_ntoa(addr->sin_addr), prog); -+ hash = HASH(address, program); - head = &(haccess_tbl[hash]); - -- hptr->access = access; -- hptr->addr.s_addr = addr->sin_addr.s_addr; -+ hptr->allowed = allowed; -+ memcpy(&hptr->address, sap, (size_t)nfs_sockaddr_length(sap)); - - if (TAILQ_EMPTY(&head->h_head)) - TAILQ_INSERT_HEAD(&head->h_head, hptr, list); - else - TAILQ_INSERT_TAIL(&head->h_head, hptr, list); - } --haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long prog) -+ -+static haccess_t * -+haccess_lookup(const struct sockaddr *sap, const char *address, -+ const unsigned long program) - { - hash_head *head; -- haccess_t *hptr; -- int hash; -+ haccess_t *hptr; -+ unsigned int hash; - -- hash = HASH(inet_ntoa(addr->sin_addr), prog); -+ hash = HASH(address, program); - head = &(haccess_tbl[hash]); - - TAILQ_FOREACH(hptr, &head->h_head, list) { -- if (hptr->addr.s_addr == addr->sin_addr.s_addr) -+ if (nfs_compare_sockaddr(&hptr->address.sa, sap)) - return hptr; - } - return NULL; - } - --int --good_client(daemon, addr) --char *daemon; --struct sockaddr_in *addr; -+static void -+logit(const char *address) -+{ -+ xlog_warn("connect from %s denied: request from unauthorized host", -+ address); -+} -+ -+static int -+good_client(char *name, struct sockaddr *sap) - { - struct request_info req; - -- request_init(&req, RQ_DAEMON, daemon, RQ_CLIENT_SIN, addr, 0); -+ request_init(&req, RQ_DAEMON, name, RQ_CLIENT_SIN, sap, 0); - sock_methods(&req); - - if (hosts_access(&req)) -@@ -159,9 +196,8 @@ struct sockaddr_in *addr; - return DENY; - } - --/* check_files - check to see if either access files have changed */ -- --static int check_files() -+static int -+check_files(void) - { - static time_t allow_mtime, deny_mtime; - struct stat astat, dstat; -@@ -186,45 +222,48 @@ static int check_files() - return changed; - } - --/* check_default - additional checks for NULL, DUMP, GETPORT and unknown */ -- -+/** -+ * check_default - additional checks for NULL, DUMP, GETPORT and unknown -+ * @name: pointer to '\0'-terminated ASCII string containing name of the -+ * daemon requesting the access check -+ * @sap: pointer to sockaddr containing network address of caller -+ * @program: RPC program number caller is attempting to access -+ * -+ * Returns TRUE if the caller is allowed access; otherwise FALSE is returned. -+ */ - int --check_default(daemon, addr, proc, prog) --char *daemon; --struct sockaddr_in *addr; --u_long proc; --u_long prog; -+check_default(char *name, struct sockaddr *sap, const unsigned long program) - { - haccess_t *acc = NULL; - int changed = check_files(); -+ char buf[INET6_ADDRSTRLEN]; -+ -+ present_address(sap, buf, sizeof(buf)); - -- acc = haccess_lookup(addr, prog); -- if (acc && changed == 0) -- return (acc->access); -+ acc = haccess_lookup(sap, buf, program); -+ if (acc != NULL && changed == 0) { -+ xlog(D_GENERAL, "%s: access by %s %s (cached)", __func__, -+ buf, acc->allowed ? "ALLOWED" : "DENIED"); -+ return acc->allowed; -+ } - -- if (!(from_local(addr) || good_client(daemon, addr))) { -- log_bad_host(addr, proc, prog); -- if (acc) -- acc->access = FALSE; -- else -- haccess_add(addr, prog, FALSE); -+ if (!(from_local(sap) || good_client(name, sap))) { -+ logit(buf); -+ if (acc != NULL) -+ acc->allowed = FALSE; -+ else -+ haccess_add(sap, buf, program, FALSE); -+ xlog(D_GENERAL, "%s: access by %s DENIED", __func__, buf); - return (FALSE); - } - -- if (acc) -- acc->access = TRUE; -- else -- haccess_add(addr, prog, TRUE); -+ if (acc != NULL) -+ acc->allowed = TRUE; -+ else -+ haccess_add(sap, buf, program, TRUE); -+ xlog(D_GENERAL, "%s: access by %s ALLOWED", __func__, buf); - -- return (TRUE); -+ return (TRUE); - } - --/* logit - report events of interest via the syslog daemon */ -- --static void logit(int severity, struct sockaddr_in *addr, -- u_long procnum, u_long prognum, char *text) --{ -- syslog(severity, "connect from %s denied: %s", -- inet_ntoa(addr->sin_addr), text); --} --#endif -+#endif /* HAVE_LIBWRAP */ -diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am -index e9462fc..60400b2 100644 ---- a/support/nfs/Makefile.am -+++ b/support/nfs/Makefile.am -@@ -4,7 +4,8 @@ noinst_LIBRARIES = libnfs.a - libnfs_a_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \ - xlog.c xcommon.c wildmat.c nfsclient.c \ - nfsexport.c getfh.c nfsctl.c rpc_socket.c getport.c \ -- svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c -+ svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c \ -+ svc_create.c - - MAINTAINERCLEANFILES = Makefile.in - -diff --git a/support/nfs/exports.c b/support/nfs/exports.c -index 1aaebf4..a93941c 100644 ---- a/support/nfs/exports.c -+++ b/support/nfs/exports.c -@@ -84,6 +84,31 @@ setexportent(char *fname, char *type) - first = 1; - } - -+static void init_exportent (struct exportent *ee, int fromkernel) -+{ -+ ee->e_flags = EXPORT_DEFAULT_FLAGS; -+ /* some kernels assume the default is sync rather than -+ * async. More recent kernels always report one or other, -+ * but this test makes sure we assume same as kernel -+ * Ditto for wgather -+ */ -+ if (fromkernel) { -+ ee->e_flags &= ~NFSEXP_ASYNC; -+ ee->e_flags &= ~NFSEXP_GATHERED_WRITES; -+ } -+ ee->e_anonuid = 65534; -+ ee->e_anongid = 65534; -+ ee->e_squids = NULL; -+ ee->e_sqgids = NULL; -+ ee->e_mountpoint = NULL; -+ ee->e_fslocmethod = FSLOC_NONE; -+ ee->e_fslocdata = NULL; -+ ee->e_secinfo[0].flav = NULL; -+ ee->e_nsquids = 0; -+ ee->e_nsqgids = 0; -+ ee->e_uuid = NULL; -+} -+ - struct exportent * - getexportent(int fromkernel, int fromexports) - { -@@ -102,26 +127,7 @@ getexportent(int fromkernel, int fromexports) - has_default_opts = 0; - has_default_subtree_opts = 0; - -- def_ee.e_flags = EXPORT_DEFAULT_FLAGS; -- /* some kernels assume the default is sync rather than -- * async. More recent kernels always report one or other, -- * but this test makes sure we assume same as kernel -- * Ditto for wgather -- */ -- if (fromkernel) { -- def_ee.e_flags &= ~NFSEXP_ASYNC; -- def_ee.e_flags &= ~NFSEXP_GATHERED_WRITES; -- } -- def_ee.e_anonuid = 65534; -- def_ee.e_anongid = 65534; -- def_ee.e_squids = NULL; -- def_ee.e_sqgids = NULL; -- def_ee.e_mountpoint = NULL; -- def_ee.e_fslocmethod = FSLOC_NONE; -- def_ee.e_fslocdata = NULL; -- def_ee.e_secinfo[0].flav = NULL; -- def_ee.e_nsquids = 0; -- def_ee.e_nsqgids = 0; -+ init_exportent(&def_ee, fromkernel); - - ok = getpath(def_ee.e_path, sizeof(def_ee.e_path)); - if (ok <= 0) -@@ -334,18 +340,7 @@ mkexportent(char *hname, char *path, char *options) - { - static struct exportent ee; - -- ee.e_flags = EXPORT_DEFAULT_FLAGS; -- ee.e_anonuid = 65534; -- ee.e_anongid = 65534; -- ee.e_squids = NULL; -- ee.e_sqgids = NULL; -- ee.e_mountpoint = NULL; -- ee.e_fslocmethod = FSLOC_NONE; -- ee.e_fslocdata = NULL; -- ee.e_secinfo[0].flav = NULL; -- ee.e_nsquids = 0; -- ee.e_nsqgids = 0; -- ee.e_uuid = NULL; -+ init_exportent(&ee, 0); - - xfree(ee.e_hostname); - ee.e_hostname = xstrdup(hname); -@@ -385,7 +380,7 @@ static int valid_uuid(char *uuid) - * do nothing if it's already there. Returns the index of flavor - * in the resulting array in any case. - */ --static int secinfo_addflavor(struct flav_info *flav, struct exportent *ep) -+int secinfo_addflavor(struct flav_info *flav, struct exportent *ep) - { - struct sec_entry *p; - -@@ -467,9 +462,20 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep) - } - } - --/* options that can vary per flavor: */ --#define NFSEXP_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \ -- | NFSEXP_ALLSQUASH) -+/* -+ * For those flags which are not allowed to vary by pseudoflavor, -+ * ensure that the export flags agree with the flags on each -+ * pseudoflavor: -+ */ -+static void fix_pseudoflavor_flags(struct exportent *ep) -+{ -+ struct export_features *ef; -+ struct sec_entry *p; -+ -+ ef = get_export_features(); -+ for (p = ep->e_secinfo; p->flav; p++) -+ p->flags |= ep->e_flags & ~ef->secinfo_flags; -+} - - /* - * Parse option string pointed to by cp and set mount options accordingly. -@@ -477,7 +483,6 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep) - static int - parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) - { -- struct sec_entry *p; - int had_subtree_opt = 0; - char *flname = efname?efname:"command line"; - int flline = efp?efp->x_line:0; -@@ -507,25 +512,25 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) - else if (strcmp(opt, "rw") == 0) - clearflags(NFSEXP_READONLY, active, ep); - else if (!strcmp(opt, "secure")) -- ep->e_flags &= ~NFSEXP_INSECURE_PORT; -+ clearflags(NFSEXP_INSECURE_PORT, active, ep); - else if (!strcmp(opt, "insecure")) -- ep->e_flags |= NFSEXP_INSECURE_PORT; -+ setflags(NFSEXP_INSECURE_PORT, active, ep); - else if (!strcmp(opt, "sync")) -- ep->e_flags &= ~NFSEXP_ASYNC; -+ clearflags(NFSEXP_ASYNC, active, ep); - else if (!strcmp(opt, "async")) -- ep->e_flags |= NFSEXP_ASYNC; -+ setflags(NFSEXP_ASYNC, active, ep); - else if (!strcmp(opt, "nohide")) -- ep->e_flags |= NFSEXP_NOHIDE; -+ setflags(NFSEXP_NOHIDE, active, ep); - else if (!strcmp(opt, "hide")) -- ep->e_flags &= ~NFSEXP_NOHIDE; -+ clearflags(NFSEXP_NOHIDE, active, ep); - else if (!strcmp(opt, "crossmnt")) -- ep->e_flags |= NFSEXP_CROSSMOUNT; -+ setflags(NFSEXP_CROSSMOUNT, active, ep); - else if (!strcmp(opt, "nocrossmnt")) -- ep->e_flags &= ~NFSEXP_CROSSMOUNT; -+ clearflags(NFSEXP_CROSSMOUNT, active, ep); - else if (!strcmp(opt, "wdelay")) -- ep->e_flags |= NFSEXP_GATHERED_WRITES; -+ setflags(NFSEXP_GATHERED_WRITES, active, ep); - else if (!strcmp(opt, "no_wdelay")) -- ep->e_flags &= ~NFSEXP_GATHERED_WRITES; -+ clearflags(NFSEXP_GATHERED_WRITES, active, ep); - else if (strcmp(opt, "root_squash") == 0) - setflags(NFSEXP_ROOTSQUASH, active, ep); - else if (!strcmp(opt, "no_root_squash")) -@@ -536,22 +541,22 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) - clearflags(NFSEXP_ALLSQUASH, active, ep); - else if (strcmp(opt, "subtree_check") == 0) { - had_subtree_opt = 1; -- ep->e_flags &= ~NFSEXP_NOSUBTREECHECK; -+ clearflags(NFSEXP_NOSUBTREECHECK, active, ep); - } else if (strcmp(opt, "no_subtree_check") == 0) { - had_subtree_opt = 1; -- ep->e_flags |= NFSEXP_NOSUBTREECHECK; -+ setflags(NFSEXP_NOSUBTREECHECK, active, ep); - } else if (strcmp(opt, "auth_nlm") == 0) -- ep->e_flags &= ~NFSEXP_NOAUTHNLM; -+ clearflags(NFSEXP_NOAUTHNLM, active, ep); - else if (strcmp(opt, "no_auth_nlm") == 0) -- ep->e_flags |= NFSEXP_NOAUTHNLM; -+ setflags(NFSEXP_NOAUTHNLM, active, ep); - else if (strcmp(opt, "secure_locks") == 0) -- ep->e_flags &= ~NFSEXP_NOAUTHNLM; -+ clearflags(NFSEXP_NOAUTHNLM, active, ep); - else if (strcmp(opt, "insecure_locks") == 0) -- ep->e_flags |= NFSEXP_NOAUTHNLM; -+ setflags(NFSEXP_NOAUTHNLM, active, ep); - else if (strcmp(opt, "acl") == 0) -- ep->e_flags &= ~NFSEXP_NOACL; -+ clearflags(NFSEXP_NOACL, active, ep); - else if (strcmp(opt, "no_acl") == 0) -- ep->e_flags |= NFSEXP_NOACL; -+ setflags(NFSEXP_NOACL, active, ep); - else if (strncmp(opt, "anonuid=", 8) == 0) { - char *oe; - ep->e_anonuid = strtol(opt+8, &oe, 10); -@@ -583,11 +588,11 @@ bad_option: - char *oe; - if (strcmp(opt+5, "root") == 0) { - ep->e_fsid = 0; -- ep->e_flags |= NFSEXP_FSID; -+ setflags(NFSEXP_FSID, active, ep); - } else { - ep->e_fsid = strtoul(opt+5, &oe, 0); - if (opt[5]!='\0' && *oe == '\0') -- ep->e_flags |= NFSEXP_FSID; -+ setflags(NFSEXP_FSID, active, ep); - else if (valid_uuid(opt+5)) - ep->e_uuid = strdup(opt+5); - else { -@@ -628,22 +633,15 @@ bad_option: - } else { - xlog(L_ERROR, "%s:%d: unknown keyword \"%s\"\n", - flname, flline, opt); -- ep->e_flags |= NFSEXP_ALLSQUASH | NFSEXP_READONLY; -+ setflags(NFSEXP_ALLSQUASH | NFSEXP_READONLY, active, ep); - goto bad_option; - } - free(opt); - while (isblank(*cp)) - cp++; - } -- /* -- * Turn on nohide which will allow this export to cross over -- * the 'mount --bind' mount point. -- */ -- if (ep->e_fslocdata) -- ep->e_flags |= NFSEXP_NOHIDE; - -- for (p = ep->e_secinfo; p->flav; p++) -- p->flags |= ep->e_flags & ~NFSEXP_SECINFO_FLAGS; -+ fix_pseudoflavor_flags(ep); - ep->e_squids = squids; - ep->e_sqgids = sqgids; - ep->e_nsquids = nsquids; -@@ -760,4 +758,34 @@ syntaxerr(char *msg) - xlog(L_ERROR, "%s:%d: syntax error: %s", - efname, efp?efp->x_line:0, msg); - } -- -+struct export_features *get_export_features(void) -+{ -+ static char *path = "/proc/fs/nfsd/export_features"; -+ static struct export_features ef; -+ static int cached = 0; -+ char buf[50]; -+ int c; -+ int fd; -+ -+ if (cached) -+ return &ef; -+ -+ ef.flags = NFSEXP_OLDFLAGS; -+ ef.secinfo_flags = NFSEXP_OLD_SECINFO_FLAGS; -+ -+ fd = open(path, O_RDONLY); -+ if (fd == -1) -+ goto good; -+ fd = read(fd, buf, 50); -+ if (fd == -1) -+ goto err; -+ c = sscanf(buf, "%x %x", &ef.flags, &ef.secinfo_flags); -+ if (c != 2) -+ goto err; -+good: -+ cached = 1; -+ return &ef; -+err: -+ xlog(L_WARNING, "unexpected error reading %s", path); -+ return &ef; -+} -diff --git a/support/nfs/getport.c b/support/nfs/getport.c -index 4bdf556..c930539 100644 ---- a/support/nfs/getport.c -+++ b/support/nfs/getport.c -@@ -45,6 +45,7 @@ - #include - #endif - -+#include "sockaddr.h" - #include "nfsrpc.h" - - /* -@@ -199,7 +200,63 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap, - return clnt; - } - --/* -+/** -+ * nfs_get_proto - Convert a netid to an address family and protocol number -+ * @netid: C string containing a netid -+ * @family: OUT: address family -+ * @protocol: OUT: protocol number -+ * -+ * Returns 1 and fills in @protocol if the netid was recognized; -+ * otherwise zero is returned. -+ */ -+#ifdef HAVE_LIBTIRPC -+int -+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol) -+{ -+ struct netconfig *nconf; -+ struct protoent *proto; -+ -+ nconf = getnetconfigent(netid); -+ if (nconf == NULL) -+ return 0; -+ -+ proto = getprotobyname(nconf->nc_proto); -+ if (proto == NULL) { -+ freenetconfigent(nconf); -+ return 0; -+ } -+ -+ *family = AF_UNSPEC; -+ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) -+ *family = AF_INET; -+ if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) -+ *family = AF_INET6; -+ freenetconfigent(nconf); -+ -+ *protocol = (unsigned long)proto->p_proto; -+ return 1; -+} -+#else /* !HAVE_LIBTIRPC */ -+int -+nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol) -+{ -+ struct protoent *proto; -+ -+ proto = getprotobyname(netid); -+ if (proto == NULL) -+ return 0; -+ -+ *family = AF_INET; -+ *protocol = (unsigned long)proto->p_proto; -+ return 1; -+} -+#endif /* !HAVE_LIBTIRPC */ -+ -+/** -+ * nfs_get_netid - Convert a protocol family and protocol name to a netid -+ * @family: protocol family -+ * @protocol: protocol number -+ * - * One of the arguments passed when querying remote rpcbind services - * via rpcbind v3 or v4 is a netid string. This replaces the pm_prot - * field used in legacy PMAP_GETPORT calls. -@@ -213,13 +270,12 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap, - * first entry that matches @family and @protocol and whose netid string - * fits in the provided buffer. - * -- * Returns a '\0'-terminated string if successful; otherwise NULL. -+ * Returns a '\0'-terminated string if successful. Caller must -+ * free the returned string. Otherwise NULL is returned, and - * rpc_createerr.cf_stat is set to reflect the error. - */ - #ifdef HAVE_LIBTIRPC -- --static char *nfs_gp_get_netid(const sa_family_t family, -- const unsigned short protocol) -+char *nfs_get_netid(const sa_family_t family, const unsigned long protocol) - { - char *nc_protofmly, *nc_proto, *nc_netid; - struct netconfig *nconf; -@@ -255,6 +311,9 @@ static char *nfs_gp_get_netid(const sa_family_t family, - - nc_netid = strdup(nconf->nc_netid); - endnetconfig(handle); -+ -+ if (nc_netid == NULL) -+ rpc_createerr.cf_stat = RPC_SYSTEMERROR; - return nc_netid; - } - endnetconfig(handle); -@@ -263,8 +322,28 @@ out: - rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; - return NULL; - } -+#else /* !HAVE_LIBTIRPC */ -+char *nfs_get_netid(const sa_family_t family, const unsigned long protocol) -+{ -+ struct protoent *proto; -+ char *netid; - --#endif /* HAVE_LIBTIRPC */ -+ if (family != AF_INET) -+ goto out; -+ proto = getprotobynumber((int)protocol); -+ if (proto == NULL) -+ goto out; -+ -+ netid = strdup(proto->p_name); -+ if (netid == NULL) -+ rpc_createerr.cf_stat = RPC_SYSTEMERROR; -+ return netid; -+ -+out: -+ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; -+ return NULL; -+} -+#endif /* !HAVE_LIBTIRPC */ - - /* - * Extract a port number from a universal address, and terminate the -@@ -421,7 +500,7 @@ static int nfs_gp_init_rpcb_parms(const struct sockaddr *sap, - { - char *netid, *addr; - -- netid = nfs_gp_get_netid(sap->sa_family, protocol); -+ netid = nfs_get_netid(sap->sa_family, protocol); - if (netid == NULL) - return 0; - -@@ -627,8 +706,8 @@ int nfs_rpc_ping(const struct sockaddr *sap, const socklen_t salen, - const rpcprog_t program, const rpcvers_t version, - const unsigned short protocol, const struct timeval *timeout) - { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - CLIENT *client; - struct timeval tout = { -1, 0 }; - int result = 0; -@@ -696,8 +775,8 @@ unsigned short nfs_getport(const struct sockaddr *sap, - const rpcvers_t version, - const unsigned short protocol) - { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - struct timeval timeout = { -1, 0 }; - unsigned short port = 0; - CLIENT *client; -@@ -755,8 +834,8 @@ int nfs_getport_ping(struct sockaddr *sap, const socklen_t salen, - } - - if (port != 0) { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - - memcpy(saddr, sap, (size_t)salen); - nfs_set_port(saddr, port); -@@ -807,8 +886,8 @@ unsigned short nfs_getlocalport(const rpcprot_t program, - const rpcvers_t version, - const unsigned short protocol) - { -- struct sockaddr_storage address; -- struct sockaddr *lb_addr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *lb_addr = &address.sa; - socklen_t lb_len = sizeof(*lb_addr); - unsigned short port = 0; - -@@ -891,8 +970,8 @@ unsigned short nfs_rpcb_getaddr(const struct sockaddr *sap, - const unsigned short protocol, - const struct timeval *timeout) - { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - CLIENT *client; - struct rpcb parms; - struct timeval tout = { -1, 0 }; -diff --git a/support/nfs/rpc_socket.c b/support/nfs/rpc_socket.c -index 9c20f61..0e20824 100644 ---- a/support/nfs/rpc_socket.c -+++ b/support/nfs/rpc_socket.c -@@ -26,6 +26,8 @@ - - #include - #include -+ -+#include - #include - #include - #include -@@ -38,6 +40,7 @@ - #include - #include - -+#include "sockaddr.h" - #include "nfsrpc.h" - - #ifdef HAVE_LIBTIRPC -@@ -51,6 +54,7 @@ - #define NFSRPC_TIMEOUT_UDP (3) - #define NFSRPC_TIMEOUT_TCP (10) - -+ - /* - * Set up an RPC client for communicating via a AF_LOCAL socket. - * -@@ -121,10 +125,10 @@ static int nfs_bind(const int sock, const sa_family_t family) - - switch (family) { - case AF_INET: -- return bind(sock, (struct sockaddr *)&sin, -+ return bind(sock, (struct sockaddr *)(char *)&sin, - (socklen_t)sizeof(sin)); - case AF_INET6: -- return bind(sock, (struct sockaddr *)&sin6, -+ return bind(sock, (struct sockaddr *)(char *)&sin6, - (socklen_t)sizeof(sin6)); - } - -@@ -153,9 +157,9 @@ static int nfs_bindresvport(const int sock, const sa_family_t family) - - switch (family) { - case AF_INET: -- return bindresvport_sa(sock, (struct sockaddr *)&sin); -+ return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin); - case AF_INET6: -- return bindresvport_sa(sock, (struct sockaddr *)&sin6); -+ return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin6); - } - - errno = EAFNOSUPPORT; -@@ -416,49 +420,6 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap, - } - - /** -- * nfs_get_port - extract port value from a socket address -- * @sap: pointer to socket address -- * -- * Returns port value in host byte order. -- */ --uint16_t --nfs_get_port(const struct sockaddr *sap) --{ -- const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -- const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; -- -- switch (sap->sa_family) { -- case AF_INET: -- return ntohs(sin->sin_port); -- case AF_INET6: -- return ntohs(sin6->sin6_port); -- } -- return 0; --} -- --/** -- * nfs_set_port - set port value in a socket address -- * @sap: pointer to socket address -- * @port: port value to set -- * -- */ --void --nfs_set_port(struct sockaddr *sap, const uint16_t port) --{ -- struct sockaddr_in *sin = (struct sockaddr_in *)sap; -- struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; -- -- switch (sap->sa_family) { -- case AF_INET: -- sin->sin_port = htons(port); -- break; -- case AF_INET6: -- sin6->sin6_port = htons(port); -- break; -- } --} -- --/** - * nfs_get_rpcclient - acquire an RPC client - * @sap: pointer to socket address of RPC server - * @salen: length of socket address -diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c -new file mode 100644 -index 0000000..59ba505 ---- /dev/null -+++ b/support/nfs/svc_create.c -@@ -0,0 +1,246 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+#include -+#include -+ -+#ifdef HAVE_TCP_WRAPPER -+#include "tcpwrapper.h" -+#endif -+ -+#include "rpcmisc.h" -+#include "xlog.h" -+ -+#ifdef HAVE_LIBTIRPC -+ -+/* -+ * Set up an appropriate bind address, given @port and @nconf. -+ * -+ * Returns getaddrinfo(3) results if successful. Caller must -+ * invoke freeaddrinfo(3) on these results. -+ * -+ * Otherwise NULL is returned if an error occurs. -+ */ -+__attribute_malloc__ -+static struct addrinfo * -+svc_create_bindaddr(struct netconfig *nconf, const uint16_t port) -+{ -+ struct addrinfo *ai = NULL; -+ struct addrinfo hint = { -+ .ai_flags = AI_PASSIVE | AI_NUMERICSERV, -+ }; -+ char buf[8]; -+ int error; -+ -+ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) -+ hint.ai_family = AF_INET; -+#ifdef IPV6_SUPPORTED -+ else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) -+ hint.ai_family = AF_INET6; -+#endif /* IPV6_SUPPORTED */ -+ else { -+ xlog(D_GENERAL, "Unrecognized bind address family: %s", -+ nconf->nc_protofmly); -+ return NULL; -+ } -+ -+ if (strcmp(nconf->nc_proto, NC_UDP) == 0) -+ hint.ai_protocol = (int)IPPROTO_UDP; -+ else if (strcmp(nconf->nc_proto, NC_TCP) == 0) -+ hint.ai_protocol = (int)IPPROTO_TCP; -+ else { -+ xlog(D_GENERAL, "Unrecognized bind address protocol: %s", -+ nconf->nc_proto); -+ return NULL; -+ } -+ -+ (void)snprintf(buf, sizeof(buf), "%u", port); -+ error = getaddrinfo(NULL, buf, &hint, &ai); -+ if (error != 0) { -+ xlog(L_ERROR, "Failed to construct bind address: %s", -+ gai_strerror(error)); -+ return NULL; -+ } -+ -+ return ai; -+} -+ -+static unsigned int -+svc_create_nconf(const char *name, const rpcprog_t program, -+ const rpcvers_t version, -+ void (*dispatch)(struct svc_req *, SVCXPRT *), -+ const uint16_t port, struct netconfig *nconf) -+{ -+ struct t_bind bindaddr; -+ struct addrinfo *ai; -+ SVCXPRT *xprt; -+ -+ ai = svc_create_bindaddr(nconf, port); -+ if (ai == NULL) -+ return 0; -+ -+ bindaddr.addr.buf = ai->ai_addr; -+ bindaddr.qlen = SOMAXCONN; -+ -+ xprt = svc_tli_create(RPC_ANYFD, nconf, &bindaddr, 0, 0); -+ freeaddrinfo(ai); -+ if (xprt == NULL) { -+ xlog(D_GENERAL, "Failed to create listener xprt " -+ "(%s, %u, %s)", name, version, nconf->nc_netid); -+ return 0; -+ } -+ -+ if (!svc_reg(xprt, program, version, dispatch, nconf)) { -+ /* svc_reg(3) destroys @xprt in this case */ -+ xlog(D_GENERAL, "Failed to register (%s, %u, %s)", -+ name, version, nconf->nc_netid); -+ return 0; -+ } -+ -+ return 1; -+} -+ -+/** -+ * nfs_svc_create - start up RPC svc listeners -+ * @name: C string containing name of new service -+ * @program: RPC program number to register -+ * @version: RPC version number to register -+ * @dispatch: address of function that handles incoming RPC requests -+ * @port: if not zero, transport listens on this port -+ * -+ * Sets up network transports for receiving RPC requests, and starts -+ * the RPC dispatcher. Returns the number of started network transports. -+ */ -+unsigned int -+nfs_svc_create(__attribute__((unused)) char *name, -+ const rpcprog_t program, const rpcvers_t version, -+ void (*dispatch)(struct svc_req *, SVCXPRT *), -+ const uint16_t port) -+{ -+ const struct sigaction create_sigaction = { -+ .sa_handler = SIG_IGN, -+ }; -+ unsigned int visible, up; -+ struct netconfig *nconf; -+ void *handlep; -+ -+ /* -+ * Ignore SIGPIPE to avoid exiting sideways when peers -+ * close their TCP connection while we're trying to reply -+ * to them. -+ */ -+ (void)sigaction(SIGPIPE, &create_sigaction, NULL); -+ -+ handlep = setnetconfig(); -+ if (handlep == NULL) { -+ xlog(L_ERROR, "Failed to access local netconfig database: %s", -+ nc_sperror()); -+ return 0; -+ } -+ -+ visible = 0; -+ up = 0; -+ while ((nconf = getnetconfig(handlep)) != NULL) { -+ if (!(nconf->nc_flag & NC_VISIBLE)) -+ continue; -+ visible++; -+ up += svc_create_nconf(name, program, version, dispatch, -+ port, nconf); -+ } -+ -+ if (visible == 0) -+ xlog(L_ERROR, "Failed to find any visible netconfig entries"); -+ -+ if (endnetconfig(handlep) == -1) -+ xlog(L_ERROR, "Failed to close local netconfig database: %s", -+ nc_sperror()); -+ -+ return up; -+} -+ -+/** -+ * nfs_svc_unregister - remove service registrations from local rpcbind database -+ * @program: RPC program number to unregister -+ * @version: RPC version number to unregister -+ * -+ * Removes all registrations for [ @program, @version ] . -+ */ -+void -+nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) -+{ -+ if (rpcb_unset(program, version, NULL) == FALSE) -+ xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", -+ (unsigned long)program, (unsigned long)version); -+} -+ -+#else /* !HAVE_LIBTIRPC */ -+ -+/** -+ * nfs_svc_create - start up RPC svc listeners -+ * @name: C string containing name of new service -+ * @program: RPC program number to register -+ * @version: RPC version number to register -+ * @dispatch: address of function that handles incoming RPC requests -+ * @port: if not zero, transport listens on this port -+ * -+ * Sets up network transports for receiving RPC requests, and starts -+ * the RPC dispatcher. Returns the number of started network transports. -+ */ -+unsigned int -+nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version, -+ void (*dispatch)(struct svc_req *, SVCXPRT *), -+ const uint16_t port) -+{ -+ rpc_init(name, (int)program, (int)version, dispatch, (int)port); -+ return 1; -+} -+ -+/** -+ * nfs_svc_unregister - remove service registrations from local rpcbind database -+ * @program: RPC program number to unregister -+ * @version: RPC version number to unregister -+ * -+ * Removes all registrations for [ @program, @version ] . -+ */ -+void -+nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) -+{ -+ if (pmap_unset((unsigned long)program, (unsigned long)version) == FALSE) -+ xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", -+ (unsigned long)program, (unsigned long)version); -+} -+ -+#endif /* !HAVE_LIBTIRPC */ -diff --git a/support/nsm/Makefile.am b/support/nsm/Makefile.am -new file mode 100644 -index 0000000..2038e68 ---- /dev/null -+++ b/support/nsm/Makefile.am -@@ -0,0 +1,45 @@ -+## Process this file with automake to produce Makefile.in -+ -+GENFILES_CLNT = sm_inter_clnt.c -+GENFILES_SVC = sm_inter_svc.c -+GENFILES_XDR = sm_inter_xdr.c -+GENFILES_H = sm_inter.h -+ -+GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) -+ -+EXTRA_DIST = sm_inter.x -+ -+noinst_LIBRARIES = libnsm.a -+libnsm_a_SOURCES = $(GENFILES) file.c rpc.c -+ -+BUILT_SOURCES = $(GENFILES) -+ -+if CONFIG_RPCGEN -+RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen -+$(RPCGEN): -+ make -C ../../tools/rpcgen all -+else -+RPCGEN = @RPCGEN_PATH@ -+endif -+ -+$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -l -o $@ $< -+ -+$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -m -o $@ $< -+ -+$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -c -o $@ $< -+ -+$(GENFILES_H): %.h: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -h -o $@ $< -+ rm -f $(top_builddir)/support/include/sm_inter.h -+ $(LN_S) ../nsm/sm_inter.h $(top_builddir)/support/include/sm_inter.h -+ -+MAINTAINERCLEANFILES = Makefile.in -+ -+CLEANFILES = $(GENFILES) $(top_builddir)/support/include/sm_inter.h -diff --git a/support/nsm/file.c b/support/nsm/file.c -new file mode 100644 -index 0000000..d469219 ---- /dev/null -+++ b/support/nsm/file.c -@@ -0,0 +1,1067 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+/* -+ * NSM for Linux. -+ * -+ * Callback information and NSM state is stored in files, usually -+ * under /var/lib/nfs. A database of information contained in local -+ * files stores NLM callback data and what remote peers to notify of -+ * reboots. -+ * -+ * For each monitored remote peer, a text file is created under the -+ * directory specified by NSM_MONITOR_DIR. The name of the file -+ * is a valid DNS hostname. The hostname string must be a valid -+ * ASCII DNS name, and must not contain slash characters, white space, -+ * or '\0' (ie. anything that might have some special meaning in a -+ * path name). -+ * -+ * The contents of each file include seven blank-separated fields of -+ * text, finished with '\n'. The first field contains the network -+ * address of the NLM service to call back. The current implementation -+ * supports using only IPv4 addresses, so the only contents of this -+ * field are a network order IPv4 address expressed in 8 hexadecimal -+ * characters. -+ * -+ * The next four fields are text strings of hexadecimal characters, -+ * representing: -+ * -+ * 2. A 4 byte RPC program number of the NLM service to call back -+ * 3. A 4 byte RPC version number of the NLM service to call back -+ * 4. A 4 byte RPC procedure number of the NLM service to call back -+ * 5. A 16 byte opaque cookie that the NLM service uses to identify -+ * the monitored host -+ * -+ * The sixth field is the monitored host's mon_name, passed to statd -+ * via an SM_MON request. -+ * -+ * The seventh field is the my_name for this peer, which is the -+ * hostname of the local NLM (currently on Linux, the result of -+ * `uname -n`). This can be used as the source address/hostname -+ * when sending SM_NOTIFY requests. -+ * -+ * The NSM protocol does not limit the contents of these strings -+ * in any way except that they must fit into 1024 bytes. Our -+ * implementation requires that these strings not contain -+ * white space or '\0'. -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include -+#endif -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#ifndef S_SPLINT_S -+#include -+#endif -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "xlog.h" -+#include "nsm.h" -+ -+#define RPCARGSLEN (4 * (8 + 1)) -+#define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1) -+ -+#define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state" -+ -+/* -+ * Some distributions place statd's files in a subdirectory -+ */ -+#define NSM_PATH_EXTENSION -+/* #define NSM_PATH_EXTENSION "/statd" */ -+ -+#define NSM_DEFAULT_STATEDIR NFS_STATEDIR NSM_PATH_EXTENSION -+ -+static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR; -+ -+#define NSM_MONITOR_DIR "sm" -+#define NSM_NOTIFY_DIR "sm.bak" -+#define NSM_STATE_FILE "state" -+ -+ -+static _Bool -+error_check(const int len, const size_t buflen) -+{ -+ return (len < 0) || ((size_t)len >= buflen); -+} -+ -+static _Bool -+exact_error_check(const ssize_t len, const size_t buflen) -+{ -+ return (len < 0) || ((size_t)len != buflen); -+} -+ -+/* -+ * Returns a dynamically allocated, '\0'-terminated buffer -+ * containing an appropriate pathname, or NULL if an error -+ * occurs. Caller must free the returned result with free(3). -+ */ -+__attribute_malloc__ -+static char * -+nsm_make_record_pathname(const char *directory, const char *hostname) -+{ -+ const char *c; -+ size_t size; -+ char *path; -+ int len; -+ -+ /* -+ * Block hostnames that contain characters that have -+ * meaning to the file system (like '/'), or that can -+ * be confusing on visual inspection (like ' '). -+ */ -+ for (c = hostname; *c != '\0'; c++) -+ if (*c == '/' || isspace((int)*c) != 0) { -+ xlog(D_GENERAL, "Hostname contains invalid characters"); -+ return NULL; -+ } -+ -+ size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3; -+ if (size > PATH_MAX) { -+ xlog(D_GENERAL, "Hostname results in pathname that is too long"); -+ return NULL; -+ } -+ -+ path = malloc(size); -+ if (path == NULL) { -+ xlog(D_GENERAL, "Failed to allocate memory for pathname"); -+ return NULL; -+ } -+ -+ len = snprintf(path, size, "%s/%s/%s", -+ nsm_base_dirname, directory, hostname); -+ if (error_check(len, size)) { -+ xlog(D_GENERAL, "Pathname did not fit in specified buffer"); -+ free(path); -+ return NULL; -+ } -+ -+ return path; -+} -+ -+/* -+ * Returns a dynamically allocated, '\0'-terminated buffer -+ * containing an appropriate pathname, or NULL if an error -+ * occurs. Caller must free the returned result with free(3). -+ */ -+__attribute_malloc__ -+static char * -+nsm_make_pathname(const char *directory) -+{ -+ size_t size; -+ char *path; -+ int len; -+ -+ size = strlen(nsm_base_dirname) + strlen(directory) + 2; -+ if (size > PATH_MAX) -+ return NULL; -+ -+ path = malloc(size); -+ if (path == NULL) -+ return NULL; -+ -+ len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory); -+ if (error_check(len, size)) { -+ free(path); -+ return NULL; -+ } -+ -+ return path; -+} -+ -+/* -+ * Returns a dynamically allocated, '\0'-terminated buffer -+ * containing an appropriate pathname, or NULL if an error -+ * occurs. Caller must free the returned result with free(3). -+ */ -+__attribute_malloc__ -+static char * -+nsm_make_temp_pathname(const char *pathname) -+{ -+ size_t size; -+ char *path; -+ int len; -+ -+ size = strlen(pathname) + sizeof(".new") + 2; -+ if (size > PATH_MAX) -+ return NULL; -+ -+ path = malloc(size); -+ if (path == NULL) -+ return NULL; -+ -+ len = snprintf(path, size, "%s.new", pathname); -+ if (error_check(len, size)) { -+ free(path); -+ return NULL; -+ } -+ -+ return path; -+} -+ -+/* -+ * Use "mktemp, write, rename" to update the contents of a file atomically. -+ * -+ * Returns true if completely successful, or false if some error occurred. -+ */ -+static _Bool -+nsm_atomic_write(const char *path, const void *buf, const size_t buflen) -+{ -+ _Bool result = false; -+ ssize_t len; -+ char *temp; -+ int fd; -+ -+ temp = nsm_make_temp_pathname(path); -+ if (temp == NULL) { -+ xlog(L_ERROR, "Failed to create new path for %s", path); -+ goto out; -+ } -+ -+ fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644); -+ if (fd == -1) { -+ xlog(L_ERROR, "Failed to create %s: %m", temp); -+ goto out; -+ } -+ -+ len = write(fd, buf, buflen); -+ if (exact_error_check(len, buflen)) { -+ xlog(L_ERROR, "Failed to write %s: %m", temp); -+ (void)close(fd); -+ (void)unlink(temp); -+ goto out; -+ } -+ -+ if (close(fd) == -1) { -+ xlog(L_ERROR, "Failed to close %s: %m", temp); -+ (void)unlink(temp); -+ goto out; -+ } -+ -+ if (rename(temp, path) == -1) { -+ xlog(L_ERROR, "Failed to rename %s -> %s: %m", -+ temp, path); -+ (void)unlink(temp); -+ goto out; -+ } -+ -+ /* Ostensibly, a sync(2) is not needed here because -+ * open(O_CREAT), write(O_SYNC), and rename(2) are -+ * already synchronous with persistent storage, for -+ * any file system we care about. */ -+ -+ result = true; -+ -+out: -+ free(temp); -+ return result; -+} -+ -+/** -+ * nsm_setup_pathnames - set up pathname -+ * @progname: C string containing name of program, for error messages -+ * @parentdir: C string containing pathname to on-disk state, or NULL -+ * -+ * This runs before logging is set up, so error messages are directed -+ * to stderr. -+ * -+ * Returns true and sets up our pathnames, if @parentdir was valid -+ * and usable; otherwise false is returned. -+ */ -+_Bool -+nsm_setup_pathnames(const char *progname, const char *parentdir) -+{ -+ static char buf[PATH_MAX]; -+ struct stat st; -+ char *path; -+ -+ /* First: test length of name and whether it exists */ -+ if (lstat(parentdir, &st) == -1) { -+ (void)fprintf(stderr, "%s: Failed to stat %s: %s", -+ progname, parentdir, strerror(errno)); -+ return false; -+ } -+ -+ /* Ensure we have a clean directory pathname */ -+ strncpy(buf, parentdir, sizeof(buf)); -+ path = dirname(buf); -+ if (*path == '.') { -+ (void)fprintf(stderr, "%s: Unusable directory %s", -+ progname, parentdir); -+ return false; -+ } -+ -+ xlog(D_CALL, "Using %s as the state directory", parentdir); -+ strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname)); -+ return true; -+} -+ -+/** -+ * nsm_is_default_parentdir - check if parent directory is default -+ * -+ * Returns true if the active statd parent directory, set by -+ * nsm_change_pathname(), is the same as the built-in default -+ * parent directory; otherwise false is returned. -+ */ -+_Bool -+nsm_is_default_parentdir(void) -+{ -+ return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0; -+} -+ -+/* -+ * Clear all capabilities but CAP_NET_BIND_SERVICE. This permits -+ * callers to acquire privileged source ports, but all other root -+ * capabilities are disallowed. -+ * -+ * Returns true if successful, or false if some error occurred. -+ */ -+static _Bool -+nsm_clear_capabilities(void) -+{ -+ cap_t caps; -+ -+ caps = cap_from_text("cap_net_bind_service=ep"); -+ if (caps == NULL) { -+ xlog(L_ERROR, "Failed to allocate capability: %m"); -+ return false; -+ } -+ -+ if (cap_set_proc(caps) == -1) { -+ xlog(L_ERROR, "Failed to set capability flags: %m"); -+ (void)cap_free(caps); -+ return false; -+ } -+ -+ (void)cap_free(caps); -+ return true; -+} -+ -+/** -+ * nsm_drop_privileges - drop root privileges -+ * @pidfd: file descriptor of a pid file -+ * -+ * Returns true if successful, or false if some error occurred. -+ * -+ * Set our effective UID and GID to that of our on-disk database. -+ */ -+_Bool -+nsm_drop_privileges(const int pidfd) -+{ -+ struct stat st; -+ -+ (void)umask(S_IRWXO); -+ -+ /* -+ * XXX: If we can't stat dirname, or if dirname is owned by -+ * root, we should use "statduser" instead, which is set up -+ * by configure.ac. Nothing in nfs-utils seems to use -+ * "statduser," though. -+ */ -+ if (lstat(nsm_base_dirname, &st) == -1) { -+ xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname); -+ return false; -+ } -+ -+ if (st.st_uid == 0) { -+ xlog_warn("Running as root. " -+ "chown %s to choose different user", nsm_base_dirname); -+ return true; -+ } -+ -+ if (chdir(nsm_base_dirname) == -1) { -+ xlog(L_ERROR, "Failed to change working directory to %s: %m", -+ nsm_base_dirname); -+ return false; -+ } -+ -+ /* -+ * If the pidfile happens to reside on NFS, dropping privileges -+ * will probably cause us to lose access, even though we are -+ * holding it open. Chown it to prevent this. -+ */ -+ if (pidfd >= 0) -+ if (fchown(pidfd, st.st_uid, st.st_gid) == -1) -+ xlog_warn("Failed to change owner of pidfile: %m"); -+ -+ /* -+ * Don't clear capabilities when dropping root. -+ */ -+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { -+ xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m"); -+ return 0; -+ } -+ -+ if (setgroups(0, NULL) == -1) { -+ xlog(L_ERROR, "Failed to drop supplementary groups: %m"); -+ return false; -+ } -+ -+ /* -+ * ORDER -+ * -+ * setgid(2) first, as setuid(2) may remove privileges needed -+ * to set the group id. -+ */ -+ if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) { -+ xlog(L_ERROR, "Failed to drop privileges: %m"); -+ return false; -+ } -+ -+ xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); -+ -+ return nsm_clear_capabilities(); -+} -+ -+/** -+ * nsm_get_state - retrieve on-disk NSM state number -+ * -+ * Returns an odd NSM state number read from disk, or an initial -+ * state number. Zero is returned if some error occurs. -+ */ -+int -+nsm_get_state(_Bool update) -+{ -+ int fd, state = 0; -+ ssize_t result; -+ char *path = NULL; -+ -+ path = nsm_make_pathname(NSM_STATE_FILE); -+ if (path == NULL) { -+ xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE); -+ goto out; -+ } -+ -+ fd = open(path, O_RDONLY); -+ if (fd == -1) { -+ if (errno != ENOENT) { -+ xlog(L_ERROR, "Failed to open %s: %m", path); -+ goto out; -+ } -+ -+ xlog(L_NOTICE, "Initializing NSM state"); -+ state = 1; -+ update = true; -+ goto update; -+ } -+ -+ result = read(fd, &state, sizeof(state)); -+ if (exact_error_check(result, sizeof(state))) { -+ xlog_warn("Failed to read %s: %m", path); -+ -+ xlog(L_NOTICE, "Initializing NSM state"); -+ state = 1; -+ update = true; -+ goto update; -+ } -+ -+ if ((state & 1) == 0) -+ state++; -+ -+update: -+ (void)close(fd); -+ -+ if (update) { -+ state += 2; -+ if (!nsm_atomic_write(path, &state, sizeof(state))) -+ state = 0; -+ } -+ -+out: -+ free(path); -+ return state; -+} -+ -+/** -+ * nsm_update_kernel_state - attempt to post new NSM state to kernel -+ * @state: NSM state number -+ * -+ */ -+void -+nsm_update_kernel_state(const int state) -+{ -+ ssize_t result; -+ char buf[20]; -+ int fd, len; -+ -+ fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY); -+ if (fd == -1) { -+ xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m"); -+ return; -+ } -+ -+ len = snprintf(buf, sizeof(buf), "%d", state); -+ if (error_check(len, sizeof(buf))) { -+ xlog_warn("Failed to form NSM state number string"); -+ return; -+ } -+ -+ result = write(fd, buf, strlen(buf)); -+ if (exact_error_check(result, strlen(buf))) -+ xlog_warn("Failed to write NSM state number: %m"); -+ -+ if (close(fd) == -1) -+ xlog(L_ERROR, "Failed to close NSM state file " -+ NSM_KERNEL_STATE_FILE ": %m"); -+} -+ -+/** -+ * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" -+ * -+ * Returns the count of host records that were moved. -+ * -+ * Note that if any error occurs during this process, some monitor -+ * records may be left in the "sm" directory. -+ */ -+unsigned int -+nsm_retire_monitored_hosts(void) -+{ -+ unsigned int count = 0; -+ struct dirent *de; -+ char *path; -+ DIR *dir; -+ -+ path = nsm_make_pathname(NSM_MONITOR_DIR); -+ if (path == NULL) { -+ xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); -+ return count; -+ } -+ -+ dir = opendir(path); -+ free(path); -+ if (dir == NULL) { -+ xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); -+ return count; -+ } -+ -+ while ((de = readdir(dir)) != NULL) { -+ char *src, *dst; -+ -+ if (de->d_type != (unsigned char)DT_REG) -+ continue; -+ if (de->d_name[0] == '.') -+ continue; -+ -+ src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); -+ if (src == NULL) { -+ xlog_warn("Bad monitor file name, skipping"); -+ continue; -+ } -+ -+ dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); -+ if (dst == NULL) { -+ free(src); -+ xlog_warn("Bad notify file name, skipping"); -+ continue; -+ } -+ -+ if (rename(src, dst) == -1) -+ xlog_warn("Failed to rename %s -> %s: %m", -+ src, dst); -+ else { -+ xlog(D_GENERAL, "Retired record for mon_name %s", -+ de->d_name); -+ count++; -+ } -+ -+ free(dst); -+ free(src); -+ } -+ -+ (void)closedir(dir); -+ return count; -+} -+ -+/* -+ * nsm_priv_to_hex - convert a NSM private cookie to a hex string. -+ * -+ * @priv: buffer holding the binary NSM private cookie -+ * @buf: output buffer for NULL terminated hex string -+ * @buflen: size of output buffer -+ * -+ * Returns the length of the resulting string or 0 on error -+ */ -+size_t -+nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen) -+{ -+ int i, len; -+ size_t remaining = buflen; -+ -+ for (i = 0; i < SM_PRIV_SIZE; i++) { -+ len = snprintf(buf, remaining, "%02x", -+ (unsigned int)(0xff & priv[i])); -+ if (error_check(len, remaining)) -+ return 0; -+ buf += len; -+ remaining -= (size_t)len; -+ } -+ -+ return buflen - remaining; -+} -+ -+/* -+ * Returns the length in bytes of the created record. -+ */ -+__attribute_noinline__ -+static size_t -+nsm_create_monitor_record(char *buf, const size_t buflen, -+ const struct sockaddr *sap, const struct mon *m) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ size_t hexlen, remaining = buflen; -+ int len; -+ -+ len = snprintf(buf, remaining, "%08x %08x %08x %08x ", -+ (unsigned int)sin->sin_addr.s_addr, -+ (unsigned int)m->mon_id.my_id.my_prog, -+ (unsigned int)m->mon_id.my_id.my_vers, -+ (unsigned int)m->mon_id.my_id.my_proc); -+ if (error_check(len, remaining)) -+ return 0; -+ buf += len; -+ remaining -= (size_t)len; -+ -+ hexlen = nsm_priv_to_hex(m->priv, buf, remaining); -+ if (hexlen == 0) -+ return 0; -+ buf += hexlen; -+ remaining -= hexlen; -+ -+ len = snprintf(buf, remaining, " %s %s\n", -+ m->mon_id.mon_name, m->mon_id.my_id.my_name); -+ if (error_check(len, remaining)) -+ return 0; -+ remaining -= (size_t)len; -+ -+ return buflen - remaining; -+} -+ -+static _Bool -+nsm_append_monitored_host(const char *path, const char *line) -+{ -+ _Bool result = false; -+ char *buf = NULL; -+ struct stat stb; -+ size_t buflen; -+ ssize_t len; -+ int fd; -+ -+ if (stat(path, &stb) == -1) { -+ xlog(L_ERROR, "Failed to insert: " -+ "could not stat original file %s: %m", path); -+ goto out; -+ } -+ buflen = (size_t)stb.st_size + strlen(line); -+ -+ buf = malloc(buflen + 1); -+ if (buf == NULL) { -+ xlog(L_ERROR, "Failed to insert: no memory"); -+ goto out; -+ } -+ memset(buf, 0, buflen + 1); -+ -+ fd = open(path, O_RDONLY); -+ if (fd == -1) { -+ xlog(L_ERROR, "Failed to insert: " -+ "could not open original file %s: %m", path); -+ goto out; -+ } -+ -+ len = read(fd, buf, (size_t)stb.st_size); -+ if (exact_error_check(len, (size_t)stb.st_size)) { -+ xlog(L_ERROR, "Failed to insert: " -+ "could not read original file %s: %m", path); -+ (void)close(fd); -+ goto out; -+ } -+ (void)close(fd); -+ -+ strcat(buf, line); -+ -+ if (nsm_atomic_write(path, buf, buflen)) -+ result = true; -+ -+out: -+ free(buf); -+ return result; -+} -+ -+/** -+ * nsm_insert_monitored_host - write callback data for one host to disk -+ * @hostname: C string containing a hostname -+ * @sap: sockaddr containing NLM callback address -+ * @mon: SM_MON arguments to save -+ * -+ * Returns true if successful, otherwise false if some error occurs. -+ */ -+_Bool -+nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, -+ const struct mon *m) -+{ -+ static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; -+ char *path; -+ _Bool result = false; -+ ssize_t len; -+ size_t size; -+ int fd; -+ -+ path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname); -+ if (path == NULL) { -+ xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'", -+ hostname); -+ return false; -+ } -+ -+ size = nsm_create_monitor_record(buf, sizeof(buf), sap, m); -+ if (size == 0) { -+ xlog(L_ERROR, "Failed to insert: record too long"); -+ goto out; -+ } -+ -+ /* -+ * If exclusive create fails, we're adding a new line to an -+ * existing file. -+ */ -+ fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR); -+ if (fd == -1) { -+ if (errno != EEXIST) { -+ xlog(L_ERROR, "Failed to insert: creating %s: %m", path); -+ goto out; -+ } -+ -+ result = nsm_append_monitored_host(path, buf); -+ goto out; -+ } -+ result = true; -+ -+ len = write(fd, buf, size); -+ if (exact_error_check(len, size)) { -+ xlog_warn("Failed to insert: writing %s: %m", path); -+ (void)unlink(path); -+ result = false; -+ } -+ -+ if (close(fd) == -1) { -+ xlog(L_ERROR, "Failed to insert: closing %s: %m", path); -+ (void)unlink(path); -+ result = false; -+ } -+ -+out: -+ free(path); -+ return result; -+} -+ -+__attribute_noinline__ -+static _Bool -+nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m) -+{ -+ unsigned int i, tmp; -+ int count; -+ char *c; -+ -+ c = strchr(line, '\n'); -+ if (c != NULL) -+ *c = '\0'; -+ -+ count = sscanf(line, "%8x %8x %8x %8x ", -+ (unsigned int *)&sin->sin_addr.s_addr, -+ (unsigned int *)&m->mon_id.my_id.my_prog, -+ (unsigned int *)&m->mon_id.my_id.my_vers, -+ (unsigned int *)&m->mon_id.my_id.my_proc); -+ if (count != 4) -+ return false; -+ -+ c = line + RPCARGSLEN; -+ for (i = 0; i < SM_PRIV_SIZE; i++) { -+ if (sscanf(c, "%2x", &tmp) != 1) -+ return false; -+ m->priv[i] = (char)tmp; -+ c += 2; -+ } -+ -+ c++; -+ m->mon_id.mon_name = c; -+ while (*c != '\0' && *c != ' ') -+ c++; -+ if (*c != '\0') -+ *c++ = '\0'; -+ while (*c == ' ') -+ c++; -+ m->mon_id.my_id.my_name = c; -+ -+ return true; -+} -+ -+/* -+ * Stuff a 'struct mon' with callback data, and call @func. -+ * -+ * Returns the count of in-core records created. -+ */ -+static unsigned int -+nsm_read_line(const char *hostname, const time_t timestamp, char *line, -+ nsm_populate_t func) -+{ -+ struct sockaddr_in sin = { -+ .sin_family = AF_INET, -+ }; -+ struct mon m; -+ -+ if (!nsm_parse_line(line, &sin, &m)) -+ return 0; -+ -+ return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp); -+} -+ -+/* -+ * Given a filename, reads data from a file under NSM_MONITOR_DIR -+ * and invokes @func so caller can populate their in-core -+ * database with this data. -+ */ -+static unsigned int -+nsm_load_host(const char *directory, const char *filename, nsm_populate_t func) -+{ -+ char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; -+ unsigned int result = 0; -+ struct stat stb; -+ char *path; -+ FILE *f; -+ -+ path = nsm_make_record_pathname(directory, filename); -+ if (path == NULL) -+ goto out_err; -+ -+ if (stat(path, &stb) == -1) { -+ xlog(L_ERROR, "Failed to stat %s: %m", path); -+ goto out_freepath; -+ } -+ -+ f = fopen(path, "r"); -+ if (f == NULL) { -+ xlog(L_ERROR, "Failed to open %s: %m", path); -+ goto out_freepath; -+ } -+ -+ while (fgets(buf, (int)sizeof(buf), f) != NULL) { -+ buf[sizeof(buf) - 1] = '\0'; -+ result += nsm_read_line(filename, stb.st_mtime, buf, func); -+ } -+ if (result == 0) -+ xlog(L_ERROR, "Failed to read monitor data from %s", path); -+ -+ (void)fclose(f); -+ -+out_freepath: -+ free(path); -+out_err: -+ return result; -+} -+ -+static unsigned int -+nsm_load_dir(const char *directory, nsm_populate_t func) -+{ -+ unsigned int count = 0; -+ struct dirent *de; -+ char *path; -+ DIR *dir; -+ -+ path = nsm_make_pathname(directory); -+ if (path == NULL) { -+ xlog(L_ERROR, "Failed to allocate path for directory %s", -+ directory); -+ return 0; -+ } -+ -+ dir = opendir(path); -+ free(path); -+ if (dir == NULL) { -+ xlog(L_ERROR, "Failed to open directory %s: %m", -+ directory); -+ return 0; -+ } -+ -+ while ((de = readdir(dir)) != NULL) { -+ if (de->d_type != (unsigned char)DT_REG) -+ continue; -+ if (de->d_name[0] == '.') -+ continue; -+ -+ count += nsm_load_host(directory, de->d_name, func); -+ } -+ -+ (void)closedir(dir); -+ return count; -+} -+ -+/** -+ * nsm_load_monitor_list - load list of hosts to monitor -+ * @func: callback function to create entry for one host -+ * -+ * Returns the count of hosts that were found in the directory. -+ */ -+unsigned int -+nsm_load_monitor_list(nsm_populate_t func) -+{ -+ return nsm_load_dir(NSM_MONITOR_DIR, func); -+} -+ -+/** -+ * nsm_load_notify_list - load list of hosts to notify -+ * @func: callback function to create entry for one host -+ * -+ * Returns the count of hosts that were found in the directory. -+ */ -+unsigned int -+nsm_load_notify_list(nsm_populate_t func) -+{ -+ return nsm_load_dir(NSM_NOTIFY_DIR, func); -+} -+ -+static void -+nsm_delete_host(const char *directory, const char *hostname, -+ const char *mon_name, const char *my_name) -+{ -+ char line[LINELEN + 1 + SM_MAXSTRLEN + 2]; -+ char *outbuf = NULL; -+ struct stat stb; -+ char *path, *next; -+ size_t remaining; -+ FILE *f; -+ -+ path = nsm_make_record_pathname(directory, hostname); -+ if (path == NULL) { -+ xlog(L_ERROR, "Bad filename, not deleting"); -+ return; -+ } -+ -+ if (stat(path, &stb) == -1) { -+ xlog(L_ERROR, "Failed to delete: " -+ "could not stat original file %s: %m", path); -+ goto out; -+ } -+ remaining = (size_t)stb.st_size + 1; -+ -+ outbuf = malloc(remaining); -+ if (outbuf == NULL) { -+ xlog(L_ERROR, "Failed to delete: no memory"); -+ goto out; -+ } -+ -+ f = fopen(path, "r"); -+ if (f == NULL) { -+ xlog(L_ERROR, "Failed to delete: " -+ "could not open original file %s: %m", path); -+ goto out; -+ } -+ -+ /* -+ * Walk the records in the file, and copy the non-matching -+ * ones to our output buffer. -+ */ -+ next = outbuf; -+ while (fgets(line, (int)sizeof(line), f) != NULL) { -+ struct sockaddr_in sin; -+ struct mon m; -+ size_t len; -+ -+ if (!nsm_parse_line(line, &sin, &m)) { -+ xlog(L_ERROR, "Failed to delete: " -+ "could not parse original file %s", path); -+ (void)fclose(f); -+ goto out; -+ } -+ -+ if (strcmp(mon_name, m.mon_id.mon_name) == 0 && -+ strcmp(my_name, m.mon_id.my_id.my_name) == 0) -+ continue; -+ -+ /* nsm_parse_line destroys the contents of line[], so -+ * reconstruct the copy in our output buffer. */ -+ len = nsm_create_monitor_record(next, remaining, -+ (struct sockaddr *)(char *)&sin, &m); -+ if (len == 0) { -+ xlog(L_ERROR, "Failed to delete: " -+ "could not construct output record"); -+ (void)fclose(f); -+ goto out; -+ } -+ next += len; -+ remaining -= len; -+ } -+ -+ (void)fclose(f); -+ -+ /* -+ * If nothing was copied when we're done, then unlink the file. -+ * Otherwise, atomically update the contents of the file. -+ */ -+ if (next != outbuf) { -+ if (!nsm_atomic_write(path, outbuf, strlen(outbuf))) -+ xlog(L_ERROR, "Failed to delete: " -+ "could not write new file %s: %m", path); -+ } else { -+ if (unlink(path) == -1) -+ xlog(L_ERROR, "Failed to delete: " -+ "could not unlink file %s: %m", path); -+ } -+ -+out: -+ free(outbuf); -+ free(path); -+} -+ -+/** -+ * nsm_delete_monitored_host - delete on-disk record for monitored host -+ * @hostname: '\0'-terminated C string containing hostname of record to delete -+ * @mon_name: '\0'-terminated C string containing monname of record to delete -+ * @my_name: '\0'-terminated C string containing myname of record to delete -+ * -+ */ -+void -+nsm_delete_monitored_host(const char *hostname, const char *mon_name, -+ const char *my_name) -+{ -+ nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name); -+} -+ -+/** -+ * nsm_delete_notified_host - delete on-disk host record after notification -+ * @hostname: '\0'-terminated C string containing hostname of record to delete -+ * @mon_name: '\0'-terminated C string containing monname of record to delete -+ * @my_name: '\0'-terminated C string containing myname of record to delete -+ * -+ */ -+void -+nsm_delete_notified_host(const char *hostname, const char *mon_name, -+ const char *my_name) -+{ -+ nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name); -+} -diff --git a/support/nsm/rpc.c b/support/nsm/rpc.c -new file mode 100644 -index 0000000..4e5f40e ---- /dev/null -+++ b/support/nsm/rpc.c -@@ -0,0 +1,534 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+/* -+ * NSM for Linux. -+ * -+ * Instead of using ONC or TI RPC library calls, statd constructs -+ * RPC calls directly in socket buffers. This allows a single -+ * socket to be concurrently shared among several different RPC -+ * programs and versions using a simple RPC request dispatcher. -+ * -+ * This file contains the details of RPC header and call -+ * construction and reply parsing, and a method for creating a -+ * socket for use with these functions. -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include -+#endif /* HAVE_CONFIG_H */ -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#ifdef HAVE_LIBTIRPC -+#include -+#include -+#endif /* HAVE_LIBTIRPC */ -+ -+#include "xlog.h" -+#include "nfsrpc.h" -+#include "nsm.h" -+#include "sm_inter.h" -+ -+/* -+ * Returns a fresh XID appropriate for RPC over UDP -- never zero. -+ */ -+static uint32_t -+nsm_next_xid(void) -+{ -+ static uint32_t nsm_xid = 0; -+ struct timeval now; -+ -+ if (nsm_xid == 0) { -+ (void)gettimeofday(&now, NULL); -+ nsm_xid = (uint32_t)getpid() ^ -+ (uint32_t)now.tv_sec ^ (uint32_t)now.tv_usec; -+ } -+ -+ return nsm_xid++; -+} -+ -+/* -+ * Select a fresh XID and construct an RPC header in @mesg. -+ * Always use AUTH_NULL credentials and verifiers. -+ * -+ * Returns the new XID. -+ */ -+static uint32_t -+nsm_init_rpc_header(const rpcprog_t program, const rpcvers_t version, -+ const rpcproc_t procedure, struct rpc_msg *mesg) -+{ -+ struct call_body *cb = &mesg->rm_call; -+ uint32_t xid = nsm_next_xid(); -+ -+ memset(mesg, 0, sizeof(*mesg)); -+ -+ mesg->rm_xid = (unsigned long)xid; -+ mesg->rm_direction = CALL; -+ -+ cb->cb_rpcvers = RPC_MSG_VERSION; -+ cb->cb_prog = program; -+ cb->cb_vers = version; -+ cb->cb_proc = procedure; -+ -+ cb->cb_cred.oa_flavor = AUTH_NULL; -+ cb->cb_cred.oa_base = (caddr_t) NULL; -+ cb->cb_cred.oa_length = 0; -+ cb->cb_verf.oa_flavor = AUTH_NULL; -+ cb->cb_verf.oa_base = (caddr_t) NULL; -+ cb->cb_verf.oa_length = 0; -+ -+ return xid; -+} -+ -+/* -+ * Initialize the network send buffer and XDR memory for encoding. -+ */ -+static void -+nsm_init_xdrmem(char *msgbuf, const unsigned int msgbuflen, -+ XDR *xdrp) -+{ -+ memset(msgbuf, 0, (size_t)msgbuflen); -+ memset(xdrp, 0, sizeof(*xdrp)); -+ xdrmem_create(xdrp, msgbuf, msgbuflen, XDR_ENCODE); -+} -+ -+/* -+ * Send a completed RPC call on a socket. -+ * -+ * Returns true if all the bytes were sent successfully; otherwise -+ * false if any error occurred. -+ */ -+static _Bool -+nsm_rpc_sendto(const int sock, const struct sockaddr *sap, -+ const socklen_t salen, XDR *xdrs, void *buf) -+{ -+ const size_t buflen = (size_t)xdr_getpos(xdrs); -+ ssize_t err; -+ -+ err = sendto(sock, buf, buflen, 0, sap, salen); -+ if ((err < 0) || ((size_t)err != buflen)) { -+ xlog(L_ERROR, "%s: sendto failed: %m", __func__); -+ return false; -+ } -+ return true; -+} -+ -+/** -+ * nsm_xmit_getport - post a PMAP_GETPORT call on a socket descriptor -+ * @sock: datagram socket descriptor -+ * @sin: pointer to AF_INET socket address of server -+ * @program: RPC program number to query -+ * @version: RPC version number to query -+ * -+ * Send a PMAP_GETPORT call to the portmap daemon at @sin using -+ * socket descriptor @sock. This request queries the RPC program -+ * [program, version, IPPROTO_UDP]. -+ * -+ * NB: PMAP_GETPORT works only for IPv4 hosts. This implementation -+ * works only over UDP, and queries only UDP registrations. -+ * -+ * Returns the XID of the call, or zero if an error occurred. -+ */ -+uint32_t -+nsm_xmit_getport(const int sock, const struct sockaddr_in *sin, -+ const unsigned long program, -+ const unsigned long version) -+{ -+ char msgbuf[NSM_MAXMSGSIZE]; -+ struct sockaddr_in addr; -+ struct rpc_msg mesg; -+ _Bool sent = false; -+ struct pmap parms = { -+ .pm_prog = program, -+ .pm_vers = version, -+ .pm_prot = (unsigned long)IPPROTO_UDP, -+ }; -+ uint32_t xid; -+ XDR xdr; -+ -+ xlog(D_CALL, "Sending PMAP_GETPORT for %u, %u, udp", program, version); -+ -+ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); -+ xid = nsm_init_rpc_header(PMAPPROG, PMAPVERS, -+ (rpcproc_t)PMAPPROC_GETPORT, &mesg); -+ -+ addr = *sin; -+ addr.sin_port = htons(PMAPPORT); -+ -+ if (xdr_callmsg(&xdr, &mesg) == TRUE && -+ xdr_pmap(&xdr, &parms) == TRUE) -+ sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr, -+ (socklen_t)sizeof(addr), &xdr, msgbuf); -+ else -+ xlog(L_ERROR, "%s: can't encode PMAP_GETPORT call", __func__); -+ -+ xdr_destroy(&xdr); -+ -+ if (sent == false) -+ return 0; -+ return xid; -+} -+ -+/** -+ * nsm_xmit_getaddr - post an RPCB_GETADDR call on a socket descriptor -+ * @sock: datagram socket descriptor -+ * @sin: pointer to AF_INET6 socket address of server -+ * @program: RPC program number to query -+ * @version: RPC version number to query -+ * -+ * Send an RPCB_GETADDR call to the rpcbind daemon at @sap using -+ * socket descriptor @sock. This request queries the RPC program -+ * [program, version, "udp6"]. -+ * -+ * NB: RPCB_GETADDR works for both IPv4 and IPv6 hosts. This -+ * implementation works only over UDP and AF_INET6, and queries -+ * only "udp6" registrations. -+ * -+ * Returns the XID of the call, or zero if an error occurred. -+ */ -+#ifdef HAVE_LIBTIRPC -+uint32_t -+nsm_xmit_getaddr(const int sock, const struct sockaddr_in6 *sin6, -+ const rpcprog_t program, const rpcvers_t version) -+{ -+ char msgbuf[NSM_MAXMSGSIZE]; -+ struct sockaddr_in6 addr; -+ struct rpc_msg mesg; -+ _Bool sent = false; -+ struct rpcb parms = { -+ .r_prog = program, -+ .r_vers = version, -+ .r_netid = "udp6", -+ .r_owner = "", -+ }; -+ uint32_t xid; -+ XDR xdr; -+ -+ xlog(D_CALL, "Sending RPCB_GETADDR for %u, %u, udp6", program, version); -+ -+ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); -+ xid = nsm_init_rpc_header(RPCBPROG, RPCBVERS, -+ (rpcproc_t)RPCBPROC_GETADDR, &mesg); -+ -+ addr = *sin6; -+ addr.sin6_port = htons(PMAPPORT); -+ parms.r_addr = nfs_sockaddr2universal((struct sockaddr *)(char *)&addr); -+ if (parms.r_addr == NULL) { -+ xlog(L_ERROR, "%s: can't encode socket address", __func__); -+ return 0; -+ } -+ -+ if (xdr_callmsg(&xdr, &mesg) == TRUE && -+ xdr_rpcb(&xdr, &parms) == TRUE) -+ sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr, -+ (socklen_t)sizeof(addr), &xdr, msgbuf); -+ else -+ xlog(L_ERROR, "%s: can't encode RPCB_GETADDR call", __func__); -+ -+ xdr_destroy(&xdr); -+ free(parms.r_addr); -+ -+ if (sent == false) -+ return 0; -+ return xid; -+} -+#else /* !HAVE_LIBTIRPC */ -+uint32_t -+nsm_xmit_getaddr(const int sock __attribute__((unused)), -+ const struct sockaddr_in6 *sin6 __attribute__((unused)), -+ const rpcprog_t program __attribute__((unused)), -+ const rpcvers_t version __attribute__((unused))) -+{ -+ return 0; -+} -+#endif /* !HAVE_LIBTIRPC */ -+ -+/** -+ * nsm_xmit_rpcbind - post an rpcbind request -+ * @sock: datagram socket descriptor -+ * @sap: pointer to socket address of server -+ * @program: RPC program number to query -+ * @version: RPC version number to query -+ * -+ * Send an rpcbind query to the rpcbind daemon at @sap using -+ * socket descriptor @sock. -+ * -+ * NB: This implementation works only over UDP, but can query IPv4 or IPv6 -+ * hosts. It queries only UDP registrations. -+ * -+ * Returns the XID of the call, or zero if an error occurred. -+ */ -+uint32_t -+nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap, -+ const rpcprog_t program, const rpcvers_t version) -+{ -+ switch (sap->sa_family) { -+ case AF_INET: -+ return nsm_xmit_getport(sock, (const struct sockaddr_in *)sap, -+ program, version); -+ case AF_INET6: -+ return nsm_xmit_getaddr(sock, (const struct sockaddr_in6 *)sap, -+ program, version); -+ } -+ return 0; -+} -+ -+/** -+ * nsm_xmit_notify - post an NSMPROC_NOTIFY call on a socket descriptor -+ * @sock: datagram socket descriptor -+ * @sap: pointer to socket address of peer to notify (port already filled in) -+ * @salen: length of socket address -+ * @program: RPC program number to use -+ * @mon_name: mon_name of local peer (ie the rebooting system) -+ * @state: state of local peer -+ * -+ * Send an NSMPROC_NOTIFY call to the peer at @sap using socket descriptor @sock. -+ * This request notifies the peer that we have rebooted. -+ * -+ * NB: This implementation works only over UDP, but supports both AF_INET -+ * and AF_INET6. -+ * -+ * Returns the XID of the call, or zero if an error occurred. -+ */ -+uint32_t -+nsm_xmit_notify(const int sock, const struct sockaddr *sap, -+ const socklen_t salen, const rpcprog_t program, -+ const char *mon_name, const int state) -+{ -+ char msgbuf[NSM_MAXMSGSIZE]; -+ struct stat_chge state_change; -+ struct rpc_msg mesg; -+ _Bool sent = false; -+ uint32_t xid; -+ XDR xdr; -+ -+ state_change.mon_name = strdup(mon_name); -+ if (state_change.mon_name == NULL) { -+ xlog(L_ERROR, "%s: no memory", __func__); -+ return 0; -+ } -+ state_change.state = state; -+ -+ xlog(D_CALL, "Sending SM_NOTIFY for %s", mon_name); -+ -+ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); -+ xid = nsm_init_rpc_header(program, SM_VERS, SM_NOTIFY, &mesg); -+ -+ if (xdr_callmsg(&xdr, &mesg) == TRUE && -+ xdr_stat_chge(&xdr, &state_change) == TRUE) -+ sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf); -+ else -+ xlog(L_ERROR, "%s: can't encode NSMPROC_NOTIFY call", -+ __func__); -+ -+ xdr_destroy(&xdr); -+ free(state_change.mon_name); -+ -+ if (sent == false) -+ return 0; -+ return xid; -+} -+ -+/** -+ * nsm_xmit_nlmcall - post an unnamed call to local NLM on a socket descriptor -+ * @sock: datagram socket descriptor -+ * @sap: address/port of NLM service to contact -+ * @salen: size of @sap -+ * @m: callback data defining RPC call to make -+ * @state: state of rebooting host -+ * -+ * Send an unnamed call (previously requested via NSMPROC_MON) to the -+ * specified local UDP-based RPC service using socket descriptor @sock. -+ * -+ * NB: This implementation works only over UDP, but supports both AF_INET -+ * and AF_INET6. -+ * -+ * Returns the XID of the call, or zero if an error occurred. -+ */ -+uint32_t -+nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap, -+ const socklen_t salen, const struct mon *m, -+ const int state) -+{ -+ const struct my_id *id = &m->mon_id.my_id; -+ char msgbuf[NSM_MAXMSGSIZE]; -+ struct status new_status; -+ struct rpc_msg mesg; -+ _Bool sent = false; -+ uint32_t xid; -+ XDR xdr; -+ -+ xlog(D_CALL, "Sending NLM downcall for %s", m->mon_id.mon_name); -+ -+ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); -+ xid = nsm_init_rpc_header((rpcprog_t)id->my_prog, -+ (rpcvers_t)id->my_vers, -+ (rpcproc_t)id->my_proc, &mesg); -+ -+ new_status.mon_name = m->mon_id.mon_name; -+ new_status.state = state; -+ memcpy(&new_status.priv, &m->priv, sizeof(new_status.priv)); -+ -+ if (xdr_callmsg(&xdr, &mesg) == TRUE && -+ xdr_status(&xdr, &new_status) == TRUE) -+ sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf); -+ else -+ xlog(L_ERROR, "%s: can't encode NLM downcall", __func__); -+ -+ xdr_destroy(&xdr); -+ -+ if (sent == false) -+ return 0; -+ return xid; -+} -+ -+/** -+ * nsm_parse_reply - parse and validate the header in an RPC reply -+ * @xdrs: pointer to XDR -+ * -+ * Returns the XID of the reply, or zero if an error occurred. -+ */ -+uint32_t -+nsm_parse_reply(XDR *xdrs) -+{ -+ struct rpc_msg mesg = { -+ .rm_reply.rp_acpt.ar_results.proc = (xdrproc_t)xdr_void, -+ }; -+ uint32_t xid; -+ -+ if (xdr_replymsg(xdrs, &mesg) == FALSE) { -+ xlog(L_ERROR, "%s: can't decode RPC reply", __func__); -+ return 0; -+ } -+ xid = (uint32_t)mesg.rm_xid; -+ -+ if (mesg.rm_reply.rp_stat != MSG_ACCEPTED) { -+ xlog(L_ERROR, "%s: [0x%x] RPC status %d", -+ __func__, xid, mesg.rm_reply.rp_stat); -+ return 0; -+ } -+ -+ if (mesg.rm_reply.rp_acpt.ar_stat != SUCCESS) { -+ xlog(L_ERROR, "%s: [0x%x] RPC accept status %d", -+ __func__, xid, mesg.rm_reply.rp_acpt.ar_stat); -+ return 0; -+ } -+ -+ return xid; -+} -+ -+/** -+ * nsm_recv_getport - parse PMAP_GETPORT reply -+ * @xdrs: pointer to XDR -+ * -+ * Returns the port number from the RPC reply, or zero -+ * if an error occurred. -+ */ -+unsigned long -+nsm_recv_getport(XDR *xdrs) -+{ -+ unsigned long port = 0; -+ -+ if (xdr_u_long(xdrs, &port) == FALSE) -+ xlog(L_ERROR, "%s: can't decode pmap reply", -+ __func__); -+ if (port > UINT16_MAX) { -+ xlog(L_ERROR, "%s: bad port number", -+ __func__); -+ port = 0; -+ } -+ -+ xlog(D_CALL, "Received PMAP_GETPORT result: %lu", port); -+ return port; -+} -+ -+/** -+ * nsm_recv_getaddr - parse RPCB_GETADDR reply -+ * @xdrs: pointer to XDR -+ * -+ * Returns the port number from the RPC reply, or zero -+ * if an error occurred. -+ */ -+uint16_t -+nsm_recv_getaddr(XDR *xdrs) -+{ -+ char *uaddr = NULL; -+ int port; -+ -+ if (xdr_wrapstring(xdrs, &uaddr) == FALSE) -+ xlog(L_ERROR, "%s: can't decode rpcb reply", -+ __func__); -+ -+ if ((uaddr == NULL) || (uaddr[0] == '\0')) { -+ xlog(D_CALL, "Received RPCB_GETADDR result: " -+ "program not registered"); -+ return 0; -+ } -+ -+ port = nfs_universal2port(uaddr); -+ -+ xdr_free((xdrproc_t)xdr_wrapstring, (char *)&uaddr); -+ -+ if (port < 0 || port > UINT16_MAX) { -+ xlog(L_ERROR, "%s: bad port number", -+ __func__); -+ return 0; -+ } -+ -+ xlog(D_CALL, "Received RPCB_GETADDR result: %d", port); -+ return (uint16_t)port; -+} -+ -+/** -+ * nsm_recv_rpcbind - parse rpcbind reply -+ * @af: address family of reply -+ * @xdrs: pointer to XDR -+ * -+ * Returns the port number from the RPC reply, or zero -+ * if an error occurred. -+ */ -+uint16_t -+nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs) -+{ -+ switch (family) { -+ case AF_INET: -+ return (uint16_t)nsm_recv_getport(xdrs); -+ case AF_INET6: -+ return nsm_recv_getaddr(xdrs); -+ } -+ return 0; -+} -diff --git a/support/nsm/sm_inter.x b/support/nsm/sm_inter.x -new file mode 100644 -index 0000000..d8e0ad7 ---- /dev/null -+++ b/support/nsm/sm_inter.x -@@ -0,0 +1,131 @@ -+/* -+ * Copyright (C) 1986 Sun Microsystems, Inc. -+ * Modified by Jeffrey A. Uphoff, 1995, 1997-1999. -+ * Modified by Olaf Kirch, 1996. -+ * Modified by H.J. Lu, 1998. -+ * -+ * NSM for Linux. -+ */ -+ -+/* -+ * Copyright (c) 2009, Sun Microsystems, Inc. -+ * All rights reserved. -+ * -+ * Redistribution and use in source and binary forms, with or without -+ * modification, are permitted provided that the following conditions are met: -+ * - Redistributions of source code must retain the above copyright notice, -+ * this list of conditions and the following disclaimer. -+ * - Redistributions in binary form must reproduce the above copyright notice, -+ * this list of conditions and the following disclaimer in the documentation -+ * and/or other materials provided with the distribution. -+ * - Neither the name of Sun Microsystems, Inc. nor the names of its -+ * contributors may be used to endorse or promote products derived -+ * from this software without specific prior written permission. -+ * -+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -+ * POSSIBILITY OF SUCH DAMAGE. -+ */ -+ -+/* -+ * Status monitor protocol specification -+ */ -+ -+#ifdef RPC_CLNT -+%#include -+#endif -+ -+program SM_PROG { -+ version SM_VERS { -+ /* res_stat = stat_succ if status monitor agrees to monitor */ -+ /* res_stat = stat_fail if status monitor cannot monitor */ -+ /* if res_stat == stat_succ, state = state number of site sm_name */ -+ struct sm_stat_res SM_STAT(struct sm_name) = 1; -+ -+ /* res_stat = stat_succ if status monitor agrees to monitor */ -+ /* res_stat = stat_fail if status monitor cannot monitor */ -+ /* stat consists of state number of local site */ -+ struct sm_stat_res SM_MON(struct mon) = 2; -+ -+ /* stat consists of state number of local site */ -+ struct sm_stat SM_UNMON(struct mon_id) = 3; -+ -+ /* stat consists of state number of local site */ -+ struct sm_stat SM_UNMON_ALL(struct my_id) = 4; -+ -+ void SM_SIMU_CRASH(void) = 5; -+ -+ void SM_NOTIFY(struct stat_chge) = 6; -+ -+ } = 1; -+} = 100024; -+ -+const SM_MAXSTRLEN = 1024; -+const SM_PRIV_SIZE = 16; -+ -+struct sm_name { -+ string mon_name; -+}; -+ -+struct my_id { -+ string my_name; /* name of the site iniates the monitoring request*/ -+ int my_prog; /* rpc program # of the requesting process */ -+ int my_vers; /* rpc version # of the requesting process */ -+ int my_proc; /* rpc procedure # of the requesting process */ -+}; -+ -+struct mon_id { -+ string mon_name; /* name of the site to be monitored */ -+ struct my_id my_id; -+}; -+ -+ -+struct mon { -+ struct mon_id mon_id; -+ opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */ -+}; -+ -+struct stat_chge { -+ string mon_name; /* name of the site that had the state change */ -+ int state; -+}; -+ -+/* -+ * state # of status monitor monitonically increases each time -+ * status of the site changes: -+ * an even number (>= 0) indicates the site is down and -+ * an odd number (> 0) indicates the site is up; -+ */ -+struct sm_stat { -+ int state; /* state # of status monitor */ -+}; -+ -+enum res { -+ stat_succ = 0, /* status monitor agrees to monitor */ -+ stat_fail = 1 /* status monitor cannot monitor */ -+}; -+ -+struct sm_stat_res { -+ res res_stat; -+ int state; -+}; -+ -+/* -+ * structure of the status message sent back by the status monitor -+ * when monitor site status changes -+ */ -+struct status { -+ string mon_name; -+ int state; -+ opaque priv[SM_PRIV_SIZE]; /* stored private information */ -+}; -+ -+%#define SM_INTER_X -diff --git a/tests/Makefile.am b/tests/Makefile.am -new file mode 100644 -index 0000000..faa8197 ---- /dev/null -+++ b/tests/Makefile.am -@@ -0,0 +1,13 @@ -+## Process this file with automake to produce Makefile.in -+ -+check_PROGRAMS = statdb_dump -+statdb_dump_SOURCES = statdb_dump.c -+ -+statdb_dump_LDADD = ../support/nfs/libnfs.a \ -+ ../support/nsm/libnsm.a $(LIBCAP) -+ -+SUBDIRS = nsm_client -+ -+MAINTAINERCLEANFILES = Makefile.in -+ -+TESTS = t0001-statd-basic-mon-unmon.sh -diff --git a/tests/nsm_client/Makefile.am b/tests/nsm_client/Makefile.am -new file mode 100644 -index 0000000..4bf0a45 ---- /dev/null -+++ b/tests/nsm_client/Makefile.am -@@ -0,0 +1,45 @@ -+## Process this file with automake to produce Makefile.in -+ -+GENFILES_CLNT = nlm_sm_inter_clnt.c -+GENFILES_SVC = nlm_sm_inter_svc.c -+GENFILES_XDR = nlm_sm_inter_xdr.c -+GENFILES_H = nlm_sm_inter.h -+ -+GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) -+ -+ -+check_PROGRAMS = nsm_client -+nsm_client_SOURCES = $(GENFILES) nsm_client.c -+ -+BUILT_SOURCES = $(GENFILES) -+nsm_client_LDADD = ../../support/nfs/libnfs.a \ -+ ../../support/nsm/libnsm.a $(LIBCAP) -+ -+if CONFIG_RPCGEN -+RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen -+$(RPCGEN): -+ make -C ../../tools/rpcgen all -+else -+RPCGEN = @RPCGEN_PATH@ -+endif -+ -+$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -l -o $@ $< -+ -+$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -m -o $@ $< -+ -+$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -c -o $@ $< -+ -+$(GENFILES_H): %.h: %.x $(RPCGEN) -+ test -f $@ && rm -rf $@ || true -+ $(RPCGEN) -h -o $@ $< -+ -+MAINTAINERCLEANFILES = Makefile.in -+ -+CLEANFILES = $(GENFILES) -+ -diff --git a/tests/nsm_client/README b/tests/nsm_client/README -new file mode 100644 -index 0000000..85379dd ---- /dev/null -+++ b/tests/nsm_client/README -@@ -0,0 +1,12 @@ -+The nsm_client program is intended for testing statd. It has the ability -+to act as a synthetic NSM client for sending artificial NSM calls to any -+host you choose. -+ -+It also has an NLM simulator that implements the call that statd uses to -+communicate with lockd. The daemon simulator will start itself up, -+register as an NLM service and listen for "downcalls" from statd. When -+it gets one, it will log a message. -+ -+Note that lockd will need to be down when using the daemon simulator. It -+also does not implement the entire NLM protocol and is only really -+useful for testing statd's downcall. -diff --git a/tests/nsm_client/nlm_sm_inter.x b/tests/nsm_client/nlm_sm_inter.x -new file mode 100644 -index 0000000..95fa326 ---- /dev/null -+++ b/tests/nsm_client/nlm_sm_inter.x -@@ -0,0 +1,43 @@ -+/* -+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff -+ * Modified by Olaf Kirch, 1996. -+ * Modified by H.J. Lu, 1998. -+ * Modified by Jeff Layton, 2010. -+ * -+ * NLM similator for Linux -+ */ -+ -+#ifdef RPC_CLNT -+%#include -+#endif -+ -+/* -+ * statd rejects monitor registrations for any non-lockd services, so pretend -+ * to be lockd when testing. Furthermore, the only call we care about from -+ * statd is #16, which is the downcall to notify the kernel of a host's status -+ * change. -+ */ -+program NLM_SM_PROG { -+ /* version 3 of the NLM protocol */ -+ version NLM_SM_VERS3 { -+ void NLM_SM_NOTIFY(struct nlm_sm_notify) = 16; -+ } = 3; -+ -+ /* version 2 of NLM protocol */ -+ version NLM_SM_VERS4 { -+ void NLM_SM_NOTIFY(struct nlm_sm_notify) = 16; -+ } = 4; -+} = 100021; -+ -+const SM_MAXSTRLEN = 1024; -+const SM_PRIV_SIZE = 16; -+ -+/* -+ * structure of the status message sent back by the status monitor -+ * when monitor site status changes -+ */ -+struct nlm_sm_notify { -+ string mon_name; -+ int state; -+ opaque priv[SM_PRIV_SIZE]; /* stored private information */ -+}; -diff --git a/tests/nsm_client/nsm_client.c b/tests/nsm_client/nsm_client.c -new file mode 100644 -index 0000000..0d1159a ---- /dev/null -+++ b/tests/nsm_client/nsm_client.c -@@ -0,0 +1,465 @@ -+/* -+ * nsm_client.c -- synthetic client and lockd simulator for testing statd -+ * -+ * Copyright (C) 2010 Red Hat, Jeff Layton -+ * -+ * This program 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. -+ * -+ * This program 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 this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, -+ * Boston, MA 02110-1301, USA. -+ * -+ * Very loosely based on "simulator.c" in the statd directory. Original -+ * copyright for that program follows: -+ * -+ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include "config.h" -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "nfslib.h" -+#include "nfsrpc.h" -+#include "nsm.h" -+#include "sm_inter.h" -+#include "nlm_sm_inter.h" -+#include "sockaddr.h" -+#include "xcommon.h" -+ -+static void daemon_simulator(void); -+static void sim_killer(int sig); -+static int nsm_client_crash(char *); -+static int nsm_client_mon(char *, char *, char *, char *, int, int); -+static int nsm_client_stat(char *, char *); -+static int nsm_client_notify(char *, char *, char *); -+static int nsm_client_unmon(char *, char *, char *, int, int); -+static int nsm_client_unmon_all(char *, char *, int, int); -+ -+extern void nlm_sm_prog_4(struct svc_req *rqstp, register SVCXPRT *transp); -+extern void svc_exit(void); -+ -+/* -+ * default to 15 retransmit interval, which seems to be the default for -+ * UDP clients w/ legacy glibc RPC -+ */ -+static struct timeval retrans_interval = -+{ -+ .tv_sec = 15, -+}; -+ -+static struct option longopts[] = -+{ -+ { "help", 0, 0, 'h' }, -+ { "host", 0, 0, 'H' }, -+ { "name", 1, 0, 'n' }, -+ { "program", 1, 0, 'P' }, -+ { "version", 1, 0, 'v' }, -+ { NULL, 0, 0, 0 }, -+}; -+ -+static int -+usage(char *program) -+{ -+ printf("Usage:\n"); -+ printf("%s [options] [arg]...\n", program); -+ printf("where command is one of these with the specified args:\n"); -+ printf("crash\t\t\t\ttell host to simulate crash\n"); -+ printf("daemon\t\t\t\t\tstart up lockd daemon simulator\n"); -+ printf("notify \tsend a reboot notification to host\n"); -+ printf("stat \t\t\tget status of on host\n"); -+ printf("unmon_all\t\t\ttell host to unmon everything\n"); -+ printf("unmon \t\t\ttell host to unmon \n"); -+ printf("mon \t\ttell host to monitor with private \n"); -+ return 1; -+} -+ -+static int -+hex2bin(char *dst, size_t dstlen, char *src) -+{ -+ int i; -+ unsigned int tmp; -+ -+ for (i = 0; *src && i < dstlen; i++) { -+ if (sscanf(src, "%2x", &tmp) != 1) -+ return 0; -+ dst[i] = tmp; -+ src++; -+ if (!*src) -+ break; -+ src++; -+ } -+ -+ return 1; -+} -+ -+static void -+bin2hex(char *dst, char *src, size_t srclen) -+{ -+ int i; -+ -+ for (i = 0; i < srclen; i++) -+ dst += sprintf(dst, "%02x", 0xff & src[i]); -+} -+ -+int -+main(int argc, char **argv) -+{ -+ int arg, err = 0; -+ int remaining_args; -+ char my_name[NI_MAXHOST], host[NI_MAXHOST]; -+ char cookie[SM_PRIV_SIZE]; -+ int my_prog = NLM_SM_PROG; -+ int my_vers = NLM_SM_VERS4; -+ -+ my_name[0] = '\0'; -+ host[0] = '\0'; -+ -+ while ((arg = getopt_long(argc, argv, "hHn:P:v:", longopts, -+ NULL)) != EOF) { -+ switch (arg) { -+ case 'H': -+ strncpy(host, optarg, sizeof(host)); -+ case 'n': -+ strncpy(my_name, optarg, sizeof(my_name)); -+ case 'P': -+ my_prog = atoi(optarg); -+ case 'v': -+ my_vers = atoi(optarg); -+ } -+ } -+ -+ remaining_args = argc - optind; -+ if (remaining_args <= 0) -+ usage(argv[0]); -+ -+ if (!my_name[0]) -+ gethostname(my_name, sizeof(my_name)); -+ if (!host[0]) -+ strncpy(host, "127.0.0.1", sizeof(host)); -+ -+ if (!strcasecmp(argv[optind], "daemon")) { -+ daemon_simulator(); -+ } else if (!strcasecmp(argv[optind], "crash")) { -+ err = nsm_client_crash(host); -+ } else if (!strcasecmp(argv[optind], "stat")) { -+ if (remaining_args < 2) -+ usage(argv[0]); -+ err = nsm_client_stat(host, argv[optind + 2]); -+ } else if (!strcasecmp(argv[optind], "unmon_all")) { -+ err = nsm_client_unmon_all(host, my_name, my_prog, my_vers); -+ } else if (!strcasecmp(argv[optind], "unmon")) { -+ if (remaining_args < 2) -+ usage(argv[0]); -+ err = nsm_client_unmon(host, argv[optind + 1], my_name, my_prog, -+ my_vers); -+ } else if (!strcasecmp(argv[optind], "notify")) { -+ if (remaining_args < 2) -+ usage(argv[0]); -+ err = nsm_client_notify(host, argv[optind + 1], -+ argv[optind + 2]); -+ } else if (!strcasecmp(argv[optind], "mon")) { -+ if (remaining_args < 2) -+ usage(argv[0]); -+ -+ memset(cookie, '\0', SM_PRIV_SIZE); -+ if (!hex2bin(cookie, sizeof(cookie), argv[optind + 2])) { -+ fprintf(stderr, "SYS:%d\n", EINVAL); -+ printf("Unable to convert hex cookie %s to binary.\n", -+ argv[optind + 2]); -+ return 1; -+ } -+ -+ err = nsm_client_mon(host, argv[optind + 1], cookie, my_name, -+ my_prog, my_vers); -+ } else { -+ err = usage(argv[0]); -+ } -+ -+ return err; -+} -+ -+static CLIENT * -+nsm_client_get_rpcclient(const char *node) -+{ -+ unsigned short port; -+ struct addrinfo *ai; -+ struct addrinfo hints = { .ai_flags = AI_ADDRCONFIG }; -+ int err; -+ CLIENT *client = NULL; -+ -+#ifndef IPV6_ENABLED -+ hints.ai_family = AF_INET; -+#endif /* IPV6_ENABLED */ -+ -+ /* FIXME: allow support for providing port? */ -+ err = getaddrinfo(node, NULL, &hints, &ai); -+ if (err) { -+ fprintf(stderr, "EAI:%d\n", err); -+ if (err == EAI_SYSTEM) -+ fprintf(stderr, "SYS:%d\n", errno); -+ printf("Unable to translate host to address: %s\n", -+ err == EAI_SYSTEM ? strerror(errno) : -+ gai_strerror(err)); -+ return client; -+ } -+ -+ /* FIXME: allow for TCP too? */ -+ port = nfs_getport(ai->ai_addr, ai->ai_addrlen, SM_PROG, -+ SM_VERS, IPPROTO_UDP); -+ if (!port) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("Unable to determine port for service\n"); -+ goto out; -+ } -+ -+ nfs_set_port(ai->ai_addr, port); -+ -+ client = nfs_get_rpcclient(ai->ai_addr, ai->ai_addrlen, IPPROTO_UDP, -+ SM_PROG, SM_VERS, &retrans_interval); -+ if (!client) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("RPC client creation failed\n"); -+ } -+out: -+ freeaddrinfo(ai); -+ return client; -+} -+ -+static int -+nsm_client_mon(char *calling, char *monitoring, char *cookie, char *my_name, -+ int my_prog, int my_vers) -+{ -+ CLIENT *client; -+ sm_stat_res *result; -+ mon mon; -+ int err = 0; -+ -+ printf("Calling %s (as %s) to monitor %s\n", calling, my_name, -+ monitoring); -+ -+ if ((client = nsm_client_get_rpcclient(calling)) == NULL) -+ return 1; -+ -+ memcpy(mon.priv, cookie, SM_PRIV_SIZE); -+ mon.mon_id.my_id.my_name = my_name; -+ mon.mon_id.my_id.my_prog = my_prog; -+ mon.mon_id.my_id.my_vers = my_vers; -+ mon.mon_id.my_id.my_proc = NLM_SM_NOTIFY; -+ mon.mon_id.mon_name = monitoring; -+ -+ if (!(result = sm_mon_1(&mon, client))) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_mon_1")); -+ err = 1; -+ goto mon_out; -+ } -+ -+ printf("SM_MON request %s, state: %d\n", -+ result->res_stat == stat_succ ? "successful" : "failed", -+ result->state); -+ -+ if (result->res_stat != stat_succ) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ err = 1; -+ } -+ -+mon_out: -+ clnt_destroy(client); -+ return err; -+} -+ -+static int -+nsm_client_unmon(char *calling, char *unmonitoring, char *my_name, int my_prog, -+ int my_vers) -+{ -+ CLIENT *client; -+ sm_stat *result; -+ mon_id mon_id; -+ int err = 0; -+ -+ printf("Calling %s (as %s) to unmonitor %s\n", calling, my_name, -+ unmonitoring); -+ -+ if ((client = nsm_client_get_rpcclient(calling)) == NULL) -+ return 1; -+ -+ mon_id.my_id.my_name = my_name; -+ mon_id.my_id.my_prog = my_prog; -+ mon_id.my_id.my_vers = my_vers; -+ mon_id.my_id.my_proc = NLM_SM_NOTIFY; -+ mon_id.mon_name = unmonitoring; -+ -+ if (!(result = sm_unmon_1(&mon_id, client))) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_unmon_1")); -+ err = 1; -+ goto unmon_out; -+ } -+ -+ printf("SM_UNMON state: %d\n", result->state); -+ -+unmon_out: -+ clnt_destroy(client); -+ return err; -+} -+ -+static int -+nsm_client_unmon_all(char *calling, char *my_name, int my_prog, int my_vers) -+{ -+ CLIENT *client; -+ sm_stat *result; -+ my_id my_id; -+ int err = 0; -+ -+ printf("Calling %s (as %s) to unmonitor all hosts\n", calling, my_name); -+ -+ if ((client = nsm_client_get_rpcclient(calling)) == NULL) { -+ printf("RPC client creation failed\n"); -+ return 1; -+ } -+ -+ my_id.my_name = my_name; -+ my_id.my_prog = my_prog; -+ my_id.my_vers = my_vers; -+ my_id.my_proc = NLM_SM_NOTIFY; -+ -+ if (!(result = sm_unmon_all_1(&my_id, client))) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_unmon_all_1")); -+ err = 1; -+ goto unmon_all_out; -+ } -+ -+ printf("SM_UNMON_ALL state: %d\n", result->state); -+ -+unmon_all_out: -+ return err; -+} -+ -+static int -+nsm_client_crash(char *host) -+{ -+ CLIENT *client; -+ -+ if ((client = nsm_client_get_rpcclient(host)) == NULL) -+ return 1; -+ -+ if (!sm_simu_crash_1(NULL, client)) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_simu_crash_1")); -+ return 1; -+ } -+ -+ return 0; -+} -+ -+static int -+nsm_client_stat(char *calling, char *monitoring) -+{ -+ CLIENT *client; -+ sm_name checking; -+ sm_stat_res *result; -+ -+ if ((client = nsm_client_get_rpcclient(calling)) == NULL) -+ return 1; -+ -+ checking.mon_name = monitoring; -+ -+ if (!(result = sm_stat_1(&checking, client))) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_stat_1")); -+ return 1; -+ } -+ -+ if (result->res_stat != stat_succ) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("stat_fail from %s for %s, state: %d\n", calling, -+ monitoring, result->state); -+ return 1; -+ } -+ -+ printf("stat_succ from %s for %s, state: %d\n", calling, -+ monitoring, result->state); -+ -+ return 0; -+} -+ -+static int -+nsm_client_notify(char *calling, char *mon_name, char *statestr) -+{ -+ CLIENT *client; -+ -+ stat_chge stat_chge = { .mon_name = mon_name }; -+ -+ stat_chge.state = atoi(statestr); -+ -+ if ((client = nsm_client_get_rpcclient(calling)) == NULL) -+ return 1; -+ -+ if (!sm_notify_1(&stat_chge, client)) { -+ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); -+ printf("%s\n", clnt_sperror(client, "sm_notify_1")); -+ return 1; -+ } -+ -+ return 0; -+} -+ -+static void sim_killer(int sig) -+{ -+#ifdef HAVE_LIBTIRPC -+ (void) rpcb_unset(NLM_SM_PROG, NLM_SM_VERS4, NULL); -+#else -+ (void) pmap_unset(NLM_SM_PROG, NLM_SM_VERS4); -+#endif -+ exit(0); -+} -+ -+static void daemon_simulator(void) -+{ -+ signal(SIGHUP, sim_killer); -+ signal(SIGINT, sim_killer); -+ signal(SIGTERM, sim_killer); -+ /* FIXME: allow for different versions? */ -+ nfs_svc_create("nlmsim", NLM_SM_PROG, NLM_SM_VERS4, nlm_sm_prog_4, 0); -+ svc_run(); -+} -+ -+void *nlm_sm_notify_4_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp) -+{ -+ static char *result; -+ char priv[SM_PRIV_SIZE * 2 + 1]; -+ -+ bin2hex(priv, argp->priv, SM_PRIV_SIZE); -+ -+ printf("state=%d:mon_name=%s:private=%s\n", argp->state, -+ argp->mon_name, priv); -+ return (void *) &result; -+} -+ -+void *nlm_sm_notify_3_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp) -+{ -+ return nlm_sm_notify_4_svc(argp, rqstp); -+} -diff --git a/tests/statdb_dump.c b/tests/statdb_dump.c -new file mode 100644 -index 0000000..92d63f2 ---- /dev/null -+++ b/tests/statdb_dump.c -@@ -0,0 +1,99 @@ -+/* -+ * statdb_dump.c -- dump contents of statd's monitor DB -+ * -+ * Copyright (C) 2010 Red Hat, Jeff Layton -+ * -+ * This program 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. -+ * -+ * This program 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 this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, -+ * Boston, MA 02110-1301, USA. -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include "config.h" -+#endif -+ -+#include -+#include -+#include -+ -+#include "nsm.h" -+#include "xlog.h" -+ -+static char cookiebuf[(SM_PRIV_SIZE * 2) + 1]; -+static char addrbuf[INET6_ADDRSTRLEN + 1]; -+ -+static unsigned int -+dump_host(const char *hostname, const struct sockaddr *sa, const struct mon *m, -+ const time_t timestamp) -+{ -+ int ret; -+ const char *addr; -+ const struct sockaddr_in *sin; -+ const struct sockaddr_in6 *sin6; -+ -+ ret = nsm_priv_to_hex(m->priv, cookiebuf, sizeof(cookiebuf)); -+ if (!ret) { -+ xlog(L_ERROR, "Unable to convert cookie to hex string.\n"); -+ return ret; -+ } -+ -+ switch (sa->sa_family) { -+ case AF_INET: -+ sin = (struct sockaddr_in *)(char *)sa; -+ addr = inet_ntop(sa->sa_family, &sin->sin_addr.s_addr, addrbuf, -+ (socklen_t)sizeof(addrbuf)); -+ break; -+ case AF_INET6: -+ sin6 = (struct sockaddr_in6 *)(char *)sa; -+ addr = inet_ntop(sa->sa_family, &sin6->sin6_addr, addrbuf, -+ (socklen_t)sizeof(addrbuf)); -+ break; -+ default: -+ xlog(L_ERROR, "Unrecognized address family: %hu\n", -+ sa->sa_family); -+ return 0; -+ } -+ -+ if (addr == NULL) { -+ xlog(L_ERROR, "Unable to convert sockaddr to string: %s\n", -+ strerror(errno)); -+ return 0; -+ } -+ -+ /* -+ * Callers of this program should assume that in the future, extra -+ * fields may be added to the output. Anyone adding extra fields to -+ * the output should add them to the end of the line. -+ */ -+ printf("%s %s %s %s %s %d %d %d\n", -+ hostname, addr, cookiebuf, -+ m->mon_id.mon_name, -+ m->mon_id.my_id.my_name, -+ m->mon_id.my_id.my_prog, -+ m->mon_id.my_id.my_vers, -+ m->mon_id.my_id.my_proc); -+ -+ return 1; -+} -+ -+int -+main(int argc, char **argv) -+{ -+ xlog_syslog(0); -+ xlog_stderr(1); -+ xlog_open(argv[0]); -+ -+ nsm_load_monitor_list(dump_host); -+ return 0; -+} -diff --git a/tests/t0001-statd-basic-mon-unmon.sh b/tests/t0001-statd-basic-mon-unmon.sh -new file mode 100644 -index 0000000..00127fb ---- /dev/null -+++ b/tests/t0001-statd-basic-mon-unmon.sh -@@ -0,0 +1,58 @@ -+#!/bin/bash -+# -+# statd_basic_mon_unmon -- test basic mon/unmon functionality with statd -+# -+# Copyright (C) 2010 Red Hat, Jeff Layton -+# -+# This program 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. -+# -+# This program 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 this program; if not, write to the Free Software Foundation, Inc., -+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -+# -+ -+. ./test-lib.sh -+ -+# This test needs root privileges -+check_root -+ -+start_statd -+if [ $? -ne 0 ]; then -+ echo "FAIL: problem starting statd" -+ exit 1 -+fi -+ -+COOKIE=`echo $$ | md5sum | cut -d' ' -f1` -+MON_NAME=`hostname` -+ -+nsm_client mon $MON_NAME $COOKIE -+if [ $? -ne 0 ]; then -+ echo "FAIL: mon failed" -+ kill_statd -+ exit 1 -+fi -+ -+statdb_dump | grep $MON_NAME | grep -q $COOKIE -+if [ $? -ne 0 ]; then -+ echo "FAIL: monitor DB doesn't seem to contain entry" -+ kill_statd -+ exit 1 -+fi -+ -+nsm_client unmon $MON_NAME -+if [ $? -ne 0 ]; then -+ echo "FAIL: unmon failed" -+ kill_statd -+ exit 1 -+fi -+ -+kill_statd -+ -diff --git a/tests/test-lib.sh b/tests/test-lib.sh -new file mode 100644 -index 0000000..3d47264 ---- /dev/null -+++ b/tests/test-lib.sh -@@ -0,0 +1,60 @@ -+#!/bin/bash -+# -+# test-lib.sh -- library of functions for nfs-utils tests -+# -+# Copyright (C) 2010 Red Hat, Jeff Layton -+# -+# This program 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. -+# -+# This program 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 this program; if not, write to the Free Software Foundation, Inc., -+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -+# -+ -+# make sure $srcdir is set and sanity check it -+srcdir=${srcdir-.} -+if [ ! -d ${srcdir} ]; then -+ echo "***ERROR***: bad installation -- \$srcdir=${srcdir}" -+ exit 1 -+fi -+ -+export PATH=$PATH:${srcdir}:${srcdir}/nsm_client -+ -+# Some tests require root privileges. Check for them and skip the test (exit 77) -+# if the caller doesn't have them. -+check_root() { -+ if [ $EUID -ne 0 ]; then -+ echo "*** Skipping this test as it requires root privs ***" -+ exit 77 -+ fi -+} -+ -+# is lockd registered as a service? -+lockd_registered() { -+ rpcinfo -p | grep -q nlockmgr -+ return $? -+} -+ -+# start up statd -+start_statd() { -+ rpcinfo -u 127.0.0.1 status 1 &> /dev/null -+ if [ $? -eq 0 ]; then -+ echo "***ERROR***: statd is already running and should " -+ echo " be down when starting this test" -+ return 1 -+ fi -+ $srcdir/../utils/statd/statd --no-notify -+} -+ -+# shut down statd -+kill_statd() { -+ kill `cat /var/run/rpc.statd.pid` -+} -diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c -index 40a2b4d..bd37a5f 100644 ---- a/utils/gssd/gssd.c -+++ b/utils/gssd/gssd.c -@@ -56,7 +56,6 @@ - #include "krb5_util.h" - - char pipefs_dir[PATH_MAX] = GSSD_PIPEFS_DIR; --char pipefs_nfsdir[PATH_MAX] = GSSD_PIPEFS_DIR; - char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE; - char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR; - char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1]; -@@ -159,11 +158,6 @@ main(int argc, char *argv[]) - if (preferred_realm == NULL) - gssd_k5_get_default_realm(&preferred_realm); - -- snprintf(pipefs_nfsdir, sizeof(pipefs_nfsdir), "%s/%s", -- pipefs_dir, GSSD_SERVICE_NAME); -- if (pipefs_nfsdir[sizeof(pipefs_nfsdir)-1] != '\0') -- errx(1, "pipefs_nfsdir path name too long"); -- - if ((progname = strrchr(argv[0], '/'))) - progname++; - else -diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h -index 3c52f46..465c305 100644 ---- a/utils/gssd/gssd.h -+++ b/utils/gssd/gssd.h -@@ -60,7 +60,6 @@ enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY}; - - - extern char pipefs_dir[PATH_MAX]; --extern char pipefs_nfsdir[PATH_MAX]; - extern char keytabfile[PATH_MAX]; - extern char *ccachesearch[]; - extern int use_memcache; -@@ -83,13 +82,24 @@ struct clnt_info { - int krb5_poll_index; - int spkm3_fd; - int spkm3_poll_index; -+ int gssd_fd; -+ int gssd_poll_index; - struct sockaddr_storage addr; - }; - -+TAILQ_HEAD(topdirs_list_head, topdirs_info) topdirs_list; -+ -+struct topdirs_info { -+ TAILQ_ENTRY(topdirs_info) list; -+ char *dirname; -+ int fd; -+}; -+ - void init_client_list(void); - int update_client_list(void); - void handle_krb5_upcall(struct clnt_info *clp); - void handle_spkm3_upcall(struct clnt_info *clp); -+void handle_gssd_upcall(struct clnt_info *clp); - int gssd_acquire_cred(char *server_name); - void gssd_run(void); - -diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c -index 917b662..f1a68d3 100644 ---- a/utils/gssd/gssd_main_loop.c -+++ b/utils/gssd/gssd_main_loop.c -@@ -49,6 +49,7 @@ - #include - #include - #include -+#include - - #include "gssd.h" - #include "err_util.h" -@@ -73,6 +74,17 @@ scan_poll_results(int ret) - - for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) - { -+ i = clp->gssd_poll_index; -+ if (i >= 0 && pollarray[i].revents) { -+ if (pollarray[i].revents & POLLHUP) -+ dir_changed = 1; -+ if (pollarray[i].revents & POLLIN) -+ handle_gssd_upcall(clp); -+ pollarray[clp->gssd_poll_index].revents = 0; -+ ret--; -+ if (!ret) -+ break; -+ } - i = clp->krb5_poll_index; - if (i >= 0 && pollarray[i].revents) { - if (pollarray[i].revents & POLLHUP) -@@ -98,12 +110,85 @@ scan_poll_results(int ret) - } - }; - -+static int -+topdirs_add_entry(struct dirent *dent) -+{ -+ struct topdirs_info *tdi; -+ -+ tdi = calloc(sizeof(struct topdirs_info), 1); -+ if (tdi == NULL) { -+ printerr(0, "ERROR: Couldn't allocate struct topdirs_info\n"); -+ return -1; -+ } -+ tdi->dirname = malloc(PATH_MAX); -+ if (tdi->dirname == NULL) { -+ printerr(0, "ERROR: Couldn't allocate directory name\n"); -+ free(tdi); -+ return -1; -+ } -+ snprintf(tdi->dirname, PATH_MAX, "%s/%s", pipefs_dir, dent->d_name); -+ tdi->fd = open(tdi->dirname, O_RDONLY); -+ if (tdi->fd != -1) { -+ fcntl(tdi->fd, F_SETSIG, DNOTIFY_SIGNAL); -+ fcntl(tdi->fd, F_NOTIFY, -+ DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT); -+ } -+ -+ TAILQ_INSERT_HEAD(&topdirs_list, tdi, list); -+ return 0; -+} -+ -+static void -+topdirs_free_list(void) -+{ -+ struct topdirs_info *tdi; -+ -+ TAILQ_FOREACH(tdi, &topdirs_list, list) { -+ free(tdi->dirname); -+ if (tdi->fd != -1) -+ close(tdi->fd); -+ TAILQ_REMOVE(&topdirs_list, tdi, list); -+ free(tdi); -+ } -+} -+ -+static int -+topdirs_init_list(void) -+{ -+ DIR *pipedir; -+ struct dirent *dent; -+ int ret; -+ -+ TAILQ_INIT(&topdirs_list); -+ -+ pipedir = opendir(pipefs_dir); -+ if (pipedir == NULL) { -+ printerr(0, "ERROR: could not open rpc_pipefs directory '%s': " -+ "%s\n", pipefs_dir, strerror(errno)); -+ return -1; -+ } -+ for (dent = readdir(pipedir); dent != NULL; dent = readdir(pipedir)) { -+ if (dent->d_type != DT_DIR || -+ strcmp(dent->d_name, ".") == 0 || -+ strcmp(dent->d_name, "..") == 0) { -+ continue; -+ } -+ ret = topdirs_add_entry(dent); -+ if (ret) -+ goto out_err; -+ } -+ closedir(pipedir); -+ return 0; -+out_err: -+ topdirs_free_list(); -+ return -1; -+} -+ - void - gssd_run() - { - int ret; - struct sigaction dn_act; -- int fd; - sigset_t set; - - /* Taken from linux/Documentation/dnotify.txt: */ -@@ -117,13 +202,8 @@ gssd_run() - sigaddset(&set, DNOTIFY_SIGNAL); - sigprocmask(SIG_UNBLOCK, &set, NULL); - -- if ((fd = open(pipefs_nfsdir, O_RDONLY)) == -1) { -- printerr(0, "ERROR: failed to open %s: %s\n", -- pipefs_nfsdir, strerror(errno)); -- exit(1); -- } -- fcntl(fd, F_SETSIG, DNOTIFY_SIGNAL); -- fcntl(fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT); -+ if (topdirs_init_list() != 0) -+ return; - - init_client_list(); - -@@ -132,8 +212,7 @@ gssd_run() - while (dir_changed) { - dir_changed = 0; - if (update_client_list()) { -- printerr(0, "ERROR: couldn't update " -- "client list\n"); -+ /* Error msg is already printed */ - exit(1); - } - } -@@ -151,6 +230,7 @@ gssd_run() - scan_poll_results(ret); - } - } -- close(fd); -+ topdirs_free_list(); -+ - return; - } -diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c -index 37e2aa5..be4fb11 100644 ---- a/utils/gssd/gssd_proc.c -+++ b/utils/gssd/gssd_proc.c -@@ -73,6 +73,7 @@ - #include "krb5_util.h" - #include "context.h" - #include "nfsrpc.h" -+#include "nfslib.h" - - /* - * pollarray: -@@ -83,20 +84,22 @@ - * linked list of struct clnt_info which associates a clntXXX directory - * with an index into pollarray[], and other basic data about that client. - * -- * Directory structure: created by the kernel nfs client -- * {pipefs_nfsdir}/clntXX : one per rpc_clnt struct in the kernel -- * {pipefs_nfsdir}/clntXX/krb5 : read uid for which kernel wants -+ * Directory structure: created by the kernel -+ * {rpc_pipefs}/{dir}/clntXX : one per rpc_clnt struct in the kernel -+ * {rpc_pipefs}/{dir}/clntXX/krb5 : read uid for which kernel wants - * a context, write the resulting context -- * {pipefs_nfsdir}/clntXX/info : stores info such as server name -+ * {rpc_pipefs}/{dir}/clntXX/info : stores info such as server name -+ * {rpc_pipefs}/{dir}/clntXX/gssd : pipe for all gss mechanisms using -+ * a text-based string of parameters - * - * Algorithm: -- * Poll all {pipefs_nfsdir}/clntXX/krb5 files. When ready, data read -- * is a uid; performs rpcsec_gss context initialization protocol to -+ * Poll all {rpc_pipefs}/{dir}/clntXX/YYYY files. When data is ready, -+ * read and process; performs rpcsec_gss context initialization protocol to - * get a cred for that user. Writes result to corresponding krb5 file - * in a form the kernel code will understand. - * In addition, we make sure we are notified whenever anything is -- * created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories, -- * and rescan the whole {pipefs_nfsdir} when this happens. -+ * created or destroyed in {rpc_pipefs} or in any of the clntXX directories, -+ * and rescan the whole {rpc_pipefs} when this happens. - */ - - struct pollfd * pollarray; -@@ -105,7 +108,7 @@ int pollsize; /* the size of pollaray (in pollfd's) */ - - /* - * convert a presentation address string to a sockaddr_storage struct. Returns -- * true on success and false on failure. -+ * true on success or false on failure. - * - * Note that we do not populate the sin6_scope_id field here for IPv6 addrs. - * gssd nececessarily relies on hostname resolution and DNS AAAA records -@@ -117,26 +120,43 @@ int pollsize; /* the size of pollaray (in pollfd's) */ - * not really feasible at present. - */ - static int --addrstr_to_sockaddr(struct sockaddr *sa, const char *addr, const int port) -+addrstr_to_sockaddr(struct sockaddr *sa, const char *node, const char *port) - { -- struct sockaddr_in *s4 = (struct sockaddr_in *) sa; --#ifdef IPV6_SUPPORTED -- struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) sa; --#endif /* IPV6_SUPPORTED */ -+ int rc; -+ struct addrinfo *res; -+ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV }; - -- if (inet_pton(AF_INET, addr, &s4->sin_addr)) { -- s4->sin_family = AF_INET; -- s4->sin_port = htons(port); --#ifdef IPV6_SUPPORTED -- } else if (inet_pton(AF_INET6, addr, &s6->sin6_addr)) { -- s6->sin6_family = AF_INET6; -- s6->sin6_port = htons(port); -+#ifndef IPV6_SUPPORTED -+ hints.ai_family = AF_INET; - #endif /* IPV6_SUPPORTED */ -- } else { -- printerr(0, "ERROR: unable to convert %s to address\n", addr); -+ -+ rc = getaddrinfo(node, port, &hints, &res); -+ if (rc) { -+ printerr(0, "ERROR: unable to convert %s|%s to sockaddr: %s\n", -+ node, port, rc == EAI_SYSTEM ? strerror(errno) : -+ gai_strerror(rc)); - return 0; - } - -+#ifdef IPV6_SUPPORTED -+ /* -+ * getnameinfo ignores the scopeid. If the address turns out to have -+ * a non-zero scopeid, we can't use it -- the resolved host might be -+ * completely different from the one intended. -+ */ -+ if (res->ai_addr->sa_family == AF_INET6) { -+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr; -+ if (sin6->sin6_scope_id) { -+ printerr(0, "ERROR: address %s has non-zero " -+ "sin6_scope_id!\n", node); -+ freeaddrinfo(res); -+ return 0; -+ } -+ } -+#endif /* IPV6_SUPPORTED */ -+ -+ memcpy(sa, res->ai_addr, res->ai_addrlen); -+ freeaddrinfo(res); - return 1; - } - -@@ -194,11 +214,10 @@ read_service_info(char *info_file_name, char **servicename, char **servername, - char program[16]; - char version[16]; - char protoname[16]; -- char cb_port[128]; -+ char port[128]; - char *p; - int fd = -1; - int numfields; -- int port = 0; - - *servicename = *servername = *protocol = NULL; - -@@ -227,20 +246,22 @@ read_service_info(char *info_file_name, char **servicename, char **servername, - goto fail; - } - -- cb_port[0] = '\0'; -+ port[0] = '\0'; - if ((p = strstr(buf, "port")) != NULL) -- sscanf(p, "port: %127s\n", cb_port); -+ sscanf(p, "port: %127s\n", port); - - /* check service, program, and version */ -- if(memcmp(service, "nfs", 3)) return -1; -+ if (memcmp(service, "nfs", 3) != 0) -+ return -1; - *prog = atoi(program + 1); /* skip open paren */ - *vers = atoi(version); -- if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4))) -- goto fail; - -- if (cb_port[0] != '\0') { -- port = atoi(cb_port); -- if (port < 0 || port > 65535) -+ if (strlen(service) == 3 ) { -+ if ((*prog != 100003) || ((*vers != 2) && (*vers != 3) && -+ (*vers != 4))) -+ goto fail; -+ } else if (memcmp(service, "nfs4_cb", 7) == 0) { -+ if (*vers != 1) - goto fail; - } - -@@ -281,9 +302,13 @@ destroy_client(struct clnt_info *clp) - if (clp->spkm3_poll_index != -1) - memset(&pollarray[clp->spkm3_poll_index], 0, - sizeof(struct pollfd)); -+ if (clp->gssd_poll_index != -1) -+ memset(&pollarray[clp->gssd_poll_index], 0, -+ sizeof(struct pollfd)); - if (clp->dir_fd != -1) close(clp->dir_fd); - if (clp->krb5_fd != -1) close(clp->krb5_fd); - if (clp->spkm3_fd != -1) close(clp->spkm3_fd); -+ if (clp->gssd_fd != -1) close(clp->gssd_fd); - free(clp->dirname); - free(clp->servicename); - free(clp->servername); -@@ -303,8 +328,10 @@ insert_new_clnt(void) - } - clp->krb5_poll_index = -1; - clp->spkm3_poll_index = -1; -+ clp->gssd_poll_index = -1; - clp->krb5_fd = -1; - clp->spkm3_fd = -1; -+ clp->gssd_fd = -1; - clp->dir_fd = -1; - - TAILQ_INSERT_HEAD(&clnt_list, clp, list); -@@ -315,19 +342,43 @@ out: - static int - process_clnt_dir_files(struct clnt_info * clp) - { -- char kname[32]; -- char sname[32]; -- char info_file_name[32]; -+ char name[PATH_MAX]; -+ char gname[PATH_MAX]; -+ char info_file_name[PATH_MAX]; - -- if (clp->krb5_fd == -1) { -- snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname); -- clp->krb5_fd = open(kname, O_RDWR); -+ if (clp->gssd_fd == -1) { -+ snprintf(gname, sizeof(gname), "%s/gssd", clp->dirname); -+ clp->gssd_fd = open(gname, O_RDWR); - } -- if (clp->spkm3_fd == -1) { -- snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname); -- clp->spkm3_fd = open(sname, O_RDWR); -+ if (clp->gssd_fd == -1) { -+ if (clp->krb5_fd == -1) { -+ snprintf(name, sizeof(name), "%s/krb5", clp->dirname); -+ clp->krb5_fd = open(name, O_RDWR); -+ } -+ if (clp->spkm3_fd == -1) { -+ snprintf(name, sizeof(name), "%s/spkm3", clp->dirname); -+ clp->spkm3_fd = open(name, O_RDWR); -+ } -+ -+ /* If we opened a gss-specific pipe, let's try opening -+ * the new upcall pipe again. If we succeed, close -+ * gss-specific pipe(s). -+ */ -+ if (clp->krb5_fd != -1 || clp->spkm3_fd != -1) { -+ clp->gssd_fd = open(gname, O_RDWR); -+ if (clp->gssd_fd != -1) { -+ if (clp->krb5_fd != -1) -+ close(clp->krb5_fd); -+ clp->krb5_fd = -1; -+ if (clp->spkm3_fd != -1) -+ close(clp->spkm3_fd); -+ clp->spkm3_fd = -1; -+ } -+ } - } -- if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1)) -+ -+ if ((clp->krb5_fd == -1) && (clp->spkm3_fd == -1) && -+ (clp->gssd_fd == -1)) - return -1; - snprintf(info_file_name, sizeof(info_file_name), "%s/info", - clp->dirname); -@@ -362,6 +413,15 @@ get_poll_index(int *ind) - static int - insert_clnt_poll(struct clnt_info *clp) - { -+ if ((clp->gssd_fd != -1) && (clp->gssd_poll_index == -1)) { -+ if (get_poll_index(&clp->gssd_poll_index)) { -+ printerr(0, "ERROR: Too many gssd clients\n"); -+ return -1; -+ } -+ pollarray[clp->gssd_poll_index].fd = clp->gssd_fd; -+ pollarray[clp->gssd_poll_index].events |= POLLIN; -+ } -+ - if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) { - if (get_poll_index(&clp->krb5_poll_index)) { - printerr(0, "ERROR: Too many krb5 clients\n"); -@@ -384,17 +444,18 @@ insert_clnt_poll(struct clnt_info *clp) - } - - static void --process_clnt_dir(char *dir) -+process_clnt_dir(char *dir, char *pdir) - { - struct clnt_info * clp; - - if (!(clp = insert_new_clnt())) - goto fail_destroy_client; - -- if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) { -+ /* An extra for the '/', and an extra for the null */ -+ if (!(clp->dirname = calloc(strlen(dir) + strlen(pdir) + 2, 1))) { - goto fail_destroy_client; - } -- memcpy(clp->dirname, dir, strlen(dir)); -+ sprintf(clp->dirname, "%s/%s", pdir, dir); - if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) { - printerr(0, "ERROR: can't open %s: %s\n", - clp->dirname, strerror(errno)); -@@ -438,16 +499,24 @@ init_client_list(void) - * directories, since the DNOTIFY could have been in there. - */ - static void --update_old_clients(struct dirent **namelist, int size) -+update_old_clients(struct dirent **namelist, int size, char *pdir) - { - struct clnt_info *clp; - void *saveprev; - int i, stillhere; -+ char fname[PATH_MAX]; - - for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { -+ /* only compare entries in the global list that are from the -+ * same pipefs parent directory as "pdir" -+ */ -+ if (strncmp(clp->dirname, pdir, strlen(pdir)) != 0) continue; -+ - stillhere = 0; - for (i=0; i < size; i++) { -- if (!strcmp(clp->dirname, namelist[i]->d_name)) { -+ snprintf(fname, sizeof(fname), "%s/%s", -+ pdir, namelist[i]->d_name); -+ if (strcmp(clp->dirname, fname) == 0) { - stillhere = 1; - break; - } -@@ -468,48 +537,69 @@ update_old_clients(struct dirent **namelist, int size) - - /* Search for a client by directory name, return 1 if found, 0 otherwise */ - static int --find_client(char *dirname) -+find_client(char *dirname, char *pdir) - { - struct clnt_info *clp; -+ char fname[PATH_MAX]; - -- for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) -- if (!strcmp(clp->dirname, dirname)) -+ for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { -+ snprintf(fname, sizeof(fname), "%s/%s", pdir, dirname); -+ if (strcmp(clp->dirname, fname) == 0) - return 1; -+ } - return 0; - } - --/* Used to read (and re-read) list of clients, set up poll array. */ --int --update_client_list(void) -+static int -+process_pipedir(char *pipe_name) - { - struct dirent **namelist; - int i, j; - -- if (chdir(pipefs_nfsdir) < 0) { -+ if (chdir(pipe_name) < 0) { - printerr(0, "ERROR: can't chdir to %s: %s\n", -- pipefs_nfsdir, strerror(errno)); -+ pipe_name, strerror(errno)); - return -1; - } - -- j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort); -+ j = scandir(pipe_name, &namelist, NULL, alphasort); - if (j < 0) { - printerr(0, "ERROR: can't scandir %s: %s\n", -- pipefs_nfsdir, strerror(errno)); -+ pipe_name, strerror(errno)); - return -1; - } -- update_old_clients(namelist, j); -+ -+ update_old_clients(namelist, j, pipe_name); - for (i=0; i < j; i++) { - if (i < FD_ALLOC_BLOCK - && !strncmp(namelist[i]->d_name, "clnt", 4) -- && !find_client(namelist[i]->d_name)) -- process_clnt_dir(namelist[i]->d_name); -+ && !find_client(namelist[i]->d_name, pipe_name)) -+ process_clnt_dir(namelist[i]->d_name, pipe_name); - free(namelist[i]); - } - - free(namelist); -+ - return 0; - } - -+/* Used to read (and re-read) list of clients, set up poll array. */ -+int -+update_client_list(void) -+{ -+ int retval = -1; -+ struct topdirs_info *tdi; -+ -+ TAILQ_FOREACH(tdi, &topdirs_list, list) { -+ retval = process_pipedir(tdi->dirname); -+ if (retval) -+ printerr(1, "WARNING: error processing %s\n", -+ tdi->dirname); -+ -+ } -+ return retval; -+} -+ - static int - do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, - gss_buffer_desc *context_token) -@@ -798,15 +888,14 @@ int create_auth_rpc_client(struct clnt_info *clp, - goto out; - } - -- - /* - * this code uses the userland rpcsec gss library to create a krb5 - * context on behalf of the kernel - */ --void --handle_krb5_upcall(struct clnt_info *clp) -+static void -+process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname, -+ char *service) - { -- uid_t uid; - CLIENT *rpc_clnt = NULL; - AUTH *auth = NULL; - struct authgss_private_data pd; -@@ -815,23 +904,51 @@ handle_krb5_upcall(struct clnt_info *clp) - char **ccname; - char **dirname; - int create_resp = -1; -+ int err, downcall_err = -EACCES; - -- printerr(1, "handling krb5 upcall\n"); -+ printerr(1, "handling krb5 upcall (%s)\n", clp->dirname); - -+ if (tgtname) { -+ if (clp->servicename) { -+ free(clp->servicename); -+ clp->servicename = strdup(tgtname); -+ } -+ } - token.length = 0; - token.value = NULL; - memset(&pd, 0, sizeof(struct authgss_private_data)); - -- if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) { -- printerr(0, "WARNING: failed reading uid from krb5 " -- "upcall pipe: %s\n", strerror(errno)); -- goto out; -- } -- -- if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) { -+ /* -+ * If "service" is specified, then the kernel is indicating that -+ * we must use machine credentials for this request. (Regardless -+ * of the uid value or the setting of root_uses_machine_creds.) -+ * If the service value is "*", then any service name can be used. -+ * Otherwise, it specifies the service name that should be used. -+ * (For now, the values of service will only be "*" or "nfs".) -+ * -+ * Restricting gssd to use "nfs" service name is needed for when -+ * the NFS server is doing a callback to the NFS client. In this -+ * case, the NFS server has to authenticate itself as "nfs" -- -+ * even if there are other service keys such as "host" or "root" -+ * in the keytab. -+ * -+ * Another case when the kernel may specify the service attribute -+ * is when gssd is being asked to create the context for a -+ * SETCLIENT_ID operation. In this case, machine credentials -+ * must be used for the authentication. However, the service name -+ * used for this case is not important. -+ * -+ */ -+ printerr(2, "%s: service is '%s'\n", __func__, -+ service ? service : ""); -+ if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 && -+ service == NULL)) { - /* Tell krb5 gss which credentials cache to use */ - for (dirname = ccachesearch; *dirname != NULL; dirname++) { -- if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0) -+ err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname); -+ if (err == -EKEYEXPIRED) -+ downcall_err = -EKEYEXPIRED; -+ else if (!err) - create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, - AUTHTYPE_KRB5); - if (create_resp == 0) -@@ -839,12 +956,13 @@ handle_krb5_upcall(struct clnt_info *clp) - } - } - if (create_resp != 0) { -- if (uid == 0 && root_uses_machine_creds == 1) { -+ if (uid == 0 && (root_uses_machine_creds == 1 || -+ service != NULL)) { - int nocache = 0; - int success = 0; - do { - gssd_refresh_krb5_machine_credential(clp->servername, -- NULL, nocache); -+ NULL, service); - /* - * Get a list of credential cache names and try each - * of them until one works or we've tried them all -@@ -904,7 +1022,7 @@ handle_krb5_upcall(struct clnt_info *clp) - goto out_return_error; - } - -- do_downcall(clp->krb5_fd, uid, &pd, &token); -+ do_downcall(fd, uid, &pd, &token); - - out: - if (token.value) -@@ -920,7 +1038,7 @@ out: - return; - - out_return_error: -- do_error_downcall(clp->krb5_fd, uid, -1); -+ do_error_downcall(fd, uid, downcall_err); - goto out; - } - -@@ -928,26 +1046,19 @@ out_return_error: - * this code uses the userland rpcsec gss library to create an spkm3 - * context on behalf of the kernel - */ --void --handle_spkm3_upcall(struct clnt_info *clp) -+static void -+process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd) - { -- uid_t uid; - CLIENT *rpc_clnt = NULL; - AUTH *auth = NULL; - struct authgss_private_data pd; - gss_buffer_desc token; - -- printerr(2, "handling spkm3 upcall\n"); -+ printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname); - - token.length = 0; - token.value = NULL; - -- if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) { -- printerr(0, "WARNING: failed reading uid from spkm3 " -- "upcall pipe: %s\n", strerror(errno)); -- goto out; -- } -- - if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) { - printerr(0, "WARNING: Failed to create spkm3 context for " - "user with uid %d\n", uid); -@@ -968,7 +1079,7 @@ handle_spkm3_upcall(struct clnt_info *clp) - goto out_return_error; - } - -- do_downcall(clp->spkm3_fd, uid, &pd, &token); -+ do_downcall(fd, uid, &pd, &token); - - out: - if (token.value) -@@ -980,6 +1091,139 @@ out: - return; - - out_return_error: -- do_error_downcall(clp->spkm3_fd, uid, -1); -+ do_error_downcall(fd, uid, -1); - goto out; - } -+ -+void -+handle_krb5_upcall(struct clnt_info *clp) -+{ -+ uid_t uid; -+ -+ if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) { -+ printerr(0, "WARNING: failed reading uid from krb5 " -+ "upcall pipe: %s\n", strerror(errno)); -+ return; -+ } -+ -+ return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL); -+} -+ -+void -+handle_spkm3_upcall(struct clnt_info *clp) -+{ -+ uid_t uid; -+ -+ if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) { -+ printerr(0, "WARNING: failed reading uid from spkm3 " -+ "upcall pipe: %s\n", strerror(errno)); -+ return; -+ } -+ -+ return process_spkm3_upcall(clp, uid, clp->spkm3_fd); -+} -+ -+void -+handle_gssd_upcall(struct clnt_info *clp) -+{ -+ uid_t uid; -+ char *lbuf = NULL; -+ int lbuflen = 0; -+ char *p; -+ char *mech = NULL; -+ char *target = NULL; -+ char *service = NULL; -+ -+ printerr(1, "handling gssd upcall (%s)\n", clp->dirname); -+ -+ if (readline(clp->gssd_fd, &lbuf, &lbuflen) != 1) { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed reading request\n"); -+ return; -+ } -+ printerr(2, "%s: '%s'\n", __func__, lbuf); -+ -+ /* find the mechanism name */ -+ if ((p = strstr(lbuf, "mech=")) != NULL) { -+ mech = malloc(lbuflen); -+ if (!mech) -+ goto out; -+ if (sscanf(p, "mech=%s", mech) != 1) { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to parse gss mechanism name " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ } else { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to find gss mechanism name " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ -+ /* read uid */ -+ if ((p = strstr(lbuf, "uid=")) != NULL) { -+ if (sscanf(p, "uid=%d", &uid) != 1) { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to parse uid " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ } else { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to find uid " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ -+ /* read target name */ -+ if ((p = strstr(lbuf, "target=")) != NULL) { -+ target = malloc(lbuflen); -+ if (!target) -+ goto out; -+ if (sscanf(p, "target=%s", target) != 1) { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to parse target name " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ } -+ -+ /* -+ * read the service name -+ * -+ * The presence of attribute "service=" indicates that machine -+ * credentials should be used for this request. If the value -+ * is "*", then any machine credentials available can be used. -+ * If the value is anything else, then machine credentials for -+ * the specified service name (always "nfs" for now) should be -+ * used. -+ */ -+ if ((p = strstr(lbuf, "service=")) != NULL) { -+ service = malloc(lbuflen); -+ if (!service) -+ goto out; -+ if (sscanf(p, "service=%s", service) != 1) { -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "failed to parse service type " -+ "in upcall string '%s'\n", lbuf); -+ goto out; -+ } -+ } -+ -+ if (strcmp(mech, "krb5") == 0) -+ process_krb5_upcall(clp, uid, clp->gssd_fd, target, service); -+ else if (strcmp(mech, "spkm3") == 0) -+ process_spkm3_upcall(clp, uid, clp->gssd_fd); -+ else -+ printerr(0, "WARNING: handle_gssd_upcall: " -+ "received unknown gss mech '%s'\n", mech); -+ -+out: -+ free(lbuf); -+ free(mech); -+ free(target); -+ free(service); -+ return; -+} -+ -diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c -index 78e9775..1295f57 100644 ---- a/utils/gssd/krb5_util.c -+++ b/utils/gssd/krb5_util.c -@@ -170,9 +170,8 @@ select_krb5_ccache(const struct dirent *d) - * what we want. Otherwise, return zero and no dirent pointer. - * The caller is responsible for freeing the dirent if one is returned. - * -- * Returns: -- * 0 => could not find an existing entry -- * 1 => found an existing entry -+ * Returns 0 if a valid-looking entry was found and a non-zero error -+ * code otherwise. - */ - static int - gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) -@@ -186,7 +185,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) - char buf[1030]; - char *princname = NULL; - char *realm = NULL; -- int score, best_match_score = 0; -+ int score, best_match_score = 0, err = -EACCES; - - memset(&best_match_stat, 0, sizeof(best_match_stat)); - *d = NULL; -@@ -229,6 +228,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) - printerr(3, "CC file '%s' is expired or corrupt\n", - statname); - free(namelist[i]); -+ err = -EKEYEXPIRED; - continue; - } - -@@ -284,11 +284,12 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) - } - free(namelist); - } -- if (found) -- { -+ if (found) { - *d = best_match_dir; -+ return 0; - } -- return found; -+ -+ return err; - } - - -@@ -797,10 +798,9 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, - */ - static int - find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, -- krb5_keytab_entry *kte) -+ krb5_keytab_entry *kte, const char **svcnames) - { - krb5_error_code code; -- const char *svcnames[] = { "root", "nfs", "host", NULL }; - char **realmnames = NULL; - char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST]; - int i, j, retval; -@@ -1025,29 +1025,29 @@ err_cache: - * given only a UID. We really need more information, but we - * do the best we can. - * -- * Returns: -- * 0 => a ccache was found -- * 1 => no ccache was found -+ * Returns 0 if a ccache was found, and a non-zero error code otherwise. - */ - int - gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname) - { - char buf[MAX_NETOBJ_SZ]; - struct dirent *d; -+ int err; - - printerr(2, "getting credentials for client with uid %u for " - "server %s\n", uid, servername); - memset(buf, 0, sizeof(buf)); -- if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) { -- snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name); -- free(d); -- } -- else -- return 1; -+ err = gssd_find_existing_krb5_ccache(uid, dirname, &d); -+ if (err) -+ return err; -+ -+ snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name); -+ free(d); -+ - printerr(2, "using %s as credentials cache for client with " - "uid %u for server %s\n", buf, uid, servername); - gssd_set_krb5_ccache_name(buf); -- return 0; -+ return err; - } - - /* -@@ -1096,7 +1096,8 @@ gssd_get_krb5_machine_cred_list(char ***list) - for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) { - if (ple->ccname) { - /* Make sure cred is up-to-date before returning it */ -- retval = gssd_refresh_krb5_machine_credential(NULL, ple, 0); -+ retval = gssd_refresh_krb5_machine_credential(NULL, ple, -+ NULL); - if (retval) - continue; - if (i + 1 > listsize) { -@@ -1186,14 +1187,24 @@ gssd_destroy_krb5_machine_creds(void) - */ - int - gssd_refresh_krb5_machine_credential(char *hostname, -- struct gssd_k5_kt_princ *ple, int nocache) -+ struct gssd_k5_kt_princ *ple, -+ char *service) - { - krb5_error_code code = 0; - krb5_context context; - krb5_keytab kt = NULL;; - int retval = 0; - char *k5err = NULL; -+ const char *svcnames[4] = { "root", "nfs", "host", NULL }; - -+ /* -+ * If a specific service name was specified, use it. -+ * Otherwise, use the default list. -+ */ -+ if (service != NULL && strcmp(service, "*") != 0) { -+ svcnames[0] = service; -+ svcnames[1] = NULL; -+ } - if (hostname == NULL && ple == NULL) - return EINVAL; - -@@ -1216,7 +1227,7 @@ gssd_refresh_krb5_machine_credential(char *hostname, - if (ple == NULL) { - krb5_keytab_entry kte; - -- code = find_keytab_entry(context, kt, hostname, &kte); -+ code = find_keytab_entry(context, kt, hostname, &kte, svcnames); - if (code) { - printerr(0, "ERROR: %s: no usable keytab entry found " - "in keytab %s for connection with host %s\n", -@@ -1241,7 +1252,7 @@ gssd_refresh_krb5_machine_credential(char *hostname, - goto out; - } - } -- retval = gssd_get_single_krb5_cred(context, kt, ple, nocache); -+ retval = gssd_get_single_krb5_cred(context, kt, ple, 0); - out: - if (kt) - krb5_kt_close(context, kt); -diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h -index 4b6b281..4602cc3 100644 ---- a/utils/gssd/krb5_util.h -+++ b/utils/gssd/krb5_util.h -@@ -30,7 +30,8 @@ void gssd_free_krb5_machine_cred_list(char **list); - void gssd_setup_krb5_machine_gss_ccache(char *servername); - void gssd_destroy_krb5_machine_creds(void); - int gssd_refresh_krb5_machine_credential(char *hostname, -- struct gssd_k5_kt_princ *ple, int nocache); -+ struct gssd_k5_kt_princ *ple, -+ char *service); - char *gssd_k5_err_msg(krb5_context context, krb5_error_code code); - void gssd_k5_get_default_realm(char **def_realm); - -diff --git a/utils/gssd/svcgssd_proc.c b/utils/gssd/svcgssd_proc.c -index 6f2ba61..f1bfbef 100644 ---- a/utils/gssd/svcgssd_proc.c -+++ b/utils/gssd/svcgssd_proc.c -@@ -56,6 +56,7 @@ - #include "gss_util.h" - #include "err_util.h" - #include "context.h" -+#include "gss_oids.h" - - extern char * mech2file(gss_OID mech); - #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel" -@@ -73,7 +74,7 @@ struct svc_cred { - static int - do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, - gss_OID mech, gss_buffer_desc *context_token, -- int32_t endtime) -+ int32_t endtime, char *client_name) - { - FILE *f; - int i; -@@ -98,9 +99,10 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, - qword_printint(f, cred->cr_gid); - qword_printint(f, cred->cr_ngroups); - printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), " -- "uid: %d, gid: %d, num aux grps: %d:\n", -+ "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n", - fname, out_handle->length, context_token->length, - endtime, endtime - time(0), -+ client_name ? client_name : "", - cred->cr_uid, cred->cr_gid, cred->cr_ngroups); - for (i=0; i < cred->cr_ngroups; i++) { - qword_printint(f, cred->cr_groups[i]); -@@ -108,6 +110,8 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, - } - qword_print(f, fname); - qword_printhex(f, context_token->value, context_token->length); -+ if (client_name) -+ qword_print(f, client_name); - err = qword_eol(f); - if (err) { - printerr(1, "WARNING: error writing to downcall channel " -@@ -307,6 +311,75 @@ print_hexl(const char *description, unsigned char *cp, int length) - } - #endif - -+static int -+get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name) -+{ -+ char *p, *sname = NULL; -+ if (strchr(name->value, '@') && strchr(name->value, '/')) { -+ if ((sname = calloc(name->length, 1)) == NULL) { -+ printerr(0, "ERROR: get_krb5_hostbased_name failed " -+ "to allocate %d bytes\n", name->length); -+ return -1; -+ } -+ /* read in name and instance and replace '/' with '@' */ -+ sscanf(name->value, "%[^@]", sname); -+ p = strrchr(sname, '/'); -+ if (p == NULL) { /* The '@' preceeded the '/' */ -+ free(sname); -+ return -1; -+ } -+ *p = '@'; -+ } -+ *hostbased_name = sname; -+ return 0; -+} -+ -+static int -+get_hostbased_client_name(gss_name_t client_name, gss_OID mech, -+ char **hostbased_name) -+{ -+ u_int32_t maj_stat, min_stat; -+ gss_buffer_desc name; -+ gss_OID name_type = GSS_C_NO_OID; -+ char *cname; -+ int res = -1; -+ -+ *hostbased_name = NULL; /* preset in case we fail */ -+ -+ /* Get the client's gss authenticated name */ -+ maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); -+ if (maj_stat != GSS_S_COMPLETE) { -+ pgsserr("get_hostbased_client_name: gss_display_name", -+ maj_stat, min_stat, mech); -+ goto out_err; -+ } -+ if (name.length >= 0xffff) { /* don't overflow */ -+ printerr(0, "ERROR: get_hostbased_client_name: " -+ "received gss_name is too long (%d bytes)\n", -+ name.length); -+ goto out_rel_buf; -+ } -+ -+ /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to -+ * an NT_HOSTBASED_SERVICE name */ -+ if (g_OID_equal(&krb5oid, mech)) { -+ if (get_krb5_hostbased_name(&name, &cname) == 0) -+ *hostbased_name = cname; -+ } -+ -+ /* No support for SPKM3, just print a warning (for now) */ -+ if (g_OID_equal(&spkm3oid, mech)) { -+ printerr(1, "WARNING: get_hostbased_client_name: " -+ "no hostbased_name support for SPKM3\n"); -+ } -+ -+ res = 0; -+out_rel_buf: -+ gss_release_buffer(&min_stat, &name); -+out_err: -+ return res; -+} -+ - void - handle_nullreq(FILE *f) { - /* XXX initialize to a random integer to reduce chances of unnecessary -@@ -325,7 +398,7 @@ handle_nullreq(FILE *f) { - null_token = {.value = NULL}; - u_int32_t ret_flags; - gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; -- gss_name_t client_name; -+ gss_name_t client_name = NULL; - gss_OID mech = GSS_C_NO_OID; - u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0; - u_int32_t ignore_min_stat; -@@ -334,6 +407,7 @@ handle_nullreq(FILE *f) { - static int lbuflen = 0; - static char *cp; - int32_t ctx_endtime; -+ char *hostbased_name = NULL; - - printerr(1, "handling null request\n"); - -@@ -396,11 +470,13 @@ handle_nullreq(FILE *f) { - if (get_ids(client_name, mech, &cred)) { - /* get_ids() prints error msg */ - maj_stat = GSS_S_BAD_NAME; /* XXX ? */ -- gss_release_name(&ignore_min_stat, &client_name); - goto out_err; - } -- gss_release_name(&ignore_min_stat, &client_name); -- -+ if (get_hostbased_client_name(client_name, mech, &hostbased_name)) { -+ /* get_hostbased_client_name() prints error msg */ -+ maj_stat = GSS_S_BAD_NAME; /* XXX ? */ -+ goto out_err; -+ } - - /* Context complete. Pass handle_seq in out_handle to use - * for context lookup in the kernel. */ -@@ -419,7 +495,8 @@ handle_nullreq(FILE *f) { - /* We no longer need the gss context */ - gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); - -- do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime); -+ do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime, -+ hostbased_name); - continue_needed: - send_response(f, &in_handle, &in_tok, maj_stat, min_stat, - &out_handle, &out_tok); -@@ -428,6 +505,9 @@ out: - free(ctx_token.value); - if (out_tok.value != NULL) - gss_release_buffer(&ignore_min_stat, &out_tok); -+ if (client_name) -+ gss_release_name(&ignore_min_stat, &client_name); -+ free(hostbased_name); - printerr(1, "finished handling null request\n"); - return; - -diff --git a/utils/mount/mount.c b/utils/mount/mount.c -index 355df79..6b9e164 100644 ---- a/utils/mount/mount.c -+++ b/utils/mount/mount.c -@@ -593,6 +593,9 @@ int main(int argc, char *argv[]) - if (mnt_err == EX_BG) { - printf(_("%s: backgrounding \"%s\"\n"), - progname, spec); -+ printf(_("%s: mount options: \"%s\"\n"), -+ progname, extra_opts); -+ - fflush(stdout); - - /* -diff --git a/utils/mount/network.c b/utils/mount/network.c -index 7b1152a..92bba2d 100644 ---- a/utils/mount/network.c -+++ b/utils/mount/network.c -@@ -42,6 +42,7 @@ - #include - #include - -+#include "sockaddr.h" - #include "xcommon.h" - #include "mount.h" - #include "nls.h" -@@ -56,10 +57,6 @@ - #define CONNECT_TIMEOUT (20) - #define MOUNT_TIMEOUT (30) - --#if SIZEOF_SOCKLEN_T - 0 == 0 --#define socklen_t unsigned int --#endif -- - extern int nfs_mount_data_version; - extern char *progname; - extern int verbose; -@@ -193,8 +190,18 @@ static const unsigned int *nfs_default_proto() - } - #endif /* MOUNT_CONFIG */ - --static int nfs_lookup(const char *hostname, const sa_family_t family, -- struct sockaddr *sap, socklen_t *salen) -+/** -+ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address -+ * @hostname: pointer to C string containing DNS hostname to resolve -+ * @family: address family hint -+ * @sap: pointer to buffer to fill with socket address -+ * @len: IN: size of buffer to fill; OUT: size of socket address -+ * -+ * Returns 1 and places a socket address at @sap if successful; -+ * otherwise zero. -+ */ -+int nfs_lookup(const char *hostname, const sa_family_t family, -+ struct sockaddr *sap, socklen_t *salen) - { - struct addrinfo *gai_results; - struct addrinfo gai_hint = { -@@ -243,25 +250,6 @@ static int nfs_lookup(const char *hostname, const sa_family_t family, - } - - /** -- * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address -- * @hostname: pointer to C string containing DNS hostname to resolve -- * @sap: pointer to buffer to fill with socket address -- * @len: IN: size of buffer to fill; OUT: size of socket address -- * -- * Returns 1 and places a socket address at @sap if successful; -- * otherwise zero. -- */ --int nfs_name_to_address(const char *hostname, -- struct sockaddr *sap, socklen_t *salen) --{ --#ifdef IPV6_SUPPORTED -- return nfs_lookup(hostname, AF_UNSPEC, sap, salen); --#else /* !IPV6_SUPPORTED */ -- return nfs_lookup(hostname, AF_INET, sap, salen); --#endif /* !IPV6_SUPPORTED */ --} -- --/** - * nfs_gethostbyname - resolve a hostname to an IPv4 address - * @hostname: pointer to a C string containing a DNS hostname - * @sin: returns an IPv4 address -@@ -283,8 +271,8 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) - * OUT: length of converted socket address - * - * Convert a presentation format address string to a socket address. -- * Similar to nfs_name_to_address(), but the DNS query is squelched, -- * and won't make any noise if the getaddrinfo() call fails. -+ * Similar to nfs_lookup(), but the DNS query is squelched, and it -+ * won't make any noise if the getaddrinfo() call fails. - * - * Returns 1 and fills in @sap and @salen if successful; otherwise zero. - * -@@ -549,8 +537,8 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, - struct pmap *pmap, const unsigned long *versions, - const unsigned int *protos) - { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - const unsigned long prog = pmap->pm_prog, *p_vers; - const unsigned int prot = (u_int)pmap->pm_prot, *p_prot; - const u_short port = (u_short) pmap->pm_port; -@@ -840,8 +828,8 @@ int start_statd(void) - int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen, - const struct pmap *pmap, const dirpath *argp) - { -- struct sockaddr_storage address; -- struct sockaddr *saddr = (struct sockaddr *)&address; -+ union nfs_sockaddr address; -+ struct sockaddr *saddr = &address.sa; - struct pmap mnt_pmap = *pmap; - struct timeval timeout = { - .tv_sec = MOUNT_TIMEOUT >> 3, -@@ -1289,6 +1277,7 @@ nfs_nfs_version(struct mount_options *options, unsigned long *version) - int - nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) - { -+ sa_family_t family; - char *option; - - switch (po_rightmost(options, nfs_transport_opttbl)) { -@@ -1300,17 +1289,8 @@ nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) - return 1; - case 2: /* proto */ - option = po_get(options, "proto"); -- if (option) { -- if (strcmp(option, "tcp") == 0) { -- *protocol = IPPROTO_TCP; -- return 1; -- } -- if (strcmp(option, "udp") == 0) { -- *protocol = IPPROTO_UDP; -- return 1; -- } -- return 0; -- } -+ if (option != NULL) -+ return nfs_get_proto(option, &family, protocol); - } - - /* -@@ -1352,6 +1332,40 @@ nfs_nfs_port(struct mount_options *options, unsigned long *port) - } - - /* -+ * Returns TRUE and fills in @family if a valid NFS protocol option -+ * is found, or FALSE if the option was specified with an invalid value. -+ */ -+int nfs_nfs_proto_family(struct mount_options *options, -+ sa_family_t *family) -+{ -+ unsigned long protocol; -+ char *option; -+ -+#ifdef IPV6_SUPPORTED -+ *family = AF_UNSPEC; -+#else -+ *family = AF_INET; -+#endif -+ -+ switch (po_rightmost(options, nfs_transport_opttbl)) { -+ case 0: /* udp */ -+ return 1; -+ case 1: /* tcp */ -+ return 1; -+ case 2: /* proto */ -+ option = po_get(options, "proto"); -+ if (option != NULL) -+ return nfs_get_proto(option, family, &protocol); -+ } -+ -+ /* -+ * NFS transport protocol wasn't specified. Return the -+ * default address family. -+ */ -+ return 1; -+} -+ -+/* - * "mountprog" is supported only by the legacy mount command. The - * kernel mount client does not support this option. - * -@@ -1419,20 +1433,12 @@ nfs_mount_version(struct mount_options *options, unsigned long *version) - static int - nfs_mount_protocol(struct mount_options *options, unsigned long *protocol) - { -+ sa_family_t family; - char *option; - - option = po_get(options, "mountproto"); -- if (option) { -- if (strcmp(option, "tcp") == 0) { -- *protocol = IPPROTO_TCP; -- return 1; -- } -- if (strcmp(option, "udp") == 0) { -- *protocol = IPPROTO_UDP; -- return 1; -- } -- return 0; -- } -+ if (option != NULL) -+ return nfs_get_proto(option, &family, protocol); - - /* - * MNT transport protocol wasn't specified. If the NFS -@@ -1472,6 +1478,35 @@ nfs_mount_port(struct mount_options *options, unsigned long *port) - return 1; - } - -+/* -+ * Returns TRUE and fills in @family if a valid MNT protocol option -+ * is found, or FALSE if the option was specified with an invalid value. -+ */ -+int nfs_mount_proto_family(struct mount_options *options, -+ sa_family_t *family) -+{ -+ unsigned long protocol; -+ char *option; -+ -+#ifdef HAVE_LIBTIRPC -+ *family = AF_UNSPEC; -+#else -+ *family = AF_INET; -+#endif -+ -+ option = po_get(options, "mountproto"); -+ if (option != NULL) -+ return nfs_get_proto(option, family, &protocol); -+ -+ /* -+ * MNT transport protocol wasn't specified. If the NFS -+ * transport protocol was specified, derive the family -+ * from that; otherwise, return the default family for -+ * NFS. -+ */ -+ return nfs_nfs_proto_family(options, family); -+} -+ - /** - * nfs_options2pmap - set up pmap structs based on mount options - * @options: pointer to mount options -diff --git a/utils/mount/network.h b/utils/mount/network.h -index 7eb89b0..2a3a110 100644 ---- a/utils/mount/network.h -+++ b/utils/mount/network.h -@@ -44,7 +44,8 @@ int nfs_probe_bothports(const struct sockaddr *, const socklen_t, - struct pmap *, const struct sockaddr *, - const socklen_t, struct pmap *); - int nfs_gethostbyname(const char *, struct sockaddr_in *); --int nfs_name_to_address(const char *, struct sockaddr *, socklen_t *); -+int nfs_lookup(const char *hostname, const sa_family_t family, -+ struct sockaddr *sap, socklen_t *salen); - int nfs_string_to_sockaddr(const char *, struct sockaddr *, socklen_t *); - int nfs_present_sockaddr(const struct sockaddr *, - const socklen_t, char *, const size_t); -@@ -56,6 +57,8 @@ int clnt_ping(struct sockaddr_in *, const unsigned long, - - struct mount_options; - -+int nfs_nfs_proto_family(struct mount_options *options, sa_family_t *family); -+int nfs_mount_proto_family(struct mount_options *options, sa_family_t *family); - int nfs_nfs_version(struct mount_options *options, unsigned long *version); - int nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol); - -diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man -index 2299637..93bd642 100644 ---- a/utils/mount/nfs.man -+++ b/utils/mount/nfs.man -@@ -58,9 +58,17 @@ The server's hostname and export pathname - are separated by a colon, while - the mount options are separated by commas. The remaining fields - are separated by blanks or tabs. -+.P - The server's hostname can be an unqualified hostname, - a fully qualified domain name, --or a dotted quad IPv4 address. -+a dotted quad IPv4 address, or -+an IPv6 address enclosed in square brackets. -+Link-local and site-local IPv6 addresses must be accompanied by an -+interface identifier. -+See -+.BR ipv6 (7) -+for details on specifying raw IPv6 addresses. -+.P - The - .I fstype - field contains either "nfs" (for version 2 or version 3 NFS mounts) -@@ -470,32 +478,38 @@ for mounting the - .B nfs - file system type. - .TP 1.5i --.BI proto= transport --The transport the NFS client uses -+.BI proto= netid -+The transport protocol name and protocol family the NFS client uses - to transmit requests to the NFS server for this mount point. --.I transport --can be either --.B udp --or --.BR tcp . --Each transport uses different default -+If an NFS server has both an IPv4 and an IPv6 address, using a specific -+netid will force the use of IPv4 or IPv6 networking to communicate -+with that server. -+.IP -+If support for TI-RPC is built into the -+.B mount.nfs -+command, -+.I netid -+is a valid netid listed in -+.IR /etc/netconfig . -+Otherwise, -+.I netid -+is one of "tcp," "udp," or "rdma," and only IPv4 may be used. -+.IP -+Each transport protocol uses different default - .B retrans - and - .B timeo --settings; refer to the description of these two mount options for details. -+settings. -+Refer to the description of these two mount options for details. - .IP - In addition to controlling how the NFS client transmits requests to - the server, this mount option also controls how the - .BR mount (8) - command communicates with the server's rpcbind and mountd services. --Specifying --.B proto=tcp --forces all traffic from the -+Specifying a netid that uses TCP forces all traffic from the - .BR mount (8) - command and the NFS client to use TCP. --Specifying --.B proto=udp --forces all traffic types to use UDP. -+Specifying a netid that uses UDP forces all traffic types to use UDP. - .IP - If the - .B proto -@@ -548,15 +562,20 @@ or the server's mountd service is not available on the advertised port. - This option can be used when mounting an NFS server - through a firewall that blocks the rpcbind protocol. - .TP 1.5i --.BI mountproto= transport --The transport the NFS client uses -+.BI mountproto= netid -+The transport protocol name and protocol family the NFS client uses - to transmit requests to the NFS server's mountd service when performing - this mount request, and when later unmounting this mount point. --.I transport --can be either --.B udp --or --.BR tcp . -+.IP -+If support for TI-RPC is built into the -+.B mount.nfs -+command, -+.I netid -+is a valid netid listed in -+.IR /etc/netconfig . -+Otherwise, -+.I netid -+is one of "tcp" or "udp," and only IPv4 may be used. - .IP - This option can be used when mounting an NFS server - through a firewall that blocks a particular transport. -@@ -566,6 +585,7 @@ option, different transports for mountd requests and NFS requests - can be specified. - If the server's mountd service is not available via the specified - transport, the mount request fails. -+.IP - Refer to the TRANSPORT METHODS section for more on how the - .B mountproto - mount option interacts with the -@@ -709,17 +729,26 @@ for mounting the - .B nfs4 - file system type. - .TP 1.5i --.BI proto= transport --The transport the NFS client uses -+.BI proto= netid -+The transport protocol name and protocol family the NFS client uses - to transmit requests to the NFS server for this mount point. --.I transport --can be either --.B udp --or --.BR tcp . -+If an NFS server has both an IPv4 and an IPv6 address, using a specific -+netid will force the use of IPv4 or IPv6 networking to communicate -+with that server. -+.IP -+If support for TI-RPC is built into the -+.B mount.nfs -+command, -+.I netid -+is a valid netid listed in -+.IR /etc/netconfig . -+Otherwise, -+.I netid -+is one of "tcp" or "udp," and only IPv4 may be used. -+.IP - All NFS version 4 servers are required to support TCP, - so if this mount option is not specified, the NFS version 4 client --uses the TCP transport protocol. -+uses the TCP protocol. - Refer to the TRANSPORT METHODS section for more details. - .TP 1.5i - .BI port= n -@@ -779,7 +808,8 @@ The DATA AND METADATA COHERENCE section discusses - the behavior of this option in more detail. - .TP 1.5i - .BI clientaddr= n.n.n.n --Specifies a single IPv4 address (in dotted-quad form) -+Specifies a single IPv4 address (in dotted-quad form), -+or a non-link-local IPv6 address, - that the NFS client advertises to allow servers - to perform NFS version 4 callback requests against - files on this mount point. If the server is unable to -@@ -855,6 +885,14 @@ This example can be used to mount /usr over NFS. - .TA 2.5i +0.7i +0.7i +.7i - server:/export /usr nfs ro,nolock,nocto,actimeo=3600 0 0 - .FI -+.P -+This example shows how to mount an NFS server -+using a raw IPv6 link-local address. -+.P -+.NF -+.TA 2.5i +0.7i +0.7i +.7i -+ [fe80::215:c5ff:fb3e:e2b1%eth0]:/export /mnt nfs defaults 0 0 -+.FI - .SH "TRANSPORT METHODS" - NFS clients send requests to NFS servers via - Remote Procedure Calls, or -@@ -1498,6 +1536,8 @@ such as security negotiation, server referrals, and named attributes. - .BR mount.nfs (5), - .BR umount.nfs (5), - .BR exports (5), -+.BR netconfig (5), -+.BR ipv6 (7), - .BR nfsd (8), - .BR sm-notify (8), - .BR rpc.statd (8), -diff --git a/utils/mount/nfs4mount.c b/utils/mount/nfs4mount.c -index a2f318f..4a2fab7 100644 ---- a/utils/mount/nfs4mount.c -+++ b/utils/mount/nfs4mount.c -@@ -217,8 +217,11 @@ int nfs4mount(const char *spec, const char *node, int flags, - progname); - goto fail; - } -- snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s", -- old_opts, *old_opts ? "," : "", s); -+ if (running_bg) -+ strncpy(new_opts, old_opts, sizeof(new_opts)); -+ else -+ snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s", -+ old_opts, *old_opts ? "," : "", s); - *extra_opts = xstrdup(new_opts); - - /* Set default options. -@@ -434,15 +437,17 @@ int nfs4mount(const char *spec, const char *node, int flags, - break; - } - -- switch(rpc_createerr.cf_stat){ -- case RPC_TIMEDOUT: -- break; -- case RPC_SYSTEMERROR: -- if (errno == ETIMEDOUT) -+ if (!bg) { -+ switch(rpc_createerr.cf_stat) { -+ case RPC_TIMEDOUT: - break; -- default: -- rpc_mount_errors(hostname, 0, bg); -- goto fail; -+ case RPC_SYSTEMERROR: -+ if (errno == ETIMEDOUT) -+ break; -+ default: -+ rpc_mount_errors(hostname, 0, bg); -+ goto fail; -+ } - } - - if (bg && !running_bg) { -diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c -index 6355681..6b3356c 100644 ---- a/utils/mount/nfsmount.c -+++ b/utils/mount/nfsmount.c -@@ -170,7 +170,7 @@ parse_options(char *old_opts, struct nfs_mount_data *data, - struct pmap *mnt_pmap = &mnt_server->pmap; - struct pmap *nfs_pmap = &nfs_server->pmap; - int len; -- char *opt, *opteq, *p, *opt_b; -+ char *opt, *opteq, *p, *opt_b, *tmp_opts; - char *mounthost = NULL; - char cbuf[128]; - int open_quote = 0; -@@ -179,7 +179,8 @@ parse_options(char *old_opts, struct nfs_mount_data *data, - *bg = 0; - - len = strlen(new_opts); -- for (p=old_opts, opt_b=NULL; p && *p; p++) { -+ tmp_opts = xstrdup(old_opts); -+ for (p=tmp_opts, opt_b=NULL; p && *p; p++) { - if (!opt_b) - opt_b = p; /* begin of the option item */ - if (*p == '"') -@@ -457,10 +458,12 @@ parse_options(char *old_opts, struct nfs_mount_data *data, - goto out_bad; - *mnt_server->hostname = mounthost; - } -+ free(tmp_opts); - return 1; - bad_parameter: - nfs_error(_("%s: Bad nfs mount parameter: %s\n"), progname, opt); - out_bad: -+ free(tmp_opts); - return 0; - } - -diff --git a/utils/mount/nfsumount.c b/utils/mount/nfsumount.c -index c5505b1..9d798a2 100644 ---- a/utils/mount/nfsumount.c -+++ b/utils/mount/nfsumount.c -@@ -169,10 +169,15 @@ out: - static int nfs_umount_do_umnt(struct mount_options *options, - char **hostname, char **dirname) - { -- struct sockaddr_storage address; -- struct sockaddr *sap = (struct sockaddr *)&address; -+ union { -+ struct sockaddr sa; -+ struct sockaddr_in s4; -+ struct sockaddr_in6 s6; -+ } address; -+ struct sockaddr *sap = &address.sa; - socklen_t salen = sizeof(address); - 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); -@@ -189,8 +194,10 @@ static int nfs_umount_do_umnt(struct mount_options *options, - return EX_FAIL; - } - -- if (nfs_name_to_address(*hostname, sap, &salen) == 0) -- /* nfs_name_to_address reports any errors */ -+ if (!nfs_mount_proto_family(options, &family)) -+ return 0; -+ if (!nfs_lookup(*hostname, family, sap, &salen)) -+ /* nfs_lookup reports any errors */ - return EX_FAIL; - - if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0) -diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c -index b595649..57a4b32 100644 ---- a/utils/mount/stropts.c -+++ b/utils/mount/stropts.c -@@ -35,9 +35,11 @@ - #include - #include - -+#include "sockaddr.h" - #include "xcommon.h" - #include "mount.h" - #include "nls.h" -+#include "nfsrpc.h" - #include "mount_constants.h" - #include "stropts.h" - #include "error.h" -@@ -81,7 +83,7 @@ struct nfsmount_info { - *node, /* mounted-on dir */ - *type; /* "nfs" or "nfs4" */ - char *hostname; /* server's hostname */ -- struct sockaddr_storage address; /* server's address */ -+ union nfs_sockaddr address; - socklen_t salen; /* size of server's address */ - - struct mount_options *options; /* parsed mount options */ -@@ -204,9 +206,9 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap, - socklen_t salen, - struct mount_options *options) - { -- struct sockaddr_storage dummy; -- struct sockaddr *my_addr = (struct sockaddr *)&dummy; -- socklen_t my_len = sizeof(dummy); -+ union nfs_sockaddr address; -+ struct sockaddr *my_addr = &address.sa; -+ socklen_t my_len = sizeof(address); - - if (po_contains(options, "clientaddr") == PO_FOUND) - return 1; -@@ -218,21 +220,33 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap, - } - - /* -- * Resolve the 'mounthost=' hostname and append a new option using -- * the resulting address. -+ * Determine whether to append a 'mountaddr=' option. The option is needed if: -+ * -+ * 1. "mounthost=" was specified, or -+ * 2. The address families for proto= and mountproto= are different. - */ --static int nfs_fix_mounthost_option(struct mount_options *options) -+static int nfs_fix_mounthost_option(struct mount_options *options, -+ const char *nfs_hostname) - { -- struct sockaddr_storage dummy; -- struct sockaddr *sap = (struct sockaddr *)&dummy; -- socklen_t salen = sizeof(dummy); -+ union nfs_sockaddr address; -+ struct sockaddr *sap = &address.sa; -+ socklen_t salen = sizeof(address); -+ sa_family_t nfs_family, mnt_family; - char *mounthost; - -+ if (!nfs_nfs_proto_family(options, &nfs_family)) -+ return 0; -+ if (!nfs_mount_proto_family(options, &mnt_family)) -+ return 0; -+ - mounthost = po_get(options, "mounthost"); -- if (!mounthost) -- return 1; -+ if (mounthost == NULL) { -+ if (nfs_family == mnt_family) -+ return 1; -+ mounthost = (char *)nfs_hostname; -+ } - -- if (!nfs_name_to_address(mounthost, sap, &salen)) { -+ if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) { - nfs_error(_("%s: unable to determine mount server's address"), - progname); - return 0; -@@ -319,13 +333,16 @@ static int nfs_set_version(struct nfsmount_info *mi) - */ - static int nfs_validate_options(struct nfsmount_info *mi) - { -- struct sockaddr *sap = (struct sockaddr *)&mi->address; -+ struct sockaddr *sap = &mi->address.sa; -+ sa_family_t family; - - if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL)) - return 0; - -+ if (!nfs_nfs_proto_family(mi->options, &family)) -+ return 0; - mi->salen = sizeof(mi->address); -- if (!nfs_name_to_address(mi->hostname, sap, &mi->salen)) -+ if (!nfs_lookup(mi->hostname, family, sap, &mi->salen)) - return 0; - - if (!nfs_set_version(mi)) -@@ -371,10 +388,13 @@ static int nfs_extract_server_addresses(struct mount_options *options, - } - - static int nfs_construct_new_options(struct mount_options *options, -+ struct sockaddr *nfs_saddr, - struct pmap *nfs_pmap, -+ struct sockaddr *mnt_saddr, - struct pmap *mnt_pmap) - { - char new_option[64]; -+ char *netid; - - po_remove_all(options, "nfsprog"); - po_remove_all(options, "mountprog"); -@@ -391,20 +411,14 @@ static int nfs_construct_new_options(struct mount_options *options, - po_remove_all(options, "proto"); - po_remove_all(options, "udp"); - po_remove_all(options, "tcp"); -- switch (nfs_pmap->pm_prot) { -- case IPPROTO_TCP: -- snprintf(new_option, sizeof(new_option) - 1, -- "proto=tcp"); -- if (po_append(options, new_option) == PO_FAILED) -- return 0; -- break; -- case IPPROTO_UDP: -- snprintf(new_option, sizeof(new_option) - 1, -- "proto=udp"); -- if (po_append(options, new_option) == PO_FAILED) -- return 0; -- break; -- } -+ netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot); -+ if (netid == NULL) -+ return 0; -+ snprintf(new_option, sizeof(new_option) - 1, -+ "proto=%s", netid); -+ free(netid); -+ if (po_append(options, new_option) == PO_FAILED) -+ return 0; - - po_remove_all(options, "port"); - if (nfs_pmap->pm_port != NFS_PORT) { -@@ -421,20 +435,14 @@ static int nfs_construct_new_options(struct mount_options *options, - return 0; - - po_remove_all(options, "mountproto"); -- switch (mnt_pmap->pm_prot) { -- case IPPROTO_TCP: -- snprintf(new_option, sizeof(new_option) - 1, -- "mountproto=tcp"); -- if (po_append(options, new_option) == PO_FAILED) -- return 0; -- break; -- case IPPROTO_UDP: -- snprintf(new_option, sizeof(new_option) - 1, -- "mountproto=udp"); -- if (po_append(options, new_option) == PO_FAILED) -- return 0; -- break; -- } -+ netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot); -+ if (netid == NULL) -+ return 0; -+ snprintf(new_option, sizeof(new_option) - 1, -+ "mountproto=%s", netid); -+ free(netid); -+ if (po_append(options, new_option) == PO_FAILED) -+ return 0; - - po_remove_all(options, "mountport"); - snprintf(new_option, sizeof(new_option) - 1, -@@ -461,12 +469,12 @@ static int nfs_construct_new_options(struct mount_options *options, - static int - nfs_rewrite_pmap_mount_options(struct mount_options *options) - { -- struct sockaddr_storage nfs_address; -- struct sockaddr *nfs_saddr = (struct sockaddr *)&nfs_address; -+ union nfs_sockaddr nfs_address; -+ struct sockaddr *nfs_saddr = &nfs_address.sa; - socklen_t nfs_salen = sizeof(nfs_address); - struct pmap nfs_pmap; -- struct sockaddr_storage mnt_address; -- struct sockaddr *mnt_saddr = (struct sockaddr *)&mnt_address; -+ union nfs_sockaddr mnt_address; -+ struct sockaddr *mnt_saddr = &mnt_address.sa; - socklen_t mnt_salen = sizeof(mnt_address); - struct pmap mnt_pmap; - char *option; -@@ -510,7 +518,8 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options) - return 0; - } - -- if (!nfs_construct_new_options(options, &nfs_pmap, &mnt_pmap)) { -+ if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap, -+ mnt_saddr, &mnt_pmap)) { - errno = EINVAL; - return 0; - } -@@ -566,7 +575,7 @@ static int nfs_try_mount_v3v2(struct nfsmount_info *mi) - return result; - } - -- if (!nfs_fix_mounthost_option(options)) { -+ if (!nfs_fix_mounthost_option(options, mi->hostname)) { - errno = EINVAL; - goto out_fail; - } -@@ -601,7 +610,7 @@ out_fail: - */ - static int nfs_try_mount_v4(struct nfsmount_info *mi) - { -- struct sockaddr *sap = (struct sockaddr *)&mi->address; -+ struct sockaddr *sap = &mi->address.sa; - struct mount_options *options = po_dup(mi->options); - int result = 0; - -@@ -611,6 +620,18 @@ static int nfs_try_mount_v4(struct nfsmount_info *mi) - } - - if (mi->version == 0) { -+ if (po_contains(options, "mounthost") || -+ po_contains(options, "mountaddr") || -+ po_contains(options, "mountvers") || -+ po_contains(options, "mountproto")) { -+ /* -+ * Since these mountd options are set assume version 3 -+ * is wanted so error out with EPROTONOSUPPORT so the -+ * protocol negation starts with v3. -+ */ -+ errno = EPROTONOSUPPORT; -+ goto out_fail; -+ } - if (po_append(options, "vers=4") == PO_FAILED) { - errno = EINVAL; - goto out_fail; -@@ -656,9 +677,10 @@ static int nfs_try_mount(struct nfsmount_info *mi) - /* - * To deal with legacy Linux servers that don't - * automatically export a pseudo root, retry -- * ENOENT errors using version 3 -+ * ENOENT errors using version 3. And for -+ * Linux servers prior to 2.6.25, retry EPERM - */ -- if (errno != ENOENT) -+ if (errno != ENOENT && errno != EPERM) - break; - } - } -diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am -index 1e76cf8..eba81fc 100644 ---- a/utils/mountd/Makefile.am -+++ b/utils/mountd/Makefile.am -@@ -8,7 +8,7 @@ KPREFIX = @kprefix@ - sbin_PROGRAMS = mountd - - mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \ -- svc_run.c fsloc.c mountd.h -+ svc_run.c fsloc.c v4root.c mountd.h - mountd_LDADD = ../../support/export/libexport.a \ - ../../support/nfs/libnfs.a \ - ../../support/misc/libmisc.a \ -diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c -index 575f207..13eba70 100644 ---- a/utils/mountd/auth.c -+++ b/utils/mountd/auth.c -@@ -20,6 +20,7 @@ - #include "exportfs.h" - #include "mountd.h" - #include "xmalloc.h" -+#include "v4root.h" - - enum auth_error - { -@@ -102,75 +103,91 @@ auth_reload() - memset(&my_client, 0, sizeof(my_client)); - xtab_export_read(); - check_useipaddr(); -+ v4root_set(); -+ - ++counter; - - return counter; - } - -+static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp, enum auth_error *error) -+{ -+ char *n; -+ -+ if (use_ipaddr) -+ return strdup(inet_ntoa(caller->sin_addr)); -+ n = client_compose(hp); -+ *error = unknown_host; -+ if (!n) -+ return NULL; -+ if (*n) -+ return n; -+ free(n); -+ return strdup("DEFAULT"); -+} -+ -+/* return static nfs_export with details filled in */ - static nfs_export * --auth_authenticate_internal(char *what, struct sockaddr_in *caller, -+auth_authenticate_newcache(char *what, struct sockaddr_in *caller, - char *path, struct hostent *hp, - enum auth_error *error) - { -- nfs_export *exp; -+ nfs_export *exp; -+ int i; - -- if (new_cache) { -- int i; -- /* return static nfs_export with details filled in */ -- char *n; -- free(my_client.m_hostname); -- if (use_ipaddr) { -- my_client.m_hostname = -- strdup(inet_ntoa(caller->sin_addr)); -- } else { -- n = client_compose(hp); -- *error = unknown_host; -- if (!n) -- my_client.m_hostname = NULL; -- else if (*n) -- my_client.m_hostname = n; -- else { -- free(n); -- my_client.m_hostname = strdup("DEFAULT"); -- } -+ free(my_client.m_hostname); -+ -+ my_client.m_hostname = get_client_hostname(caller, hp, error); -+ if (my_client.m_hostname == NULL) -+ return NULL; -+ -+ my_client.m_naddr = 1; -+ my_client.m_addrlist[0] = caller->sin_addr; -+ my_exp.m_client = &my_client; -+ -+ exp = NULL; -+ for (i = 0; !exp && i < MCL_MAXTYPES; i++) -+ for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { -+ if (strcmp(path, exp->m_export.e_path)) -+ 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)) -+ continue; -+ break; - } -- if (my_client.m_hostname == NULL) -- return NULL; -- my_client.m_naddr = 1; -- my_client.m_addrlist[0] = caller->sin_addr; -- my_exp.m_client = &my_client; -- -- exp = NULL; -- for (i = 0; !exp && i < MCL_MAXTYPES; i++) -- for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { -- if (strcmp(path, exp->m_export.e_path)) -- 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)) -- continue; -- break; -- } -- *error = not_exported; -- if (!exp) -- return exp; -+ *error = not_exported; -+ if (!exp) -+ return NULL; - -- my_exp.m_export = exp->m_export; -- exp = &my_exp; -+ my_exp.m_export = exp->m_export; -+ exp = &my_exp; -+ return exp; -+} -+ -+static nfs_export * -+auth_authenticate_internal(char *what, struct sockaddr_in *caller, -+ char *path, struct hostent *hp, -+ enum auth_error *error) -+{ -+ nfs_export *exp; - -+ if (new_cache) { -+ exp = auth_authenticate_newcache(what, caller, path, hp, error); -+ if (!exp) -+ return NULL; - } else { - if (!(exp = export_find(hp, path))) { - *error = no_entry; - return NULL; - } -- if (!exp->m_mayexport) { -- *error = not_exported; -- return NULL; -- } -+ } -+ if (exp->m_export.e_flags & NFSEXP_V4ROOT) { -+ *error = no_entry; -+ return NULL; - } - if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && -- (ntohs(caller->sin_port) < IPPORT_RESERVED/2 || -- ntohs(caller->sin_port) >= IPPORT_RESERVED)) { -+ ntohs(caller->sin_port) >= IPPORT_RESERVED) { - *error = illegal_port; - return NULL; - } -diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c -index e4e2f22..d63e10a 100644 ---- a/utils/mountd/cache.c -+++ b/utils/mountd/cache.c -@@ -614,73 +614,54 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex - return qword_eol(f); - } - --void nfsd_export(FILE *f) -+static int is_subdirectory(char *subpath, char *path) - { -- /* requests are: -- * domain path -- * determine export options and return: -- * domain path expiry flags anonuid anongid fsid -- */ -- -- char *cp; -- int i; -- char *dom, *path; -- nfs_export *exp, *found = NULL; -- int found_type = 0; -- struct in_addr addr; -- struct hostent *he = NULL; -- -- -- if (readline(fileno(f), &lbuf, &lbuflen) != 1) -- return; -+ int l = strlen(path); - -- xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf); -+ return strcmp(subpath, path) == 0 -+ || (strncmp(subpath, path, l) == 0 && path[l] == '/'); -+} - -- cp = lbuf; -- dom = malloc(strlen(cp)); -- path = malloc(strlen(cp)); -+static int path_matches(nfs_export *exp, char *path) -+{ -+ if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) -+ return is_subdirectory(path, exp->m_export.e_path); -+ return strcmp(path, exp->m_export.e_path) == 0; -+} - -- if (!dom || !path) -- goto out; -+static int client_matches(nfs_export *exp, char *dom, struct hostent *he) -+{ -+ if (use_ipaddr) -+ return client_check(exp->m_client, he); -+ return client_member(dom, exp->m_client->m_hostname); -+} - -- if (qword_get(&cp, dom, strlen(lbuf)) <= 0) -- goto out; -- if (qword_get(&cp, path, strlen(lbuf)) <= 0) -- goto out; -+static int export_matches(nfs_export *exp, char *dom, char *path, struct hostent *he) -+{ -+ return path_matches(exp, path) && client_matches(exp, dom, he); -+} - -- auth_reload(); -+static nfs_export *lookup_export(char *dom, char *path, struct hostent *he) -+{ -+ nfs_export *exp; -+ nfs_export *found = NULL; -+ int found_type = 0; -+ int i; - -- /* now find flags for this export point in this domain */ - for (i=0 ; i < MCL_MAXTYPES; i++) { - for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { -- if (!use_ipaddr && !client_member(dom, exp->m_client->m_hostname)) -- continue; -- if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) { -- /* if path is a mountpoint below e_path, then OK */ -- int l = strlen(exp->m_export.e_path); -- if (strcmp(path, exp->m_export.e_path) == 0 || -- (strncmp(path, exp->m_export.e_path, l) == 0 && -- path[l] == '/' && -- is_mountpoint(path))) -- /* ok */; -- else -- continue; -- } else if (strcmp(path, exp->m_export.e_path) != 0) -+ if (!export_matches(exp, dom, path, he)) - continue; -- if (use_ipaddr) { -- if (he == NULL) { -- if (!inet_aton(dom, &addr)) -- goto out; -- he = client_resolve(addr); -- } -- if (!client_check(exp->m_client, he)) -- continue; -- } - if (!found) { - found = exp; - found_type = i; - continue; - } -+ -+ /* Always prefer non-V4ROOT mounts */ -+ if (found->m_export.e_flags & NFSEXP_V4ROOT) -+ continue; -+ - /* If one is a CROSSMOUNT, then prefer the longest path */ - if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) || - (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) && -@@ -703,6 +684,50 @@ void nfsd_export(FILE *f) - } - } - } -+ return found; -+} -+ -+void nfsd_export(FILE *f) -+{ -+ /* requests are: -+ * domain path -+ * determine export options and return: -+ * domain path expiry flags anonuid anongid fsid -+ */ -+ -+ char *cp; -+ char *dom, *path; -+ nfs_export *found = NULL; -+ struct in_addr addr; -+ struct hostent *he = NULL; -+ -+ -+ if (readline(fileno(f), &lbuf, &lbuflen) != 1) -+ return; -+ -+ xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf); -+ -+ cp = lbuf; -+ dom = malloc(strlen(cp)); -+ path = malloc(strlen(cp)); -+ -+ if (!dom || !path) -+ goto out; -+ -+ if (qword_get(&cp, dom, strlen(lbuf)) <= 0) -+ goto out; -+ if (qword_get(&cp, path, strlen(lbuf)) <= 0) -+ goto out; -+ -+ auth_reload(); -+ -+ if (use_ipaddr) { -+ if (!inet_aton(dom, &addr)) -+ goto out; -+ he = client_resolve(addr); -+ } -+ -+ found = lookup_export(dom, path, he); - - if (found) { - if (dump_to_cache(f, dom, path, &found->m_export) < 0) { -diff --git a/utils/mountd/mount_dispatch.c b/utils/mountd/mount_dispatch.c -index 199fcec..ba6981d 100644 ---- a/utils/mountd/mount_dispatch.c -+++ b/utils/mountd/mount_dispatch.c -@@ -70,12 +70,10 @@ mount_dispatch(struct svc_req *rqstp, SVCXPRT *transp) - { - union mountd_arguments argument; - union mountd_results result; --#ifdef HAVE_TCP_WRAPPER -- struct sockaddr_in *sin = nfs_getrpccaller_in(transp); - -+#ifdef HAVE_TCP_WRAPPER - /* remote host authorization check */ -- if (sin->sin_family == AF_INET && -- !check_default("mountd", sin, rqstp->rq_proc, MOUNTPROG)) { -+ if (!check_default("mountd", nfs_getrpccaller(transp), MOUNTPROG)) { - svcerr_auth (transp, AUTH_FAILED); - return; - } -diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c -index 888fd8c..a0a1f2d 100644 ---- a/utils/mountd/mountd.c -+++ b/utils/mountd/mountd.c -@@ -509,12 +509,89 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, - return fh; - } - -+static void remove_all_clients(exportnode *e) -+{ -+ struct groupnode *g, *ng; -+ -+ for (g = e->ex_groups; g; g = ng) { -+ ng = g->gr_next; -+ xfree(g->gr_name); -+ xfree(g); -+ } -+ e->ex_groups = NULL; -+} -+ -+static void free_exportlist(exports *elist) -+{ -+ struct exportnode *e, *ne; -+ -+ for (e = *elist; e != NULL; e = ne) { -+ ne = e->ex_next; -+ remove_all_clients(e); -+ xfree(e->ex_dir); -+ xfree(e); -+ } -+ *elist = NULL; -+} -+ -+static void prune_clients(nfs_export *exp, struct exportnode *e) -+{ -+ struct hostent *hp; -+ 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)) { -+ *cp = c->gr_next; -+ xfree(c->gr_name); -+ xfree(c); -+ xfree (hp); -+ continue; -+ } -+ xfree (hp); -+ } -+ cp = &(c->gr_next); -+ } -+} -+ -+static exportnode *lookup_or_create_elist_entry(exports *elist, nfs_export *exp) -+{ -+ exportnode *e; -+ -+ for (e = *elist; e != NULL; e = e->ex_next) { -+ if (!strcmp(exp->m_export.e_path, e->ex_dir)) -+ return e; -+ } -+ e = xmalloc(sizeof(*e)); -+ e->ex_next = *elist; -+ e->ex_groups = NULL; -+ e->ex_dir = xstrdup(exp->m_export.e_path); -+ *elist = e; -+ return e; -+} -+ -+static void insert_group(struct exportnode *e, char *newname) -+{ -+ struct groupnode *g; -+ -+ for (g = e->ex_groups; g; g = g->gr_next) -+ if (strcmp(g->gr_name, newname)) -+ return; -+ -+ g = xmalloc(sizeof(*g)); -+ g->gr_name = xstrdup(newname); -+ g->gr_next = e->ex_groups; -+ e->ex_groups = g; -+} -+ - static exports - get_exportlist(void) - { - static exports elist = NULL; -- struct exportnode *e, *ne; -- struct groupnode *g, *ng, *c, **cp; -+ struct exportnode *e; - nfs_export *exp; - int i; - static unsigned int ecounter; -@@ -526,77 +603,26 @@ get_exportlist(void) - - ecounter = acounter; - -- for (e = elist; e != NULL; e = ne) { -- ne = e->ex_next; -- for (g = e->ex_groups; g != NULL; g = ng) { -- ng = g->gr_next; -- xfree(g->gr_name); -- xfree(g); -- } -- xfree(e->ex_dir); -- xfree(e); -- } -- elist = NULL; -+ free_exportlist(&elist); - - for (i = 0; i < MCL_MAXTYPES; i++) { - for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { -- for (e = elist; e != NULL; e = e->ex_next) { -- if (!strcmp(exp->m_export.e_path, e->ex_dir)) -- break; -- } -- if (!e) { -- e = (struct exportnode *) xmalloc(sizeof(*e)); -- e->ex_next = elist; -- e->ex_groups = NULL; -- e->ex_dir = xstrdup(exp->m_export.e_path); -- elist = e; -- } -- -- /* We need to check if we should remove -- previous ones. */ -- if (i == MCL_ANONYMOUS && e->ex_groups) { -- for (g = e->ex_groups; g; g = ng) { -- ng = g->gr_next; -- xfree(g->gr_name); -- xfree(g); -- } -- e->ex_groups = NULL; -+ /* Don't show pseudo exports */ -+ if (exp->m_export.e_flags & NFSEXP_V4ROOT) - continue; -- } -- -- if (i != MCL_FQDN && e->ex_groups) { -- struct hostent *hp; -+ e = lookup_or_create_elist_entry(&elist, exp); - -- 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)) { -- *cp = c->gr_next; -- xfree(c->gr_name); -- xfree(c); -- xfree (hp); -+ /* exports to "*" absorb any others */ -+ if (i == MCL_ANONYMOUS && e->ex_groups) { -+ remove_all_clients(e); - continue; -- } -- xfree (hp); -- } -- cp = &(c->gr_next); -- } - } -+ /* non-FQDN's absorb FQDN's they contain: */ -+ if (i != MCL_FQDN && e->ex_groups) -+ prune_clients(exp, e); - -- if (exp->m_export.e_hostname [0] != '\0') { -- for (g = e->ex_groups; g; g = g->gr_next) -- if (strcmp (exp->m_export.e_hostname, -- g->gr_name) == 0) -- break; -- if (g) -- continue; -- g = (struct groupnode *) xmalloc(sizeof(*g)); -- g->gr_name = xstrdup(exp->m_export.e_hostname); -- g->gr_next = e->ex_groups; -- e->ex_groups = g; -- } -+ if (exp->m_export.e_hostname[0] != '\0') -+ insert_group(e, exp->m_export.e_hostname); - } - } - -diff --git a/utils/mountd/rmtab.c b/utils/mountd/rmtab.c -index c371f8d..b028529 100644 ---- a/utils/mountd/rmtab.c -+++ b/utils/mountd/rmtab.c -@@ -143,23 +143,16 @@ mountlist_del_all(struct sockaddr_in *sin) - return; - if (!(hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) { - xlog(L_ERROR, "can't get hostname of %s", inet_ntoa(addr)); -- xfunlock(lockid); -- return; -+ goto out_unlock; - } -- else -- hp = hostent_dup (hp); -+ hp = hostent_dup (hp); -+ -+ if (!setrmtabent("r")) -+ goto out_free; -+ -+ if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) -+ goto out_close; - -- if (!setrmtabent("r")) { -- xfunlock(lockid); -- free (hp); -- return; -- } -- if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) { -- endrmtabent(); -- xfunlock(lockid); -- free (hp); -- return; -- } - while ((rep = getrmtabent(1, NULL)) != NULL) { - if (strcmp(rep->r_client, hp->h_name) == 0 && - (exp = auth_authenticate("umountall", sin, rep->r_path))) -@@ -170,10 +163,13 @@ mountlist_del_all(struct sockaddr_in *sin) - xlog(L_ERROR, "couldn't rename %s to %s", - _PATH_RMTABTMP, _PATH_RMTAB); - } -- endrmtabent(); /* close & unlink */ - fendrmtabent(fp); -- xfunlock(lockid); -+out_close: -+ endrmtabent(); /* close & unlink */ -+out_free: - free (hp); -+out_unlock: -+ xfunlock(lockid); - } - - mountlist -diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c -new file mode 100644 -index 0000000..7fd6af3 ---- /dev/null -+++ b/utils/mountd/v4root.c -@@ -0,0 +1,196 @@ -+/* -+ * Copyright (C) 2009 Red Hat -+ * -+ * support/export/v4root.c -+ * -+ * Routines used to support NFSv4 pseudo roots -+ * -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "xlog.h" -+#include "exportfs.h" -+#include "nfslib.h" -+#include "misc.h" -+#include "v4root.h" -+ -+int v4root_needed; -+ -+static nfs_export pseudo_root = { -+ .m_next = NULL, -+ .m_client = NULL, -+ .m_export = { -+ .e_hostname = "*", -+ .e_path = "/", -+ .e_flags = NFSEXP_READONLY | NFSEXP_ROOTSQUASH -+ | NFSEXP_NOSUBTREECHECK | NFSEXP_FSID -+ | NFSEXP_V4ROOT, -+ .e_anonuid = 65534, -+ .e_anongid = 65534, -+ .e_squids = NULL, -+ .e_nsquids = 0, -+ .e_sqgids = NULL, -+ .e_nsqgids = 0, -+ .e_fsid = 0, -+ .e_mountpoint = NULL, -+ }, -+ .m_exported = 0, -+ .m_xtabent = 1, -+ .m_mayexport = 1, -+ .m_changed = 0, -+ .m_warned = 0, -+}; -+ -+void set_pseudofs_security(struct exportent *pseudo, struct exportent *source) -+{ -+ struct sec_entry *se; -+ int i; -+ -+ if (source->e_flags & NFSEXP_INSECURE_PORT) -+ pseudo->e_flags |= NFSEXP_INSECURE_PORT; -+ for (se = source->e_secinfo; se->flav; se++) { -+ struct sec_entry *new; -+ -+ i = secinfo_addflavor(se->flav, pseudo); -+ new = &pseudo->e_secinfo[i]; -+ -+ if (se->flags & NFSEXP_INSECURE_PORT) -+ new->flags |= NFSEXP_INSECURE_PORT; -+ } -+} -+ -+/* -+ * Create a pseudo export -+ */ -+static struct exportent * -+v4root_create(char *path, nfs_export *export) -+{ -+ nfs_export *exp; -+ struct exportent eep; -+ struct exportent *curexp = &export->m_export; -+ -+ dupexportent(&eep, &pseudo_root.m_export); -+ eep.e_hostname = strdup(curexp->e_hostname); -+ strncpy(eep.e_path, path, sizeof(eep.e_path)); -+ if (strcmp(path, "/") != 0) -+ eep.e_flags &= ~NFSEXP_FSID; -+ set_pseudofs_security(&eep, curexp); -+ exp = export_create(&eep, 0); -+ if (exp == NULL) -+ return NULL; -+ xlog(D_CALL, "v4root_create: path '%s'", exp->m_export.e_path); -+ return &exp->m_export; -+} -+ -+/* -+ * Make sure the kernel has pseudo root support. -+ */ -+static int -+v4root_support(void) -+{ -+ struct export_features *ef; -+ static int warned = 0; -+ -+ ef = get_export_features(); -+ -+ if (ef->flags & NFSEXP_V4ROOT) -+ return 1; -+ if (!warned) { -+ xlog(L_WARNING, "Kernel does not have pseudo root support."); -+ xlog(L_WARNING, "NFS v4 mounts will be disabled unless fsid=0"); -+ xlog(L_WARNING, "is specfied in /etc/exports file."); -+ warned++; -+ } -+ return 0; -+} -+ -+int pseudofs_update(char *hostname, char *path, nfs_export *source) -+{ -+ nfs_export *exp; -+ -+ exp = export_lookup(hostname, path, 0); -+ if (exp && !(exp->m_export.e_flags & NFSEXP_V4ROOT)) -+ return 0; -+ if (!exp) { -+ if (v4root_create(path, source) == NULL) { -+ xlog(L_WARNING, "v4root_set: Unable to create " -+ "pseudo export for '%s'", path); -+ return -ENOMEM; -+ } -+ return 0; -+ } -+ /* Update an existing V4ROOT export: */ -+ set_pseudofs_security(&exp->m_export, &source->m_export); -+ return 0; -+} -+ -+static int v4root_add_parents(nfs_export *exp) -+{ -+ char *hostname = exp->m_export.e_hostname; -+ char *path; -+ char *ptr; -+ -+ path = strdup(exp->m_export.e_path); -+ if (!path) -+ return -ENOMEM; -+ for (ptr = path + 1; ptr; ptr = strchr(ptr, '/')) { -+ int ret; -+ char saved; -+ -+ saved = *ptr; -+ *ptr = '\0'; -+ ret = pseudofs_update(hostname, path, exp); -+ if (ret) -+ return ret; -+ *ptr = saved; -+ ptr++; -+ } -+ free(path); -+ return 0; -+} -+ -+/* -+ * Create pseudo exports by running through the real export -+ * looking at the components of the path that make up the export. -+ * Those path components, if not exported, will become pseudo -+ * exports allowing them to be found when the kernel does an upcall -+ * looking for components of the v4 mount. -+ */ -+void -+v4root_set() -+{ -+ nfs_export *exp; -+ int i, ret; -+ -+ if (!v4root_needed) -+ return; -+ if (!v4root_support()) -+ return; -+ -+ for (i = 0; i < MCL_MAXTYPES; i++) { -+ for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { -+ if (exp->m_export.e_flags & NFSEXP_V4ROOT) -+ /* -+ * We just added this one, so its -+ * parents are already dealt with! -+ */ -+ continue; -+ -+ ret = v4root_add_parents(exp); -+ /* XXX: error handling! */ -+ } -+ } -+} -diff --git a/utils/nfsd/nfssvc.c b/utils/nfsd/nfssvc.c -index 12d3253..b8028bb 100644 ---- a/utils/nfsd/nfssvc.c -+++ b/utils/nfsd/nfssvc.c -@@ -212,7 +212,7 @@ int - nfssvc_set_sockets(const int family, const unsigned int protobits, - const char *host, const char *port) - { -- struct addrinfo hints = { .ai_flags = AI_PASSIVE | AI_ADDRCONFIG }; -+ struct addrinfo hints = { .ai_flags = AI_PASSIVE }; - - hints.ai_family = family; - -diff --git a/utils/showmount/showmount.c b/utils/showmount/showmount.c -index 418e8b9..f567093 100644 ---- a/utils/showmount/showmount.c -+++ b/utils/showmount/showmount.c -@@ -78,29 +78,36 @@ static void usage(FILE *fp, int n) - exit(n); - } - --static const char *nfs_sm_pgmtbl[] = { -+static const char *mount_pgm_tbl[] = { - "showmount", - "mount", - "mountd", - NULL, - }; - -+static const rpcvers_t mount_vers_tbl[] = { -+ MOUNTVERS_NFSV3, -+ MOUNTVERS_POSIX, -+ MOUNTVERS, -+}; -+static const unsigned int max_vers_tblsz = -+ (sizeof(mount_vers_tbl)/sizeof(mount_vers_tbl[0])); -+ - /* - * Generate an RPC client handle connected to the mountd service - * at @hostname, or die trying. - * - * Supports both AF_INET and AF_INET6 server addresses. - */ --static CLIENT *nfs_get_mount_client(const char *hostname) -+static CLIENT *nfs_get_mount_client(const char *hostname, rpcvers_t vers) - { -- rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, nfs_sm_pgmtbl); -+ rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, mount_pgm_tbl); - CLIENT *client; - -- client = clnt_create(hostname, program, MOUNTVERS, "tcp"); -+ client = clnt_create(hostname, program, vers, "tcp"); - if (client) - return client; -- -- client = clnt_create(hostname, program, MOUNTVERS, "udp"); -+ client = clnt_create(hostname, program, vers, "udp"); - if (client) - return client; - -@@ -123,6 +130,7 @@ int main(int argc, char **argv) - int i; - int n; - int maxlen; -+ int unsigned vers=0; - char **dumpv; - - program_name = argv[0]; -@@ -185,11 +193,12 @@ int main(int argc, char **argv) - break; - } - -- mclient = nfs_get_mount_client(hostname); -+ mclient = nfs_get_mount_client(hostname, mount_vers_tbl[vers]); - mclient->cl_auth = authunix_create_default(); - total_timeout.tv_sec = TOTAL_TIMEOUT; - total_timeout.tv_usec = 0; - -+again: - if (eflag) { - memset(&exportlist, '\0', sizeof(exportlist)); - -@@ -197,6 +206,13 @@ int main(int argc, char **argv) - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_exports, (caddr_t) &exportlist, - total_timeout); -+ if (clnt_stat == RPC_PROGVERSMISMATCH) { -+ if (++vers < max_vers_tblsz) { -+ (void)CLNT_CONTROL(mclient, CLSET_VERS, -+ (void *)&mount_vers_tbl[vers]); -+ goto again; -+ } -+ } - if (clnt_stat != RPC_SUCCESS) { - clnt_perror(mclient, "rpc mount export"); - clnt_destroy(mclient); -@@ -232,6 +248,13 @@ int main(int argc, char **argv) - (xdrproc_t) xdr_void, NULL, - (xdrproc_t) xdr_mountlist, (caddr_t) &dumplist, - total_timeout); -+ if (clnt_stat == RPC_PROGVERSMISMATCH) { -+ if (++vers < max_vers_tblsz) { -+ (void)CLNT_CONTROL(mclient, CLSET_VERS, -+ (void *)&mount_vers_tbl[vers]); -+ goto again; -+ } -+ } - if (clnt_stat != RPC_SUCCESS) { - clnt_perror(mclient, "rpc mount dump"); - clnt_destroy(mclient); -diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am -index 8a3ba4e..1744791 100644 ---- a/utils/statd/Makefile.am -+++ b/utils/statd/Makefile.am -@@ -2,31 +2,25 @@ - - man8_MANS = statd.man sm-notify.man - --GENFILES_CLNT = sm_inter_clnt.c --GENFILES_SVC = sm_inter_svc.c --GENFILES_XDR = sm_inter_xdr.c --GENFILES_H = sm_inter.h -- --GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) -- - RPCPREFIX = rpc. - KPREFIX = @kprefix@ - sbin_PROGRAMS = statd sm-notify - dist_sbin_SCRIPTS = start-statd --statd_SOURCES = callback.c notlist.c log.c misc.c monitor.c \ -+statd_SOURCES = callback.c notlist.c misc.c monitor.c hostname.c \ - simu.c stat.c statd.c svc_run.c rmtcall.c \ -- sm_inter_clnt.c sm_inter_svc.c sm_inter_xdr.c log.h \ -- notlist.h statd.h system.h version.h sm_inter.h -+ notlist.h statd.h system.h version.h - sm_notify_SOURCES = sm-notify.c - - BUILT_SOURCES = $(GENFILES) --statd_LDADD = ../../support/export/libexport.a \ -+statd_LDADD = ../../support/nsm/libnsm.a \ - ../../support/nfs/libnfs.a \ - ../../support/misc/libmisc.a \ -- $(LIBWRAP) $(LIBNSL) --sm_notify_LDADD = $(LIBNSL) -+ $(LIBWRAP) $(LIBNSL) $(LIBCAP) -+sm_notify_LDADD = ../../support/nsm/libnsm.a \ -+ ../../support/nfs/libnfs.a \ -+ $(LIBNSL) $(LIBCAP) - --EXTRA_DIST = sim_sm_inter.x sm_inter.x $(man8_MANS) COPYRIGHT simulate.c -+EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c - - if CONFIG_RPCGEN - RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen -diff --git a/utils/statd/callback.c b/utils/statd/callback.c -index 8885238..d1cc139 100644 ---- a/utils/statd/callback.c -+++ b/utils/statd/callback.c -@@ -10,10 +10,9 @@ - #include - #endif - --#include -+#include - - #include "rpcmisc.h" --#include "misc.h" - #include "statd.h" - #include "notlist.h" - -@@ -21,30 +20,85 @@ - /* notify_list *cbnl = NULL; ... never used */ - - --/* -+/* - * Services SM_NOTIFY requests. -- * Any clients that have asked us to monitor that host are put on -- * the global callback list, which is processed as soon as statd -- * returns to svc_run. -+ * -+ * When NLM uses an SM_MON request to tell statd to monitor a remote, -+ * the request contains a "mon_name" argument. This is usually the -+ * "caller_name" argument of an NLMPROC_LOCK request. On Linux, the -+ * NLM can send statd the remote's IP address instead of its -+ * caller_name. The NSM protocol does not allow both the remote's -+ * caller_name and it's IP address to be sent in the same SM_MON -+ * request. -+ * -+ * The remote's caller_name is useful because it makes it simple -+ * to identify rebooting remotes by matching the "mon_name" argument -+ * they sent via an SM_NOTIFY request. -+ * -+ * The caller_name string may not be a fully qualified domain name, -+ * or even registered in the DNS database, however. Having the -+ * remote's IP address is useful because then there is no ambiguity -+ * about where to send an SM_NOTIFY after the local system reboots. -+ * -+ * Without the actual caller_name, however, statd must use an -+ * heuristic to match an incoming SM_NOTIFY request to one of the -+ * hosts it is currently monitoring. The incoming mon_name in an -+ * SM_NOTIFY address is converted to a list of IP addresses using -+ * DNS. Each mon_name on statd's monitor list is also converted to -+ * an address list, and the two lists are checked to see if there is -+ * a matching address. -+ * -+ * There are some risks to this strategy: -+ * -+ * 1. The external DNS database is not reliable. It can change -+ * over time, or the forward and reverse mappings could be -+ * inconsistent. -+ * -+ * 2. If statd's monitor list becomes substantial, finding a match -+ * can generate a not inconsequential amount of DNS traffic. -+ * -+ * 3. statd is a single-threaded service. When DNS becomes slow or -+ * unresponsive, statd also becomes slow or unresponsive. -+ * -+ * 4. If the remote does not have a DNS entry at all (or if the -+ * remote can resolve itself, but the local host can't resolve -+ * the remote's hostname), the remote cannot be monitored, and -+ * therefore NLM locking cannot be provided for that host. -+ * -+ * 5. Local DNS resolution can produce different results for the -+ * mon_name than the results the remote might see for the same -+ * query, especially if the remote did not send a caller_name -+ * or mon_name that is a fully qualified domain name. -+ * -+ * Note that a caller_name is passed from NFS client to server, -+ * but the client never knows what mon_name the server might use -+ * to notify it of a reboot. On Linux, the client extracts the -+ * server's name from the devname it was passed by the mount -+ * command. This is often not a fully-qualified domain name. - */ - void * - sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp) - { - notify_list *lp, *call; - static char *result = NULL; -- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); -- char *ip_addr = xstrdup(inet_ntoa(sin->sin_addr)); -+ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); -+ char ip_addr[INET6_ADDRSTRLEN]; - -- dprintf(N_DEBUG, "Received SM_NOTIFY from %s, state: %d", -+ xlog(D_CALL, "Received SM_NOTIFY from %s, state: %d", - argp->mon_name, argp->state); - - /* quick check - don't bother if we're not monitoring anyone */ - if (rtnl == NULL) { -- note(N_WARNING, "SM_NOTIFY from %s while not monitoring any hosts.", -+ xlog_warn("SM_NOTIFY from %s while not monitoring any hosts", - argp->mon_name); - return ((void *) &result); - } - -+ if (!statd_present_address(sap, ip_addr, sizeof(ip_addr))) { -+ xlog_warn("Unrecognized sender address"); -+ return ((void *) &result); -+ } -+ - /* okir change: statd doesn't remove the remote host from its - * internal monitor list when receiving an SM_NOTIFY call from - * it. Lockd will want to continue monitoring the remote host -@@ -52,8 +106,8 @@ sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp) - */ - for (lp = rtnl ; lp ; lp = lp->next) - if (NL_STATE(lp) != argp->state && -- (matchhostname(argp->mon_name, lp->dns_name) || -- matchhostname(ip_addr, lp->dns_name))) { -+ (statd_matchhostname(argp->mon_name, lp->dns_name) || -+ statd_matchhostname(ip_addr, lp->dns_name))) { - NL_STATE(lp) = argp->state; - call = nlist_clone(lp); - nlist_insert(¬ify, call); -diff --git a/utils/statd/hostname.c b/utils/statd/hostname.c -new file mode 100644 -index 0000000..7d704cc ---- /dev/null -+++ b/utils/statd/hostname.c -@@ -0,0 +1,284 @@ -+/* -+ * Copyright 2009 Oracle. All rights reserved. -+ * -+ * 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 . -+ */ -+ -+/* -+ * NSM for Linux. -+ */ -+ -+#ifdef HAVE_CONFIG_H -+#include -+#endif -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "sockaddr.h" -+#include "statd.h" -+#include "xlog.h" -+ -+#ifndef HAVE_DECL_AI_ADDRCONFIG -+#define AI_ADDRCONFIG 0 -+#endif -+ -+/** -+ * statd_present_address - convert sockaddr to presentation address -+ * @sap: pointer to socket address to convert -+ * @buf: pointer to buffer to fill in -+ * @buflen: length of buffer -+ * -+ * Convert the passed-in sockaddr-style address to presentation format. -+ * The presentation format address is placed in @buf and is -+ * '\0'-terminated. -+ * -+ * Returns true if successful; otherwise false. -+ * -+ * getnameinfo(3) is preferred, since it can parse IPv6 scope IDs. -+ * An alternate version of statd_present_address() is available to -+ * handle older glibcs that do not have getnameinfo(3). -+ */ -+#ifdef HAVE_GETNAMEINFO -+_Bool -+statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen) -+{ -+ socklen_t salen; -+ int error; -+ -+ salen = nfs_sockaddr_length(sap); -+ if (salen == 0) { -+ xlog(D_GENERAL, "%s: unsupported address family", -+ __func__); -+ return false; -+ } -+ -+ error = getnameinfo(sap, salen, buf, (socklen_t)buflen, -+ NULL, 0, NI_NUMERICHOST); -+ if (error != 0) { -+ xlog(D_GENERAL, "%s: getnameinfo(3): %s", -+ __func__, gai_strerror(error)); -+ return false; -+ } -+ return true; -+} -+#else /* !HAVE_GETNAMEINFO */ -+_Bool -+statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen) -+{ -+ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; -+ -+ if (sin->sin_family != AF_INET) { -+ xlog(D_GENERAL, "%s: unsupported address family", __func__); -+ return false; -+ } -+ -+ /* ensure '\0' termination */ -+ memset(buf, 0, buflen); -+ -+ if (inet_ntop(AF_INET, (char *)&sin->sin_addr, -+ buf, (socklen_t)buflen) == NULL) { -+ xlog(D_GENERAL, "%s: inet_ntop(3): %m", __func__); -+ return false; -+ } -+ return true; -+} -+#endif /* !HAVE_GETNAMEINFO */ -+ -+/* -+ * Look up the hostname; report exceptional errors. Caller must -+ * call freeaddrinfo(3) if a valid addrinfo is returned. -+ */ -+__attribute_malloc__ -+static struct addrinfo * -+get_addrinfo(const char *hostname, const struct addrinfo *hint) -+{ -+ struct addrinfo *ai = NULL; -+ int error; -+ -+ error = getaddrinfo(hostname, NULL, hint, &ai); -+ switch (error) { -+ case 0: -+ return ai; -+ case EAI_NONAME: -+ break; -+ default: -+ xlog(D_GENERAL, "%s: failed to resolve host %s: %s", -+ __func__, hostname, gai_strerror(error)); -+ } -+ -+ return NULL; -+} -+ -+#ifdef HAVE_GETNAMEINFO -+static _Bool -+get_nameinfo(const struct sockaddr *sap, const socklen_t salen, -+ /*@out@*/ char *buf, const socklen_t buflen) -+{ -+ int error; -+ -+ error = getnameinfo(sap, salen, buf, buflen, NULL, 0, NI_NAMEREQD); -+ if (error != 0) { -+ xlog(D_GENERAL, "%s: failed to resolve address: %s", -+ __func__, gai_strerror(error)); -+ return false; -+ } -+ -+ return true; -+} -+#else /* !HAVE_GETNAMEINFO */ -+static _Bool -+get_nameinfo(const struct sockaddr *sap, -+ __attribute__ ((unused)) const socklen_t salen, -+ /*@out@*/ char *buf, socklen_t buflen) -+{ -+ struct sockaddr_in *sin = (struct sockaddr_in *)(char *)sap; -+ struct hostent *hp; -+ -+ if (sin->sin_family != AF_INET) { -+ xlog(D_GENERAL, "%s: unknown address family: %d", -+ sin->sin_family); -+ return false; -+ } -+ -+ hp = gethostbyaddr((const char *)&(sin->sin_addr.s_addr), -+ sizeof(struct in_addr), AF_INET); -+ if (hp == NULL) { -+ xlog(D_GENERAL, "%s: failed to resolve address: %m", __func__); -+ return false; -+ } -+ -+ strncpy(buf, hp->h_name, (size_t)buflen); -+ return true; -+} -+#endif /* !HAVE_GETNAMEINFO */ -+ -+/** -+ * statd_canonical_name - choose file name for monitor record files -+ * @hostname: C string containing hostname or presentation address -+ * -+ * Returns a '\0'-terminated ASCII string containing a fully qualified -+ * canonical hostname, or NULL if @hostname does not have a reverse -+ * mapping. Caller must free the result with free(3). -+ * -+ * Incoming hostnames are looked up to determine the canonical hostname, -+ * and incoming presentation addresses are converted to canonical -+ * hostnames. -+ * -+ * We won't monitor peers that don't have a reverse map. The canonical -+ * name gives us a key for our monitor list. -+ */ -+__attribute_malloc__ -+char * -+statd_canonical_name(const char *hostname) -+{ -+ struct addrinfo hint = { -+#ifdef IPV6_SUPPORTED -+ .ai_family = AF_UNSPEC, -+#else /* !IPV6_SUPPORTED */ -+ .ai_family = AF_INET, -+#endif /* !IPV6_SUPPORTED */ -+ .ai_flags = AI_NUMERICHOST, -+ .ai_protocol = (int)IPPROTO_UDP, -+ }; -+ char buf[NI_MAXHOST]; -+ struct addrinfo *ai; -+ -+ ai = get_addrinfo(hostname, &hint); -+ if (ai != NULL) { -+ /* @hostname was a presentation address */ -+ _Bool result; -+ result = get_nameinfo(ai->ai_addr, ai->ai_addrlen, -+ buf, (socklen_t)sizeof(buf)); -+ freeaddrinfo(ai); -+ if (!result) -+ return NULL; -+ return strdup(buf); -+ } -+ -+ /* @hostname was a hostname */ -+ hint.ai_flags = AI_CANONNAME; -+ ai = get_addrinfo(hostname, &hint); -+ if (ai == NULL) -+ return NULL; -+ strcpy(buf, ai->ai_canonname); -+ freeaddrinfo(ai); -+ -+ return strdup(buf); -+} -+ -+/** -+ * statd_matchhostname - check if two hostnames are equivalent -+ * @hostname1: C string containing hostname -+ * @hostname2: C string containing hostname -+ * -+ * Returns true if the hostnames are the same, the hostnames resolve -+ * to the same canonical name, or the hostnames resolve to at least -+ * one address that is the same. False is returned if the hostnames -+ * do not match in any of these ways, if either hostname contains -+ * wildcard characters, if either hostname is a netgroup name, or -+ * if an error occurs. -+ */ -+_Bool -+statd_matchhostname(const char *hostname1, const char *hostname2) -+{ -+ struct addrinfo *ai1, *ai2, *results1 = NULL, *results2 = NULL; -+ struct addrinfo hint = { -+ .ai_family = AF_UNSPEC, -+ .ai_flags = AI_CANONNAME, -+ .ai_protocol = (int)IPPROTO_UDP, -+ }; -+ _Bool result = false; -+ -+ if (strcasecmp(hostname1, hostname2) == 0) { -+ result = true; -+ goto out; -+ } -+ -+ results1 = get_addrinfo(hostname1, &hint); -+ if (results1 == NULL) -+ goto out; -+ results2 = get_addrinfo(hostname2, &hint); -+ if (results2 == NULL) -+ goto out; -+ -+ if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) { -+ result = true; -+ goto out; -+ } -+ -+ for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next) -+ for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next) -+ if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) { -+ result = true; -+ break; -+ } -+ -+out: -+ freeaddrinfo(results2); -+ freeaddrinfo(results1); -+ -+ xlog(D_CALL, "%s: hostnames %s", __func__, -+ (result ? "matched" : "did not match")); -+ return result; -+} -diff --git a/utils/statd/log.c b/utils/statd/log.c -deleted file mode 100644 -index a6ca996..0000000 ---- a/utils/statd/log.c -+++ /dev/null -@@ -1,95 +0,0 @@ --/* -- * Copyright (C) 1995 Olaf Kirch -- * Modified by Jeffrey A. Uphoff, 1995, 1997, 1999. -- * Modified by H.J. Lu, 1998. -- * Modified by Lon Hohberger, Oct. 2000 -- * -- * NSM for Linux. -- */ -- --/* -- * log.c - logging functions for lockd/statd -- * 260295 okir started with simply syslog logging. -- */ -- --#ifdef HAVE_CONFIG_H --#include --#endif -- --#include --#include --#include --#include --#include --#include --#include --#include --#include "log.h" --#include "statd.h" -- --static pid_t mypid; -- /* Turns on logging to console/stderr. */ --#if 0 --static int opt_debug = 0; /* Will be command-line option, eventually */ --#endif -- --void log_init(void) --{ -- if (!(run_mode & MODE_LOG_STDERR)) -- openlog(name_p, LOG_PID | LOG_NDELAY, LOG_DAEMON); -- -- mypid = getpid(); -- -- note(N_WARNING,"Version %s Starting",version_p); --} -- --void log_background(void) --{ -- /* NOP */ --} -- --void die(char *fmt, ...) --{ -- char buffer[1024]; -- va_list ap; -- -- va_start(ap, fmt); -- vsnprintf (buffer, 1024, fmt, ap); -- va_end(ap); -- buffer[1023]=0; -- -- note(N_FATAL, "%s", buffer); -- --#ifndef DEBUG -- exit (2); --#else -- abort(); /* make a core */ --#endif --} -- --void note(int level, char *fmt, ...) --{ -- char buffer[1024]; -- va_list ap; -- -- va_start(ap, fmt); -- vsnprintf (buffer, 1024, fmt, ap); -- va_end(ap); -- buffer[1023]=0; -- -- if ((!(run_mode & MODE_LOG_STDERR)) && (level < N_DEBUG)) { -- syslog(level, "%s", buffer); -- } else if (run_mode & MODE_LOG_STDERR) { -- /* Log everything, including dprintf() stuff to stderr */ -- time_t now; -- struct tm * tm; -- -- time(&now); -- tm = localtime(&now); -- fprintf (stderr, "%02d/%02d/%04d %02d:%02d:%02d %s[%d]: %s\n", -- tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900, -- tm->tm_hour, tm->tm_min, tm->tm_sec, -- name_p, mypid, -- buffer); -- } --} -diff --git a/utils/statd/log.h b/utils/statd/log.h -deleted file mode 100644 -index fc55d3c..0000000 ---- a/utils/statd/log.h -+++ /dev/null -@@ -1,42 +0,0 @@ --/* -- * Copyright (C) 1995 Olaf Kirch -- * Modified by Jeffrey A. Uphoff, 1996, 1997, 1999. -- * Modified by Lon Hohberger, Oct. 2000 -- * -- * NSM for Linux. -- */ -- --/* -- * logging functionality -- * 260295 okir -- */ -- --#ifndef _LOCKD_LOG_H_ --#define _LOCKD_LOG_H_ -- --#include -- --void log_init(void); --void log_background(void); --void log_enable(int facility); --int log_enabled(int facility); --void note(int level, char *fmt, ...); --void die(char *fmt, ...); -- --/* -- * Map per-application severity to system severity. What's fatal for -- * lockd is merely an itching spot from the universe's point of view. -- */ --#define N_CRIT LOG_CRIT --#define N_FATAL LOG_ERR --#define N_ERROR LOG_WARNING --#define N_WARNING LOG_NOTICE --#define N_DEBUG LOG_DEBUG -- --#ifdef DEBUG --#define dprintf note --#else --#define dprintf if (run_mode & MODE_LOG_STDERR) note --#endif -- --#endif /* _LOCKD_LOG_H_ */ -diff --git a/utils/statd/misc.c b/utils/statd/misc.c -index 7256291..f2a086f 100644 ---- a/utils/statd/misc.c -+++ b/utils/statd/misc.c -@@ -29,8 +29,7 @@ xmalloc (size_t size) - return ((void *)NULL); - - if (!(ptr = malloc (size))) -- /* SHIT! SHIT! SHIT! */ -- die ("malloc failed"); -+ xlog_err ("malloc failed"); - - return (ptr); - } -@@ -46,32 +45,7 @@ xstrdup (const char *string) - - /* Will only fail if underlying malloc() fails (ENOMEM). */ - if (!(result = strdup (string))) -- die ("strdup failed"); -+ xlog_err ("strdup failed"); - - return (result); - } -- -- --/* -- * Unlinking a file. -- */ --void --xunlink (char *path, char *host) --{ -- char *tozap; -- -- tozap = malloc(strlen(path)+strlen(host)+2); -- if (tozap == NULL) { -- note(N_ERROR, "xunlink: malloc failed: errno %d (%s)", -- errno, strerror(errno)); -- return; -- } -- sprintf (tozap, "%s/%s", path, host); -- -- if (unlink (tozap) == -1) -- note(N_ERROR, "unlink (%s): %s", tozap, strerror (errno)); -- else -- dprintf (N_DEBUG, "Unlinked %s", tozap); -- -- free(tozap); --} -diff --git a/utils/statd/monitor.c b/utils/statd/monitor.c -index a2c9e2b..325dfd3 100644 ---- a/utils/statd/monitor.c -+++ b/utils/statd/monitor.c -@@ -21,34 +21,38 @@ - #include - #include - -+#include "sockaddr.h" - #include "rpcmisc.h" --#include "misc.h" -+#include "nsm.h" - #include "statd.h" - #include "notlist.h" - #include "ha-callout.h" - - notify_list * rtnl = NULL; /* Run-time notify list. */ - --#define LINELEN (4*(8+1)+SM_PRIV_SIZE*2+1) -- - /* - * Reject requests from non-loopback addresses in order - * to prevent attack described in CERT CA-99.05. -+ * -+ * Although the kernel contacts the statd service via only IPv4 -+ * transports, the statd service can receive other requests, such -+ * as SM_NOTIFY, from remote peers via IPv6. - */ --static int -+static _Bool - caller_is_localhost(struct svc_req *rqstp) - { -- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); -- struct in_addr caller; -- -- caller = sin->sin_addr; -- if (caller.s_addr != htonl(INADDR_LOOPBACK)) { -- note(N_WARNING, -- "Call to statd from non-local host %s", -- inet_ntoa(caller)); -- return 0; -- } -- return 1; -+ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); -+ char buf[INET6_ADDRSTRLEN]; -+ -+ if (!nfs_is_v4_loopback(sap)) -+ goto out_nonlocal; -+ return true; -+ -+out_nonlocal: -+ if (!statd_present_address(sap, buf, sizeof(buf))) -+ buf[0] = '\0'; -+ xlog_warn("SM_MON/SM_UNMON call from non-local host %s", buf); -+ return false; - } - - /* -@@ -61,13 +65,15 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - char *mon_name = argp->mon_id.mon_name, - *my_name = argp->mon_id.my_id.my_name; - struct my_id *id = &argp->mon_id.my_id; -- char *path; - char *cp; -- int fd; - notify_list *clnt; -- struct in_addr my_addr; -- char *dnsname; -- struct hostent *hostinfo = NULL; -+ struct sockaddr_in my_addr = { -+ .sin_family = AF_INET, -+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK), -+ }; -+ char *dnsname = NULL; -+ -+ xlog(D_CALL, "Received SM_MON for %s from %s", mon_name, my_name); - - /* Assume that we'll fail. */ - result.res_stat = STAT_FAIL; -@@ -79,7 +85,6 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - */ - if (!caller_is_localhost(rqstp)) - goto failure; -- my_addr.s_addr = htonl(INADDR_LOOPBACK); - - /* 2. Reject any registrations for non-lockd services. - * -@@ -92,8 +97,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - if (id->my_prog != 100021 || - (id->my_proc != 16 && id->my_proc != 24)) - { -- note(N_WARNING, -- "Attempt to register callback to %d/%d", -+ xlog_warn("Attempt to register callback to %d/%d", - id->my_prog, id->my_proc); - goto failure; - } -@@ -105,12 +109,9 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - - /* must check for /'s in hostname! See CERT's CA-96.09 for details. */ - if (strchr(mon_name, '/') || mon_name[0] == '.') { -- note(N_CRIT, "SM_MON request for hostname containing '/' " -+ xlog(L_ERROR, "SM_MON request for hostname containing '/' " - "or starting '.': %s", mon_name); -- note(N_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); -- goto failure; -- } else if ((hostinfo = gethostbyname(mon_name)) == NULL) { -- note(N_WARNING, "gethostbyname error for %s", mon_name); -+ xlog(L_ERROR, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); - goto failure; - } - -@@ -124,15 +125,13 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - * Now choose a hostname to use for matching. We cannot - * really trust much in the incoming NOTIFY, so to make - * sure that multi-homed hosts work nicely, we get an -- * FQDN now, and use that for matching -+ * FQDN now, and use that for matching. - */ -- hostinfo = gethostbyaddr(hostinfo->h_addr, -- hostinfo->h_length, -- hostinfo->h_addrtype); -- if (hostinfo) -- dnsname = xstrdup(hostinfo->h_name); -- else -- dnsname = xstrdup(my_name); -+ dnsname = statd_canonical_name(mon_name); -+ if (dnsname == NULL) { -+ xlog(L_WARNING, "No canonical hostname found for %s", mon_name); -+ goto failure; -+ } - - /* Now check to see if this is a duplicate, and warn if so. - * I will also return STAT_FAIL. (I *think* this is how I should -@@ -146,18 +145,19 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - clnt = rtnl; - - while ((clnt = nlist_gethost(clnt, mon_name, 0))) { -- if (matchhostname(NL_MY_NAME(clnt), my_name) && -+ if (statd_matchhostname(NL_MY_NAME(clnt), my_name) && - NL_MY_PROC(clnt) == id->my_proc && - NL_MY_PROG(clnt) == id->my_prog && - NL_MY_VERS(clnt) == id->my_vers && - memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) { - /* Hey! We already know you guys! */ -- dprintf(N_DEBUG, -+ xlog(D_GENERAL, - "Duplicate SM_MON request for %s " - "from procedure on %s", - mon_name, my_name); - - /* But we'll let you pass anyway. */ -+ free(dnsname); - goto success; - } - clnt = NL_NEXT(clnt); -@@ -168,11 +168,11 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - * doesn't fail. (I should probably fix this assumption.) - */ - if (!(clnt = nlist_new(my_name, mon_name, 0))) { -- note(N_WARNING, "out of memory"); -+ free(dnsname); -+ xlog_warn("out of memory"); - goto failure; - } - -- NL_ADDR(clnt) = my_addr; - NL_MY_PROG(clnt) = id->my_prog; - NL_MY_VERS(clnt) = id->my_vers; - NL_MY_PROC(clnt) = id->my_proc; -@@ -182,40 +182,16 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - /* - * Now, Create file on stable storage for host. - */ -- -- path=xmalloc(strlen(SM_DIR)+strlen(dnsname)+2); -- sprintf(path, "%s/%s", SM_DIR, dnsname); -- if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT|O_APPEND, -- S_IRUSR|S_IWUSR)) < 0) { -- /* Didn't fly. We won't monitor. */ -- note(N_ERROR, "creat(%s) failed: %s", path, strerror (errno)); -+ if (!nsm_insert_monitored_host(dnsname, -+ (struct sockaddr *)(char *)&my_addr, argp)) { - nlist_free(NULL, clnt); -- free(path); - goto failure; - } -- { -- char buf[LINELEN + 1 + SM_MAXSTRLEN*2 + 4]; -- char *e; -- int i; -- e = buf + sprintf(buf, "%08x %08x %08x %08x ", -- my_addr.s_addr, id->my_prog, -- id->my_vers, id->my_proc); -- for (i=0; ipriv[i])); -- if (e+1-buf != LINELEN) abort(); -- e += sprintf(e, " %s %s\n", mon_name, my_name); -- if (write(fd, buf, e-buf) != (e-buf)) { -- note(N_WARNING, "writing to %s failed: errno %d (%s)", -- path, errno, strerror(errno)); -- } -- } - -- free(path); - /* PRC: do the HA callout: */ - ha_callout("add-client", mon_name, my_name, -1); - nlist_insert(&rtnl, clnt); -- close(fd); -- dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name); -+ xlog(D_GENERAL, "MONITORING %s for %s", mon_name, my_name); - success: - result.res_stat = STAT_SUCC; - /* SUN's sm_inter.x says this should be "state number of local site". -@@ -232,75 +208,49 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) - return (&result); - - failure: -- note(N_WARNING, "STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); -+ xlog_warn("STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); - return (&result); - } - --void load_state(void) -+static unsigned int -+load_one_host(const char *hostname, -+ __attribute__ ((unused)) const struct sockaddr *sap, -+ const struct mon *m, -+ __attribute__ ((unused)) const time_t timestamp) - { -- DIR *d; -- struct dirent *de; -- char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; -- -- d = opendir(SM_DIR); -- if (!d) -- return; -- while ((de = readdir(d))) { -- char *path; -- FILE *f; -- int p; -- -- if (de->d_name[0] == '.') -- continue; -- path = xmalloc(strlen(SM_DIR)+strlen(de->d_name)+2); -- sprintf(path, "%s/%s", SM_DIR, de->d_name); -- f = fopen(path, "r"); -- free(path); -- if (f == NULL) -- continue; -- while (fgets(buf, sizeof(buf), f) != NULL) { -- int addr, proc, prog, vers; -- char priv[SM_PRIV_SIZE]; -- char *monname, *myname; -- char *b; -- int i; -- notify_list *clnt; -- -- buf[sizeof(buf)-1] = 0; -- b = strchr(buf, '\n'); -- if (b) *b = 0; -- sscanf(buf, "%x %x %x %x ", -- &addr, &prog, &vers, &proc); -- b = buf+36; -- for (i=0; idns_name = xstrdup(de->d_name); -- memcpy(NL_PRIV(clnt), priv, SM_PRIV_SIZE); -- nlist_insert(&rtnl, clnt); -- } -- fclose(f); -+ notify_list *clnt; -+ -+ clnt = nlist_new(m->mon_id.my_id.my_name, -+ m->mon_id.mon_name, 0); -+ if (clnt == NULL) -+ return 0; -+ -+ clnt->dns_name = strdup(hostname); -+ if (clnt->dns_name == NULL) { -+ nlist_free(NULL, clnt); -+ return 0; - } -- closedir(d); --} - -+ xlog(D_GENERAL, "Adding record for %s to the monitor list...", -+ hostname); - -+ NL_MY_PROG(clnt) = m->mon_id.my_id.my_prog; -+ NL_MY_VERS(clnt) = m->mon_id.my_id.my_vers; -+ NL_MY_PROC(clnt) = m->mon_id.my_id.my_proc; -+ memcpy(NL_PRIV(clnt), m->priv, SM_PRIV_SIZE); - -+ nlist_insert(&rtnl, clnt); -+ return 1; -+} -+ -+void load_state(void) -+{ -+ unsigned int count; -+ -+ count = nsm_load_monitor_list(load_one_host); -+ if (count) -+ xlog(D_GENERAL, "Loaded %u previously monitored hosts"); -+} - - /* - * Services SM_UNMON requests. -@@ -320,6 +270,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) - struct my_id *id = &argp->my_id; - char *cp; - -+ xlog(D_CALL, "Received SM_UNMON for %s from %s", mon_name, my_name); -+ - result.state = MY_STATE; - - if (!caller_is_localhost(rqstp)) -@@ -333,9 +285,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) - - /* Check if we're monitoring anyone. */ - if (rtnl == NULL) { -- note(N_WARNING, -- "Received SM_UNMON request from %s for %s while not " -- "monitoring any hosts.", my_name, argp->mon_name); -+ xlog_warn("Received SM_UNMON request from %s for %s while not " -+ "monitoring any hosts", my_name, argp->mon_name); - return (&result); - } - clnt = rtnl; -@@ -347,18 +298,19 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) - * entry winds up in the list the way I'm currently handling them.) - */ - while ((clnt = nlist_gethost(clnt, mon_name, 0))) { -- if (matchhostname(NL_MY_NAME(clnt), my_name) && -+ if (statd_matchhostname(NL_MY_NAME(clnt), my_name) && - NL_MY_PROC(clnt) == id->my_proc && - NL_MY_PROG(clnt) == id->my_prog && - NL_MY_VERS(clnt) == id->my_vers) { - /* Match! */ -- dprintf(N_DEBUG, "UNMONITORING %s for %s", -+ xlog(D_GENERAL, "UNMONITORING %s for %s", - mon_name, my_name); - - /* PRC: do the HA callout: */ - ha_callout("del-client", mon_name, my_name, -1); - -- xunlink(SM_DIR, clnt->dns_name); -+ nsm_delete_monitored_host(clnt->dns_name, -+ mon_name, my_name); - nlist_free(&rtnl, clnt); - - return (&result); -@@ -367,7 +319,7 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) - } - - failure: -- note(N_WARNING, "Received erroneous SM_UNMON request from %s for %s", -+ xlog_warn("Received erroneous SM_UNMON request from %s for %s", - my_name, mon_name); - return (&result); - } -@@ -381,13 +333,15 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) - notify_list *clnt; - char *my_name = argp->my_name; - -+ xlog(D_CALL, "Received SM_UNMON_ALL for %s", my_name); -+ - if (!caller_is_localhost(rqstp)) - goto failure; - - result.state = MY_STATE; - - if (rtnl == NULL) { -- note(N_WARNING, "Received SM_UNMON_ALL request from %s " -+ xlog_warn("Received SM_UNMON_ALL request from %s " - "while not monitoring any hosts", my_name); - return (&result); - } -@@ -401,7 +355,7 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) - char mon_name[SM_MAXSTRLEN + 1]; - notify_list *temp; - -- dprintf(N_DEBUG, -+ xlog(D_GENERAL, - "UNMONITORING (SM_UNMON_ALL) %s for %s", - NL_MON_NAME(clnt), NL_MY_NAME(clnt)); - strncpy(mon_name, NL_MON_NAME(clnt), -@@ -410,7 +364,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) - temp = NL_NEXT(clnt); - /* PRC: do the HA callout: */ - ha_callout("del-client", mon_name, my_name, -1); -- xunlink(SM_DIR, clnt->dns_name); -+ nsm_delete_monitored_host(clnt->dns_name, -+ mon_name, my_name); - nlist_free(&rtnl, clnt); - ++count; - clnt = temp; -@@ -419,8 +374,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) - } - - if (!count) { -- dprintf(N_DEBUG, "SM_UNMON_ALL request from %s with no " -- "SM_MON requests from it.", my_name); -+ xlog(D_GENERAL, "SM_UNMON_ALL request from %s with no " -+ "SM_MON requests from it", my_name); - } - - failure: -diff --git a/utils/statd/notlist.c b/utils/statd/notlist.c -index 1698c26..0341c15 100644 ---- a/utils/statd/notlist.c -+++ b/utils/statd/notlist.c -@@ -17,7 +17,6 @@ - #endif - - #include --#include "misc.h" - #include "statd.h" - #include "notlist.h" - -@@ -190,7 +189,6 @@ nlist_clone(notify_list *entry) - NL_MY_PROG(new) = NL_MY_PROG(entry); - NL_MY_VERS(new) = NL_MY_VERS(entry); - NL_MY_PROC(new) = NL_MY_PROC(entry); -- NL_ADDR(new) = NL_ADDR(entry); - memcpy(NL_PRIV(new), NL_PRIV(entry), SM_PRIV_SIZE); - - return new; -@@ -234,7 +232,8 @@ nlist_gethost(notify_list *list, char *host, int myname) - notify_list *lp; - - for (lp = list; lp; lp = lp->next) { -- if (matchhostname(host, myname? NL_MY_NAME(lp) : NL_MON_NAME(lp))) -+ if (statd_matchhostname(host, -+ myname? NL_MY_NAME(lp) : NL_MON_NAME(lp))) - return lp; - } - -diff --git a/utils/statd/notlist.h b/utils/statd/notlist.h -index 664c9d8..6ed0da8 100644 ---- a/utils/statd/notlist.h -+++ b/utils/statd/notlist.h -@@ -12,15 +12,14 @@ - */ - struct notify_list { - mon mon; /* Big honkin' NSM structure. */ -- struct in_addr addr; /* IP address for callback. */ -- unsigned short port; /* port number for callback */ -+ in_port_t port; /* port number for callback */ - short int times; /* Counter used for various things. */ - int state; /* For storing notified state for callbacks. */ - char *dns_name; /* used for matching incoming - * NOTIFY requests */ - struct notify_list *next; /* Linked list forward pointer. */ - struct notify_list *prev; /* Linked list backward pointer. */ -- u_int32_t xid; /* XID of MS_NOTIFY RPC call */ -+ uint32_t xid; /* XID of MS_NOTIFY RPC call */ - time_t when; /* notify: timeout for re-xmit */ - }; - -@@ -53,7 +52,6 @@ extern notify_list * nlist_gethost(notify_list *, char *, int); - #define NL_FIRST NL_NEXT - #define NL_PREV(L) ((L)->prev) - #define NL_DATA(L) ((L)->mon) --#define NL_ADDR(L) ((L)->addr) - #define NL_STATE(L) ((L)->state) - #define NL_TIMES(L) ((L)->times) - #define NL_MON_ID(L) (NL_DATA((L)).mon_id) -diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c -index cc1a4a4..0e52fe2 100644 ---- a/utils/statd/rmtcall.c -+++ b/utils/statd/rmtcall.c -@@ -37,22 +37,19 @@ - #include - #include - #include --#ifdef HAVE_IFADDRS_H --#include --#endif /* HAVE_IFADDRS_H */ -+ - #include "sm_inter.h" - #include "statd.h" - #include "notlist.h" --#include "log.h" - #include "ha-callout.h" - -+#include "nsm.h" -+#include "nfsrpc.h" -+ - #if SIZEOF_SOCKLEN_T - 0 == 0 - #define socklen_t int - #endif - --#define MAXMSGSIZE (2048 / sizeof(unsigned int)) -- --static unsigned long xid = 0; /* RPC XID counter */ - static int sockfd = -1; /* notify socket */ - - /* -@@ -81,7 +78,7 @@ statd_get_socket(void) - if (sockfd >= 0) close(sockfd); - - if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { -- note(N_CRIT, "%s: Can't create socket: %m", __func__); -+ xlog(L_ERROR, "%s: Can't create socket: %m", __func__); - return -1; - } - -@@ -91,7 +88,7 @@ statd_get_socket(void) - sin.sin_addr.s_addr = INADDR_ANY; - - if (bindresvport(sockfd, &sin) < 0) { -- dprintf(N_WARNING, "%s: can't bind to reserved port", -+ xlog(D_GENERAL, "%s: can't bind to reserved port", - __func__); - break; - } -@@ -104,112 +101,37 @@ statd_get_socket(void) - return sockfd; - } - --static unsigned long --xmit_call(struct sockaddr_in *sin, -- u_int32_t prog, u_int32_t vers, u_int32_t proc, -- xdrproc_t func, void *obj) --/* __u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */ --{ -- unsigned int msgbuf[MAXMSGSIZE], msglen; -- struct rpc_msg mesg; -- struct pmap pmap; -- XDR xdr, *xdrs = &xdr; -- int err; -- -- if (!xid) -- xid = getpid() + time(NULL); -- -- mesg.rm_xid = ++xid; -- mesg.rm_direction = CALL; -- mesg.rm_call.cb_rpcvers = 2; -- if (sin->sin_port == 0) { -- sin->sin_port = htons(PMAPPORT); -- mesg.rm_call.cb_prog = PMAPPROG; -- mesg.rm_call.cb_vers = PMAPVERS; -- mesg.rm_call.cb_proc = PMAPPROC_GETPORT; -- pmap.pm_prog = prog; -- pmap.pm_vers = vers; -- pmap.pm_prot = IPPROTO_UDP; -- pmap.pm_port = 0; -- func = (xdrproc_t) xdr_pmap; -- obj = &pmap; -- } else { -- mesg.rm_call.cb_prog = prog; -- mesg.rm_call.cb_vers = vers; -- mesg.rm_call.cb_proc = proc; -- } -- mesg.rm_call.cb_cred.oa_flavor = AUTH_NULL; -- mesg.rm_call.cb_cred.oa_base = (caddr_t) NULL; -- mesg.rm_call.cb_cred.oa_length = 0; -- mesg.rm_call.cb_verf.oa_flavor = AUTH_NULL; -- mesg.rm_call.cb_verf.oa_base = (caddr_t) NULL; -- mesg.rm_call.cb_verf.oa_length = 0; -- -- /* Create XDR memory object for encoding */ -- xdrmem_create(xdrs, (caddr_t) msgbuf, sizeof(msgbuf), XDR_ENCODE); -- -- /* Encode the RPC header part and payload */ -- if (!xdr_callmsg(xdrs, &mesg) || !func(xdrs, obj)) { -- dprintf(N_WARNING, "%s: can't encode RPC message!", __func__); -- xdr_destroy(xdrs); -- return 0; -- } -- -- /* Get overall length of datagram */ -- msglen = xdr_getpos(xdrs); -- -- if ((err = sendto(sockfd, msgbuf, msglen, 0, -- (struct sockaddr *) sin, sizeof(*sin))) < 0) { -- dprintf(N_WARNING, "%s: sendto failed: %m", __func__); -- } else if (err != msglen) { -- dprintf(N_WARNING, "%s: short write: %m", __func__); -- } -- -- xdr_destroy(xdrs); -- -- return err == msglen? xid : 0; --} -- - static notify_list * --recv_rply(struct sockaddr_in *sin, u_long *portp) -+recv_rply(u_long *portp) - { -- unsigned int msgbuf[MAXMSGSIZE], msglen; -- struct rpc_msg mesg; -+ char msgbuf[NSM_MAXMSGSIZE]; -+ ssize_t msglen; - notify_list *lp = NULL; -- XDR xdr, *xdrs = &xdr; -- socklen_t alen = sizeof(*sin); -- -- /* Receive message */ -- if ((msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, -- (struct sockaddr *) sin, &alen)) < 0) { -- dprintf(N_WARNING, "%s: recvfrom failed: %m", __func__); -+ XDR xdr; -+ struct sockaddr_in sin; -+ socklen_t alen = (socklen_t)sizeof(sin); -+ uint32_t xid; -+ -+ memset(msgbuf, 0, sizeof(msgbuf)); -+ msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, -+ (struct sockaddr *)(char *)&sin, &alen); -+ if (msglen == (ssize_t)-1) { -+ xlog_warn("%s: recvfrom failed: %m", __func__); - return NULL; - } - -- /* Create XDR object for decoding buffer */ -- xdrmem_create(xdrs, (caddr_t) msgbuf, msglen, XDR_DECODE); -- -- memset(&mesg, 0, sizeof(mesg)); -- mesg.rm_reply.rp_acpt.ar_results.where = NULL; -- mesg.rm_reply.rp_acpt.ar_results.proc = (xdrproc_t) xdr_void; -- -- if (!xdr_replymsg(xdrs, &mesg)) { -- note(N_WARNING, "%s: can't decode RPC message!", __func__); -+ memset(&xdr, 0, sizeof(xdr)); -+ xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); -+ xid = nsm_parse_reply(&xdr); -+ if (xid == 0) - goto done; -- } -+ if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { -+ struct in_addr addr = sin.sin_addr; -+ char buf[INET_ADDRSTRLEN]; - -- if (mesg.rm_reply.rp_stat != 0) { -- note(N_WARNING, "%s: [%s] RPC status %d", -- __func__, -- inet_ntoa(sin->sin_addr), -- mesg.rm_reply.rp_stat); -- goto done; -- } -- if (mesg.rm_reply.rp_acpt.ar_stat != 0) { -- note(N_WARNING, "%s: [%s] RPC status %d", -- __func__, -- inet_ntoa(sin->sin_addr), -- mesg.rm_reply.rp_acpt.ar_stat); -+ xlog_warn("%s: Unrecognized reply from %s", __func__, -+ inet_ntop(AF_INET, &addr, buf, -+ (socklen_t)sizeof(buf))); - goto done; - } - -@@ -217,32 +139,15 @@ recv_rply(struct sockaddr_in *sin, u_long *portp) - /* LH - this was a bug... it should have been checking - * the xid from the response message from the client, - * not the static, internal xid */ -- if (lp->xid != mesg.rm_xid) -+ if (lp->xid != xid) - continue; -- if (lp->addr.s_addr != sin->sin_addr.s_addr) { -- char addr [18]; -- strncpy (addr, inet_ntoa(lp->addr), -- sizeof (addr) - 1); -- addr [sizeof (addr) - 1] = '\0'; -- dprintf(N_WARNING, "%s: address mismatch: " -- "expected %s, got %s", __func__, -- addr, inet_ntoa(sin->sin_addr)); -- } -- if (lp->port == 0) { -- if (!xdr_u_long(xdrs, portp)) { -- note(N_WARNING, -- "%s: [%s] can't decode reply body!", -- __func__, -- inet_ntoa(sin->sin_addr)); -- lp = NULL; -- goto done; -- } -- } -+ if (lp->port == 0) -+ *portp = nsm_recv_getport(&xdr); - break; - } - - done: -- xdr_destroy(xdrs); -+ xdr_destroy(&xdr); - return lp; - } - -@@ -253,15 +158,10 @@ static int - process_entry(notify_list *lp) - { - struct sockaddr_in sin; -- struct status new_status; -- xdrproc_t func; -- void *objp; -- u_int32_t proc, vers, prog; --/* __u32 proc, vers, prog; */ - - if (NL_TIMES(lp) == 0) { -- note(N_DEBUG, "%s: Cannot notify %s, giving up.", -- __func__, inet_ntoa(NL_ADDR(lp))); -+ xlog(D_GENERAL, "%s: Cannot notify localhost, giving up", -+ __func__); - return 0; - } - -@@ -270,23 +170,31 @@ process_entry(notify_list *lp) - sin.sin_port = lp->port; - /* LH - moved address into switch */ - -- prog = NL_MY_PROG(lp); -- vers = NL_MY_VERS(lp); -- proc = NL_MY_PROC(lp); -- - /* __FORCE__ loopback for callbacks to lockd ... */ - /* Just in case we somehow ignored it thus far */ - sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - -- func = (xdrproc_t) xdr_status; -- objp = &new_status; -- new_status.mon_name = NL_MON_NAME(lp); -- new_status.state = NL_STATE(lp); -- memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE); -+ if (sin.sin_port == 0) -+ lp->xid = nsm_xmit_getport(sockfd, &sin, -+ (rpcprog_t)NL_MY_PROG(lp), -+ (rpcvers_t)NL_MY_VERS(lp)); -+ else { -+ struct mon m; -+ -+ memcpy(m.priv, NL_PRIV(lp), SM_PRIV_SIZE); - -- lp->xid = xmit_call(&sin, prog, vers, proc, func, objp); -- if (!lp->xid) { -- note(N_WARNING, "%s: failed to notify port %d", -+ m.mon_id.mon_name = NL_MON_NAME(lp); -+ m.mon_id.my_id.my_name = NULL; -+ m.mon_id.my_id.my_prog = NL_MY_PROG(lp); -+ m.mon_id.my_id.my_vers = NL_MY_VERS(lp); -+ m.mon_id.my_id.my_proc = NL_MY_PROC(lp); -+ -+ lp->xid = nsm_xmit_nlmcall(sockfd, -+ (struct sockaddr *)(char *)&sin, -+ (socklen_t)sizeof(sin), &m, NL_STATE(lp)); -+ } -+ if (lp->xid == 0) { -+ xlog_warn("%s: failed to notify port %d", - __func__, ntohs(lp->port)); - } - NL_TIMES(lp) -= 1; -@@ -300,14 +208,13 @@ process_entry(notify_list *lp) - int - process_reply(FD_SET_TYPE *rfds) - { -- struct sockaddr_in sin; - notify_list *lp; - u_long port; - - if (sockfd == -1 || !FD_ISSET(sockfd, rfds)) - return 0; - -- if (!(lp = recv_rply(&sin, &port))) -+ if (!(lp = recv_rply(&port))) - return 1; - - if (lp->port == 0) { -@@ -319,10 +226,10 @@ process_reply(FD_SET_TYPE *rfds) - nlist_insert_timer(¬ify, lp); - return 1; - } -- note(N_WARNING, "%s: [%s] service %d not registered", -- __func__, inet_ntoa(lp->addr), NL_MY_PROG(lp)); -+ xlog_warn("%s: service %d not registered on localhost", -+ __func__, NL_MY_PROG(lp)); - } else { -- dprintf(N_DEBUG, "%s: Callback to %s (for %d) succeeded.", -+ xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded", - __func__, NL_MY_NAME(lp), NL_MON_NAME(lp)); - } - nlist_free(¬ify, lp); -@@ -346,8 +253,8 @@ process_notify_list(void) - nlist_remove(¬ify, entry); - nlist_insert_timer(¬ify, entry); - } else { -- note(N_ERROR, -- "%s: Can't callback %s (%d,%d), giving up.", -+ xlog(L_ERROR, -+ "%s: Can't callback %s (%d,%d), giving up", - __func__, - NL_MY_NAME(entry), - NL_MY_PROG(entry), -diff --git a/utils/statd/simu.c b/utils/statd/simu.c -index a7ecb85..825e428 100644 ---- a/utils/statd/simu.c -+++ b/utils/statd/simu.c -@@ -8,8 +8,10 @@ - #include - #endif - -+#include - #include - -+#include "sockaddr.h" - #include "rpcmisc.h" - #include "statd.h" - #include "notlist.h" -@@ -19,32 +21,29 @@ extern void my_svc_exit (void); - - /* - * Services SM_SIMU_CRASH requests. -+ * -+ * Although the kernel contacts the statd service via only IPv4 -+ * transports, the statd service can receive other requests, such -+ * as SM_NOTIFY, from remote peers via IPv6. - */ - void * --sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp) -+sm_simu_crash_1_svc (__attribute__ ((unused)) void *argp, struct svc_req *rqstp) - { -- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); -+ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); -+ char buf[INET6_ADDRSTRLEN]; - static char *result = NULL; -- struct in_addr caller; - -- if (sin->sin_family != AF_INET) { -- note(N_WARNING, "Call to statd from non-AF_INET address"); -- goto failure; -- } -+ xlog(D_CALL, "Received SM_SIMU_CRASH"); - -- caller = sin->sin_addr; -- if (caller.s_addr != htonl(INADDR_LOOPBACK)) { -- note(N_WARNING, "Call to statd from non-local host %s", -- inet_ntoa(caller)); -- goto failure; -- } -+ if (!nfs_is_v4_loopback(sap)) -+ goto out_nonlocal; - -- if (ntohs(sin->sin_port) >= 1024) { -- note(N_WARNING, "Call to statd-simu-crash from unprivileged port"); -+ if ((int)nfs_get_port(sap) >= IPPORT_RESERVED) { -+ xlog_warn("SM_SIMU_CRASH call from unprivileged port"); - goto failure; - } - -- note (N_WARNING, "*** SIMULATING CRASH! ***"); -+ xlog_warn("*** SIMULATING CRASH! ***"); - my_svc_exit (); - - if (rtnl) -@@ -52,4 +51,10 @@ sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp) - - failure: - return ((void *)&result); -+ -+ out_nonlocal: -+ if (!statd_present_address(sap, buf, sizeof(buf))) -+ buf[0] = '\0'; -+ xlog_warn("SM_SIMU_CRASH call from non-local host %s", buf); -+ goto failure; - } -diff --git a/utils/statd/simulate.c b/utils/statd/simulate.c -index de8f1c9..4ed1468 100644 ---- a/utils/statd/simulate.c -+++ b/utils/statd/simulate.c -@@ -38,7 +38,9 @@ extern void svc_exit (void); - void - simulator (int argc, char **argv) - { -- log_enable (1); -+ xlog_stderr (1); -+ xlog_syslog (0); -+ xlog_open ("statd simulator"); - - if (argc == 2) - if (!strcasecmp (*argv, "crash")) -@@ -61,7 +63,7 @@ simulator (int argc, char **argv) - simulate_mon (*(&argv[1]), *(&argv[2]), *(&argv[3]), *(&argv[4]), - *(&argv[5])); - } -- die ("WTF? Give me something I can use!"); -+ xlog_err ("WTF? Give me something I can use!"); - } - - static void -@@ -72,11 +74,11 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy, - sm_stat_res *result; - mon mon; - -- dprintf (N_DEBUG, "Calling %s (as %s) to monitor %s", calling, as, -+ xlog (D_GENERAL, "Calling %s (as %s) to monitor %s", calling, as, - monitoring); - - if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) -- die ("%s", clnt_spcreateerror ("clnt_create")); -+ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); - - memcpy (mon.priv, fool, SM_PRIV_SIZE); - mon.mon_id.my_id.my_name = xstrdup (as); -@@ -87,16 +89,15 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy, - mon.mon_id.mon_name = monitoring; - - if (!(result = sm_mon_1 (&mon, client))) -- die ("%s", clnt_sperror (client, "sm_mon_1")); -+ xlog_err ("%s", clnt_sperror (client, "sm_mon_1")); - - free (mon.mon_id.my_id.my_name); - - if (result->res_stat != STAT_SUCC) { -- note (N_FATAL, "SM_MON request failed, state: %d", result->state); -- exit (0); -+ xlog_err ("SM_MON request failed, state: %d", result->state); - } else { -- dprintf (N_DEBUG, "SM_MON result successful, state: %d\n", result->state); -- dprintf (N_DEBUG, "Waiting for callback."); -+ xlog (D_GENERAL, "SM_MON result successful, state: %d\n", result->state); -+ xlog (D_GENERAL, "Waiting for callback"); - daemon_simulator (); - exit (0); - } -@@ -109,11 +110,11 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy) - sm_stat *result; - mon_id mon_id; - -- dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor %s", calling, as, -+ xlog (D_GENERAL, "Calling %s (as %s) to unmonitor %s", calling, as, - unmonitoring); - - if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) -- die ("%s", clnt_spcreateerror ("clnt_create")); -+ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); - - mon_id.my_id.my_name = xstrdup (as); - mon_id.my_id.my_prog = atoi (proggy) * SIM_SM_PROG; -@@ -122,10 +123,10 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy) - mon_id.mon_name = unmonitoring; - - if (!(result = sm_unmon_1 (&mon_id, client))) -- die ("%s", clnt_sperror (client, "sm_unmon_1")); -+ xlog_err ("%s", clnt_sperror (client, "sm_unmon_1")); - - free (mon_id.my_id.my_name); -- dprintf (N_DEBUG, "SM_UNMON request returned state: %d\n", result->state); -+ xlog (D_GENERAL, "SM_UNMON request returned state: %d\n", result->state); - exit (0); - } - -@@ -136,10 +137,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy) - sm_stat *result; - my_id my_id; - -- dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor all hosts", calling, as); -+ xlog (D_GENERAL, "Calling %s (as %s) to unmonitor all hosts", calling, as); - - if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) -- die ("%s", clnt_spcreateerror ("clnt_create")); -+ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); - - my_id.my_name = xstrdup (as); - my_id.my_prog = atoi (proggy) * SIM_SM_PROG; -@@ -147,10 +148,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy) - my_id.my_proc = SIM_SM_MON; - - if (!(result = sm_unmon_all_1 (&my_id, client))) -- die ("%s", clnt_sperror (client, "sm_unmon_all_1")); -+ xlog_err ("%s", clnt_sperror (client, "sm_unmon_all_1")); - - free (my_id.my_name); -- dprintf (N_DEBUG, "SM_UNMON_ALL request returned state: %d\n", result->state); -+ xlog (D_GENERAL, "SM_UNMON_ALL request returned state: %d\n", result->state); - exit (0); - } - -@@ -160,10 +161,10 @@ simulate_crash (char *host) - CLIENT *client; - - if ((client = clnt_create (host, SM_PROG, SM_VERS, "udp")) == NULL) -- die ("%s", clnt_spcreateerror ("clnt_create")); -+ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); - - if (!sm_simu_crash_1 (NULL, client)) -- die ("%s", clnt_sperror (client, "sm_simu_crash_1")); -+ xlog_err ("%s", clnt_sperror (client, "sm_simu_crash_1")); - - exit (0); - } -@@ -176,18 +177,18 @@ simulate_stat (char *calling, char *monitoring) - sm_stat_res *result; - - if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) -- die ("%s", clnt_spcreateerror ("clnt_create")); -+ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); - - checking.mon_name = monitoring; - - if (!(result = sm_stat_1 (&checking, client))) -- die ("%s", clnt_sperror (client, "sm_stat_1")); -+ xlog_err ("%s", clnt_sperror (client, "sm_stat_1")); - - if (result->res_stat == STAT_SUCC) -- dprintf (N_DEBUG, "STAT_SUCC from %s for %s, state: %d", calling, -+ xlog (D_GENERAL, "STAT_SUCC from %s for %s, state: %d", calling, - monitoring, result->state); - else -- dprintf (N_DEBUG, "STAT_FAIL from %s for %s, state: %d", calling, -+ xlog (D_GENERAL, "STAT_FAIL from %s for %s, state: %d", calling, - monitoring, result->state); - - exit (0); -@@ -196,9 +197,8 @@ simulate_stat (char *calling, char *monitoring) - static void - sim_killer (int sig) - { -- note (N_FATAL, "Simulator caught signal %d, un-registering and exiting.", sig); - pmap_unset (sim_port, SIM_SM_VERS); -- exit (0); -+ xlog_err ("Simulator caught signal %d, un-registering and exiting", sig); - } - - static void -@@ -219,7 +219,7 @@ sim_sm_mon_1_svc (struct status *argp, struct svc_req *rqstp) - { - static char *result; - -- dprintf (N_DEBUG, "Recieved state %d for mon_name %s (opaque \"%s\")", -+ xlog (D_GENERAL, "Recieved state %d for mon_name %s (opaque \"%s\")", - argp->state, argp->mon_name, argp->priv); - svc_exit (); - return ((void *)&result); -diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c -index 72dcff4..3259a3e 100644 ---- a/utils/statd/sm-notify.c -+++ b/utils/statd/sm-notify.c -@@ -8,6 +8,7 @@ - #include - #endif - -+#include - #include - #include - #include -@@ -28,129 +29,114 @@ - #include - #include - --#ifndef BASEDIR --# ifdef NFS_STATEDIR --# define BASEDIR NFS_STATEDIR --# else --# define BASEDIR "/var/lib/nfs" --# endif --#endif -- --#define DEFAULT_SM_STATE_PATH BASEDIR "/state" --#define DEFAULT_SM_DIR_PATH BASEDIR "/sm" --#define DEFAULT_SM_BAK_PATH DEFAULT_SM_DIR_PATH ".bak" -+#include "sockaddr.h" -+#include "xlog.h" -+#include "nsm.h" -+#include "nfsrpc.h" - --char *_SM_BASE_PATH = BASEDIR; --char *_SM_STATE_PATH = DEFAULT_SM_STATE_PATH; --char *_SM_DIR_PATH = DEFAULT_SM_DIR_PATH; --char *_SM_BAK_PATH = DEFAULT_SM_BAK_PATH; -+#ifndef HAVE_DECL_AI_ADDRCONFIG -+#define AI_ADDRCONFIG 0 -+#endif - --#define NSM_PROG 100024 --#define NSM_PROGRAM 100024 --#define NSM_VERSION 1 - #define NSM_TIMEOUT 2 --#define NSM_NOTIFY 6 - #define NSM_MAX_TIMEOUT 120 /* don't make this too big */ --#define MAXMSGSIZE 256 - - struct nsm_host { - struct nsm_host * next; - char * name; -- char * path; -- struct sockaddr_storage addr; -+ char * mon_name; -+ char * my_name; - struct addrinfo *ai; - time_t last_used; - time_t send_next; - unsigned int timeout; - unsigned int retries; -- unsigned int xid; -+ uint32_t xid; - }; - - static char nsm_hostname[256]; --static uint32_t nsm_state; -+static int nsm_state; -+static int nsm_family = AF_INET; - static int opt_debug = 0; --static int opt_quiet = 0; --static int opt_update_state = 1; -+static _Bool opt_update_state = true; - static unsigned int opt_max_retry = 15 * 60; --static char * opt_srcaddr = 0; --static uint16_t opt_srcport = 0; --static int log_syslog = 0; -+static char * opt_srcaddr = NULL; -+static char * opt_srcport = NULL; - --static unsigned int nsm_get_state(int); --static void notify(void); -+static void notify(const int sock); - static int notify_host(int, struct nsm_host *); - static void recv_reply(int); --static void backup_hosts(const char *, const char *); --static void get_hosts(const char *); - static void insert_host(struct nsm_host *); - static struct nsm_host *find_host(uint32_t); --static void nsm_log(int fac, const char *fmt, ...); - static int record_pid(void); --static void drop_privs(void); --static void set_kernel_nsm_state(int state); - - static struct nsm_host * hosts = NULL; - --/* -- * Address handling utilities -- */ -- --static unsigned short smn_get_port(const struct sockaddr *sap) -+__attribute_malloc__ -+static struct addrinfo * -+smn_lookup(const char *name) - { -- switch (sap->sa_family) { -- case AF_INET: -- return ntohs(((struct sockaddr_in *)sap)->sin_port); -- case AF_INET6: -- return ntohs(((struct sockaddr_in6 *)sap)->sin6_port); -- } -- return 0; --} -+ struct addrinfo *ai = NULL; -+ struct addrinfo hint = { -+ .ai_flags = AI_ADDRCONFIG, -+ .ai_family = (nsm_family == AF_INET ? AF_INET: AF_UNSPEC), -+ .ai_protocol = (int)IPPROTO_UDP, -+ }; -+ int error; - --static void smn_set_port(struct sockaddr *sap, const unsigned short port) --{ -- switch (sap->sa_family) { -- case AF_INET: -- ((struct sockaddr_in *)sap)->sin_port = htons(port); -- break; -- case AF_INET6: -- ((struct sockaddr_in6 *)sap)->sin6_port = htons(port); -- break; -+ error = getaddrinfo(name, NULL, &hint, &ai); -+ if (error != 0) { -+ xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error)); -+ return NULL; - } -+ -+ return ai; - } - --static struct addrinfo *smn_lookup(const char *name) -+__attribute_malloc__ -+static struct nsm_host * -+smn_alloc_host(const char *hostname, const char *mon_name, -+ const char *my_name, const time_t timestamp) - { -- struct addrinfo *ai, hint = { --#if HAVE_DECL_AI_ADDRCONFIG -- .ai_flags = AI_ADDRCONFIG, --#endif /* HAVE_DECL_AI_ADDRCONFIG */ -- .ai_family = AF_INET, -- .ai_protocol = IPPROTO_UDP, -- }; -- int error; -+ struct nsm_host *host; - -- error = getaddrinfo(name, NULL, &hint, &ai); -- switch (error) { -- case 0: -- return ai; -- case EAI_SYSTEM: -- if (opt_debug) -- nsm_log(LOG_ERR, "getaddrinfo(3): %s", -- strerror(errno)); -- break; -- default: -- if (opt_debug) -- nsm_log(LOG_ERR, "getaddrinfo(3): %s", -- gai_strerror(error)); -+ host = calloc(1, sizeof(*host)); -+ if (host == NULL) -+ goto out_nomem; -+ -+ host->name = strdup(hostname); -+ host->mon_name = strdup(mon_name); -+ host->my_name = strdup(my_name); -+ if (host->name == NULL || -+ host->mon_name == NULL || -+ host->my_name == NULL) { -+ free(host->my_name); -+ free(host->mon_name); -+ free(host->name); -+ free(host); -+ goto out_nomem; - } - -+ host->last_used = timestamp; -+ host->timeout = NSM_TIMEOUT; -+ host->retries = 100; /* force address retry */ -+ -+ return host; -+ -+out_nomem: -+ xlog_warn("Unable to allocate memory"); - return NULL; - } - - static void smn_forget_host(struct nsm_host *host) - { -- unlink(host->path); -- free(host->path); -+ xlog(D_CALL, "Removing %s (%s, %s) from notify list", -+ host->name, host->mon_name, host->my_name); -+ -+ nsm_delete_notified_host(host->name, host->mon_name, host->my_name); -+ -+ free(host->my_name); -+ free(host->mon_name); - free(host->name); - if (host->ai) - freeaddrinfo(host->ai); -@@ -158,13 +144,219 @@ static void smn_forget_host(struct nsm_host *host) - free(host); - } - -+static unsigned int -+smn_get_host(const char *hostname, -+ __attribute__ ((unused)) const struct sockaddr *sap, -+ const struct mon *m, const time_t timestamp) -+{ -+ struct nsm_host *host; -+ -+ host = smn_alloc_host(hostname, -+ m->mon_id.mon_name, m->mon_id.my_id.my_name, timestamp); -+ if (host == NULL) -+ return 0; -+ -+ insert_host(host); -+ xlog(D_GENERAL, "Added host %s to notify list", hostname); -+ return 1; -+} -+ -+#ifdef IPV6_SUPPORTED -+static int smn_socket(void) -+{ -+ int sock; -+ -+ /* -+ * Use an AF_INET socket if IPv6 is disabled on the -+ * local system. -+ */ -+ sock = socket(AF_INET6, SOCK_DGRAM, 0); -+ if (sock == -1) { -+ if (errno != EAFNOSUPPORT) { -+ xlog(L_ERROR, "Failed to create RPC socket: %m"); -+ return -1; -+ } -+ sock = socket(AF_INET, SOCK_DGRAM, 0); -+ if (sock < 0) { -+ xlog(L_ERROR, "Failed to create RPC socket: %m"); -+ return -1; -+ } -+ } else -+ nsm_family = AF_INET6; -+ -+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { -+ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); -+ goto out_close; -+ } -+ -+ /* -+ * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4. However, -+ * since sm-notify open-codes all of its RPC support, it can -+ * use a single socket and let the local network stack provide -+ * the correct mapping between address families automatically. -+ * This is the same thing that is done in the kernel. -+ */ -+ if (nsm_family == AF_INET6) { -+ const int zero = 0; -+ socklen_t zerolen = (socklen_t)sizeof(zero); -+ -+ if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, -+ (char *)&zero, zerolen) == -1) { -+ xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m"); -+ goto out_close; -+ } -+ } -+ -+ return sock; -+ -+out_close: -+ (void)close(sock); -+ return -1; -+} -+#else /* !IPV6_SUPPORTED */ -+static int smn_socket(void) -+{ -+ int sock; -+ -+ sock = socket(AF_INET, SOCK_DGRAM, 0); -+ if (sock == -1) { -+ xlog(L_ERROR, "Failed to create RPC socket: %m"); -+ return -1; -+ } -+ -+ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { -+ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); -+ (void)close(sock); -+ return -1; -+ } -+ -+ return sock; -+} -+#endif /* !IPV6_SUPPORTED */ -+ -+/* -+ * If admin specified a source address or srcport, then convert those -+ * to a sockaddr and return it. Otherwise, return an ANYADDR address. -+ */ -+__attribute_malloc__ -+static struct addrinfo * -+smn_bind_address(const char *srcaddr, const char *srcport) -+{ -+ struct addrinfo *ai = NULL; -+ struct addrinfo hint = { -+ .ai_flags = AI_NUMERICSERV, -+ .ai_family = nsm_family, -+ .ai_protocol = (int)IPPROTO_UDP, -+ }; -+ int error; -+ -+ if (srcaddr == NULL) -+ hint.ai_flags |= AI_PASSIVE; -+ -+ if (srcport == NULL) -+ error = getaddrinfo(srcaddr, "", &hint, &ai); -+ else -+ error = getaddrinfo(srcaddr, srcport, &hint, &ai); -+ if (error != 0) { -+ xlog(L_ERROR, -+ "Invalid bind address or port for RPC socket: %s", -+ gai_strerror(error)); -+ return NULL; -+ } -+ -+ return ai; -+} -+ -+#ifdef HAVE_LIBTIRPC -+static int -+smn_bindresvport(int sock, struct sockaddr *sap) -+{ -+ return bindresvport_sa(sock, sap); -+} -+ -+#else /* !HAVE_LIBTIRPC */ -+static int -+smn_bindresvport(int sock, struct sockaddr *sap) -+{ -+ if (sap->sa_family != AF_INET) { -+ errno = EAFNOSUPPORT; -+ return -1; -+ } -+ -+ return bindresvport(sock, (struct sockaddr_in *)(char *)sap); -+} -+#endif /* !HAVE_LIBTIRPC */ -+ -+/* -+ * Prepare a socket for sending RPC requests -+ * -+ * Returns a bound datagram socket file descriptor, or -1 if -+ * an error occurs. -+ */ -+static int -+smn_create_socket(const char *srcaddr, const char *srcport) -+{ -+ int sock, retry_cnt = 0; -+ struct addrinfo *ai; -+ -+retry: -+ sock = smn_socket(); -+ if (sock == -1) -+ return -1; -+ -+ ai = smn_bind_address(srcaddr, srcport); -+ if (ai == NULL) { -+ (void)close(sock); -+ return -1; -+ } -+ -+ /* Use source port if provided on the command line, -+ * otherwise use bindresvport */ -+ if (srcport) { -+ if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { -+ xlog(L_ERROR, "Failed to bind RPC socket: %m"); -+ freeaddrinfo(ai); -+ (void)close(sock); -+ return -1; -+ } -+ } else { -+ struct servent *se; -+ -+ if (smn_bindresvport(sock, ai->ai_addr) == -1) { -+ xlog(L_ERROR, -+ "bindresvport on RPC socket failed: %m"); -+ freeaddrinfo(ai); -+ (void)close(sock); -+ return -1; -+ } -+ -+ /* try to avoid known ports */ -+ se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp"); -+ if (se != NULL && retry_cnt < 100) { -+ retry_cnt++; -+ freeaddrinfo(ai); -+ (void)close(sock); -+ goto retry; -+ } -+ } -+ -+ freeaddrinfo(ai); -+ return sock; -+} -+ - int - main(int argc, char **argv) - { -- int c; -- int force = 0; -+ int c, sock, force = 0; -+ char * progname; - -- while ((c = getopt(argc, argv, "dm:np:v:qP:f")) != -1) { -+ progname = strrchr(argv[0], '/'); -+ if (progname != NULL) -+ progname++; -+ else -+ progname = argv[0]; -+ -+ while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) { - switch (c) { - case 'f': - force = 1; -@@ -176,32 +368,17 @@ main(int argc, char **argv) - opt_max_retry = atoi(optarg) * 60; - break; - case 'n': -- opt_update_state = 0; -+ opt_update_state = false; - break; - case 'p': -- opt_srcport = atoi(optarg); -+ opt_srcport = optarg; - break; - case 'v': - opt_srcaddr = optarg; - break; -- case 'q': -- opt_quiet = 1; -- break; - case 'P': -- _SM_BASE_PATH = strdup(optarg); -- _SM_STATE_PATH = malloc(strlen(optarg)+1+sizeof("state")); -- _SM_DIR_PATH = malloc(strlen(optarg)+1+sizeof("sm")); -- _SM_BAK_PATH = malloc(strlen(optarg)+1+sizeof("sm.bak")); -- if (_SM_BASE_PATH == NULL || -- _SM_STATE_PATH == NULL || -- _SM_DIR_PATH == NULL || -- _SM_BAK_PATH == NULL) { -- nsm_log(LOG_ERR, "unable to allocate memory"); -+ if (!nsm_setup_pathnames(argv[0], optarg)) - exit(1); -- } -- strcat(strcpy(_SM_STATE_PATH, _SM_BASE_PATH), "/state"); -- strcat(strcpy(_SM_DIR_PATH, _SM_BASE_PATH), "/sm"); -- strcat(strcpy(_SM_BAK_PATH, _SM_BASE_PATH), "/sm.bak"); - break; - - default: -@@ -211,18 +388,26 @@ main(int argc, char **argv) - - if (optind < argc) { - usage: fprintf(stderr, -- "Usage: sm-notify [-dfq] [-m max-retry-minutes] [-p srcport]\n" -- " [-P /path/to/state/directory] [-v my_host_name]\n"); -+ "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n" -+ " [-P /path/to/state/directory] [-v my_host_name]\n", -+ progname); - exit(1); - } - -- log_syslog = 1; -- openlog("sm-notify", LOG_PID, LOG_DAEMON); -+ xlog_syslog(1); -+ if (opt_debug) { -+ xlog_stderr(1); -+ xlog_config(D_ALL, 1); -+ } else -+ xlog_stderr(0); -+ -+ xlog_open(progname); -+ xlog(L_NOTICE, "Version " VERSION " starting"); - -- if (strcmp(_SM_BASE_PATH, BASEDIR) == 0) { -- if (record_pid() == 0 && force == 0 && opt_update_state == 1) { -+ if (nsm_is_default_parentdir()) { -+ if (record_pid() == 0 && force == 0 && opt_update_state) { - /* already run, don't try again */ -- nsm_log(LOG_NOTICE, "Already notifying clients; Exiting!"); -+ xlog(L_NOTICE, "Already notifying clients; Exiting!"); - exit(0); - } - } -@@ -231,31 +416,26 @@ usage: fprintf(stderr, - strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1); - } else - if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) { -- nsm_log(LOG_ERR, "Failed to obtain name of local host: %s", -- strerror(errno)); -+ xlog(L_ERROR, "Failed to obtain name of local host: %m"); - exit(1); - } - -- backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH); -- get_hosts(_SM_BAK_PATH); -- -- /* If there are not hosts to notify, just exit */ -- if (!hosts) { -- nsm_log(LOG_DEBUG, "No hosts to notify; exiting"); -+ (void)nsm_retire_monitored_hosts(); -+ if (nsm_load_notify_list(smn_get_host) == 0) { -+ xlog(D_GENERAL, "No hosts to notify; exiting"); - return 0; - } - -- /* Get and update the NSM state. This will call sync() */ - nsm_state = nsm_get_state(opt_update_state); -- set_kernel_nsm_state(nsm_state); -+ if (nsm_state == 0) -+ exit(1); -+ nsm_update_kernel_state(nsm_state); - - if (!opt_debug) { -- if (!opt_quiet) -- printf("Backgrounding to notify hosts...\n"); -+ xlog(L_NOTICE, "Backgrounding to notify hosts...\n"); - - if (daemon(0, 0) < 0) { -- nsm_log(LOG_ERR, "unable to background: %s", -- strerror(errno)); -+ xlog(L_ERROR, "unable to background: %m"); - exit(1); - } - -@@ -264,15 +444,21 @@ usage: fprintf(stderr, - close(2); - } - -- notify(); -+ sock = smn_create_socket(opt_srcaddr, opt_srcport); -+ if (sock == -1) -+ exit(1); -+ -+ if (!nsm_drop_privileges(-1)) -+ exit(1); -+ -+ notify(sock); - - if (hosts) { - struct nsm_host *hp; - - while ((hp = hosts) != 0) { - hosts = hp->next; -- nsm_log(LOG_NOTICE, -- "Unable to notify %s, giving up", -+ xlog(L_NOTICE, "Unable to notify %s, giving up", - hp->name); - } - exit(1); -@@ -285,69 +471,13 @@ usage: fprintf(stderr, - * Notify hosts - */ - static void --notify(void) -+notify(const int sock) - { -- struct sockaddr_storage address; -- struct sockaddr *local_addr = (struct sockaddr *)&address; - time_t failtime = 0; -- int sock = -1; -- int retry_cnt = 0; -- -- retry: -- sock = socket(AF_INET, SOCK_DGRAM, 0); -- if (sock < 0) { -- nsm_log(LOG_ERR, "Failed to create RPC socket: %s", -- strerror(errno)); -- exit(1); -- } -- fcntl(sock, F_SETFL, O_NONBLOCK); -- -- memset(&address, 0, sizeof(address)); -- local_addr->sa_family = AF_INET; /* Default to IPv4 */ -- -- /* Bind source IP if provided on command line */ -- if (opt_srcaddr) { -- struct addrinfo *ai = smn_lookup(opt_srcaddr); -- if (!ai) { -- nsm_log(LOG_ERR, -- "Not a valid hostname or address: \"%s\"", -- opt_srcaddr); -- exit(1); -- } -- -- /* We know it's IPv4 at this point */ -- memcpy(local_addr, ai->ai_addr, ai->ai_addrlen); -- -- freeaddrinfo(ai); -- } -- -- /* Use source port if provided on the command line, -- * otherwise use bindresvport */ -- if (opt_srcport) { -- smn_set_port(local_addr, opt_srcport); -- if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) { -- nsm_log(LOG_ERR, "Failed to bind RPC socket: %s", -- strerror(errno)); -- exit(1); -- } -- } else { -- struct servent *se; -- struct sockaddr_in *sin = (struct sockaddr_in *)local_addr; -- (void) bindresvport(sock, sin); -- /* try to avoid known ports */ -- se = getservbyport(sin->sin_port, "udp"); -- if (se && retry_cnt < 100) { -- retry_cnt++; -- close(sock); -- goto retry; -- } -- } - - if (opt_max_retry) - failtime = time(NULL) + opt_max_retry; - -- drop_privs(); -- - while (hosts) { - struct pollfd pfd; - time_t now = time(NULL); -@@ -383,7 +513,7 @@ notify(void) - if (hosts == NULL) - return; - -- nsm_log(LOG_DEBUG, "Host %s due in %ld seconds", -+ xlog(D_GENERAL, "Host %s due in %ld seconds", - hosts->name, wait); - - pfd.fd = sock; -@@ -405,34 +535,18 @@ notify(void) - static int - notify_host(int sock, struct nsm_host *host) - { -- struct sockaddr_storage address; -- struct sockaddr *dest = (struct sockaddr *)&address; -- socklen_t destlen = sizeof(address); -- static unsigned int xid = 0; -- uint32_t msgbuf[MAXMSGSIZE], *p; -- unsigned int len; -- -- if (!xid) -- xid = getpid() + time(NULL); -- if (!host->xid) -- host->xid = xid++; -+ struct sockaddr *sap; -+ socklen_t salen; - - if (host->ai == NULL) { - host->ai = smn_lookup(host->name); - if (host->ai == NULL) { -- nsm_log(LOG_WARNING, -- "DNS resolution of %s failed; " -+ xlog_warn("DNS resolution of %s failed; " - "retrying later", host->name); - return 0; - } - } - -- memset(msgbuf, 0, sizeof(msgbuf)); -- p = msgbuf; -- *p++ = htonl(host->xid); -- *p++ = 0; -- *p++ = htonl(2); -- - /* If we retransmitted 4 times, reset the port to force - * a new portmap lookup (in case statd was restarted). - * We also rotate through multiple IP addresses at this -@@ -440,10 +554,7 @@ notify_host(int sock, struct nsm_host *host) - */ - if (host->retries >= 4) { - /* don't rotate if there is only one addrinfo */ -- if (host->ai->ai_next == NULL) -- memcpy(&host->addr, host->ai->ai_addr, -- host->ai->ai_addrlen); -- else { -+ if (host->ai->ai_next != NULL) { - struct addrinfo *first = host->ai; - struct addrinfo **next = &host->ai; - -@@ -456,213 +567,100 @@ notify_host(int sock, struct nsm_host *host) - next = & (*next)->ai_next; - /* put first entry at end */ - *next = first; -- memcpy(&host->addr, first->ai_addr, -- first->ai_addrlen); - } - -- smn_set_port((struct sockaddr *)&host->addr, 0); -+ nfs_set_port(host->ai->ai_addr, 0); - host->retries = 0; - } - -- memcpy(dest, &host->addr, destlen); -- if (smn_get_port(dest) == 0) { -- /* Build a PMAP packet */ -- nsm_log(LOG_DEBUG, "Sending portmap query to %s", host->name); -+ sap = host->ai->ai_addr; -+ salen = host->ai->ai_addrlen; - -- smn_set_port(dest, 111); -- *p++ = htonl(100000); -- *p++ = htonl(2); -- *p++ = htonl(3); -- -- /* Auth and verf */ -- *p++ = 0; *p++ = 0; -- *p++ = 0; *p++ = 0; -- -- *p++ = htonl(NSM_PROGRAM); -- *p++ = htonl(NSM_VERSION); -- *p++ = htonl(IPPROTO_UDP); -- *p++ = 0; -- } else { -- /* Build an SM_NOTIFY packet */ -- nsm_log(LOG_DEBUG, "Sending SM_NOTIFY to %s", host->name); -- -- *p++ = htonl(NSM_PROGRAM); -- *p++ = htonl(NSM_VERSION); -- *p++ = htonl(NSM_NOTIFY); -- -- /* Auth and verf */ -- *p++ = 0; *p++ = 0; -- *p++ = 0; *p++ = 0; -- -- /* state change */ -- len = strlen(nsm_hostname); -- *p++ = htonl(len); -- memcpy(p, nsm_hostname, len); -- p += (len + 3) >> 2; -- *p++ = htonl(nsm_state); -- } -- len = (p - msgbuf) << 2; -- -- if (sendto(sock, msgbuf, len, 0, dest, destlen) < 0) -- nsm_log(LOG_WARNING, "Sending Reboot Notification to " -- "'%s' failed: errno %d (%s)", host->name, errno, strerror(errno)); -+ if (nfs_get_port(sap) == 0) -+ 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); - - return 0; - } - - /* -- * Receive reply from remote host -+ * Extract the returned port number and set up the SM_NOTIFY call. - */ - static void --recv_reply(int sock) -+recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr) - { -- struct nsm_host *hp; -- struct sockaddr *sap; -- uint32_t msgbuf[MAXMSGSIZE], *p, *end; -- uint32_t xid; -- int res; -- -- res = recv(sock, msgbuf, sizeof(msgbuf), 0); -- if (res < 0) -- return; -- -- nsm_log(LOG_DEBUG, "Received packet..."); -+ uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr); - -- p = msgbuf; -- end = p + (res >> 2); -- -- xid = ntohl(*p++); -- if (*p++ != htonl(1) /* must be REPLY */ -- || *p++ != htonl(0) /* must be ACCEPTED */ -- || *p++ != htonl(0) /* must be NULL verifier */ -- || *p++ != htonl(0) -- || *p++ != htonl(0)) /* must be SUCCESS */ -- return; -+ host->send_next = time(NULL); -+ host->xid = 0; - -- /* Before we look at the data, find the host struct for -- this reply */ -- if ((hp = find_host(xid)) == NULL) -- return; -- sap = (struct sockaddr *)&hp->addr; -- -- if (smn_get_port(sap) == 0) { -- /* This was a portmap request */ -- unsigned int port; -- -- port = ntohl(*p++); -- if (p > end) -- goto fail; -- -- hp->send_next = time(NULL); -- if (port == 0) { -- /* No binding for statd. Delay the next -- * portmap query for max timeout */ -- nsm_log(LOG_DEBUG, "No statd on %s", hp->name); -- hp->timeout = NSM_MAX_TIMEOUT; -- hp->send_next += NSM_MAX_TIMEOUT; -- } else { -- smn_set_port(sap, port); -- if (hp->timeout >= NSM_MAX_TIMEOUT / 4) -- hp->timeout = NSM_MAX_TIMEOUT / 4; -- } -- hp->xid = 0; -+ if (port == 0) { -+ /* No binding for statd... */ -+ xlog(D_GENERAL, "No statd on host %s", host->name); -+ host->timeout = NSM_MAX_TIMEOUT; -+ host->send_next += NSM_MAX_TIMEOUT; - } else { -- /* Successful NOTIFY call. Server returns void, -- * so nothing we need to do here (except -- * check that we didn't read past the end of the -- * packet) -- */ -- if (p <= end) { -- nsm_log(LOG_DEBUG, "Host %s notified successfully", -- hp->name); -- smn_forget_host(hp); -- return; -- } -+ nfs_set_port(sap, port); -+ if (host->timeout >= NSM_MAX_TIMEOUT / 4) -+ host->timeout = NSM_MAX_TIMEOUT / 4; - } - --fail: /* Re-insert the host */ -- insert_host(hp); -+ insert_host(host); - } - - /* -- * Back up all hosts from the sm directory to sm.bak -+ * Successful NOTIFY call. Server returns void, so nothing -+ * we need to do here. - */ - static void --backup_hosts(const char *dirname, const char *bakname) -+recv_notify_reply(struct nsm_host *host) - { -- struct dirent *de; -- DIR *dir; -- -- if (!(dir = opendir(dirname))) { -- nsm_log(LOG_WARNING, -- "Failed to open %s: %s", dirname, strerror(errno)); -- return; -- } -+ xlog(D_GENERAL, "Host %s notified successfully", host->name); - -- while ((de = readdir(dir)) != NULL) { -- char src[1024], dst[1024]; -- -- if (de->d_name[0] == '.') -- continue; -- -- snprintf(src, sizeof(src), "%s/%s", dirname, de->d_name); -- snprintf(dst, sizeof(dst), "%s/%s", bakname, de->d_name); -- if (rename(src, dst) < 0) { -- nsm_log(LOG_WARNING, -- "Failed to rename %s -> %s: %m", -- src, dst); -- } -- } -- closedir(dir); -+ smn_forget_host(host); - } - - /* -- * Get all entries from sm.bak and convert them to host entries -+ * Receive reply from remote host - */ - static void --get_hosts(const char *dirname) -+recv_reply(int sock) - { -- struct nsm_host *host; -- struct dirent *de; -- DIR *dir; -+ struct nsm_host *hp; -+ struct sockaddr *sap; -+ char msgbuf[NSM_MAXMSGSIZE]; -+ uint32_t xid; -+ ssize_t msglen; -+ XDR xdr; - -- if (!(dir = opendir(dirname))) { -- nsm_log(LOG_WARNING, -- "Failed to open %s: %s", dirname, strerror(errno)); -+ memset(msgbuf, 0 , sizeof(msgbuf)); -+ msglen = recv(sock, msgbuf, sizeof(msgbuf), 0); -+ if (msglen < 0) - return; -- } -- -- host = NULL; -- while ((de = readdir(dir)) != NULL) { -- struct stat stb; -- char path[1024]; - -- if (de->d_name[0] == '.') -- continue; -- if (host == NULL) -- host = calloc(1, sizeof(*host)); -- if (host == NULL) { -- nsm_log(LOG_WARNING, "Unable to allocate memory"); -- return; -- } -+ xlog(D_GENERAL, "Received packet..."); - -- snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name); -- if (stat(path, &stb) < 0) -- continue; -+ memset(&xdr, 0, sizeof(xdr)); -+ xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); -+ xid = nsm_parse_reply(&xdr); -+ if (xid == 0) -+ goto out; - -- host->last_used = stb.st_mtime; -- host->timeout = NSM_TIMEOUT; -- host->path = strdup(path); -- host->name = strdup(de->d_name); -- host->retries = 100; /* force address retry */ -+ /* Before we look at the data, find the host struct for -+ this reply */ -+ if ((hp = find_host(xid)) == NULL) -+ goto out; - -- insert_host(host); -- host = NULL; -- } -- closedir(dir); -+ sap = hp->ai->ai_addr; -+ if (nfs_get_port(sap) == 0) -+ recv_rpcbind_reply(sap, hp, &xdr); -+ else -+ recv_notify_reply(hp); - -- if (host) -- free(host); -+out: -+ xdr_destroy(&xdr); - } - - /* -@@ -712,84 +710,6 @@ find_host(uint32_t xid) - return NULL; - } - -- --/* -- * Retrieve the current NSM state -- */ --static unsigned int --nsm_get_state(int update) --{ -- char newfile[PATH_MAX]; -- int fd, state; -- -- if ((fd = open(_SM_STATE_PATH, O_RDONLY)) < 0) { -- if (!opt_quiet) { -- nsm_log(LOG_WARNING, "%s: %m", _SM_STATE_PATH); -- nsm_log(LOG_WARNING, "Creating %s, set initial state 1", -- _SM_STATE_PATH); -- } -- state = 1; -- update = 1; -- } else { -- if (read(fd, &state, sizeof(state)) != sizeof(state)) { -- nsm_log(LOG_WARNING, -- "%s: bad file size, setting state = 1", -- _SM_STATE_PATH); -- state = 1; -- update = 1; -- } else { -- if (!(state & 1)) -- state += 1; -- } -- close(fd); -- } -- -- if (update) { -- state += 2; -- snprintf(newfile, sizeof(newfile), -- "%s.new", _SM_STATE_PATH); -- if ((fd = open(newfile, O_CREAT|O_WRONLY, 0644)) < 0) { -- nsm_log(LOG_ERR, "Cannot create %s: %m", newfile); -- exit(1); -- } -- if (write(fd, &state, sizeof(state)) != sizeof(state)) { -- nsm_log(LOG_ERR, -- "Failed to write state to %s", newfile); -- exit(1); -- } -- close(fd); -- if (rename(newfile, _SM_STATE_PATH) < 0) { -- nsm_log(LOG_ERR, -- "Cannot create %s: %m", _SM_STATE_PATH); -- exit(1); -- } -- sync(); -- } -- -- return state; --} -- --/* -- * Log a message -- */ --static void --nsm_log(int fac, const char *fmt, ...) --{ -- va_list ap; -- -- if (fac == LOG_DEBUG && !opt_debug) -- return; -- -- va_start(ap, fmt); -- if (log_syslog) -- vsyslog(fac, fmt, ap); -- else { -- vfprintf(stderr, fmt, ap); -- fputs("\n", stderr); -- } -- va_end(ap); --} -- - /* - * Record pid in /var/run/sm-notify.pid - * This file should remain until a reboot, even if the -@@ -799,61 +719,20 @@ nsm_log(int fac, const char *fmt, ...) - static int record_pid(void) - { - char pid[20]; -+ ssize_t len; - int fd; - -- snprintf(pid, 20, "%d\n", getpid()); -+ (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid()); - fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600); - if (fd < 0) - return 0; -- if (write(fd, pid, strlen(pid)) != strlen(pid)) { -- nsm_log(LOG_WARNING, "Writing to pid file failed: errno %d(%s)", -- errno, strerror(errno)); -- } -- close(fd); -- return 1; --} - --/* Drop privileges to match owner of state-directory -- * (in case a reply triggers some unknown bug). -- */ --static void drop_privs(void) --{ -- struct stat st; -- -- if (stat(_SM_DIR_PATH, &st) == -1 && -- stat(_SM_BASE_PATH, &st) == -1) { -- st.st_uid = 0; -- st.st_gid = 0; -- } -- -- if (st.st_uid == 0) { -- nsm_log(LOG_WARNING, -- "sm-notify running as root. chown %s to choose different user", -- _SM_DIR_PATH); -- return; -- } -- -- setgroups(0, NULL); -- if (setgid(st.st_gid) == -1 -- || setuid(st.st_uid) == -1) { -- nsm_log(LOG_ERR, "Fail to drop privileges"); -- exit(1); -+ len = write(fd, pid, strlen(pid)); -+ if ((len < 0) || ((size_t)len != strlen(pid))) { -+ xlog_warn("Writing to pid file failed: errno %d (%m)", -+ errno); - } --} - --static void set_kernel_nsm_state(int state) --{ -- int fd; -- const char *file = "/proc/sys/fs/nfs/nsm_local_state"; -- -- fd = open(file ,O_WRONLY); -- if (fd >= 0) { -- char buf[20]; -- snprintf(buf, sizeof(buf), "%d", state); -- if (write(fd, buf, strlen(buf)) != strlen(buf)) { -- nsm_log(LOG_WARNING, "Writing to '%s' failed: errno %d (%s)", -- file, errno, strerror(errno)); -- } -- close(fd); -- } -+ (void)close(fd); -+ return 1; - } -diff --git a/utils/statd/sm-notify.man b/utils/statd/sm-notify.man -index dd03b8d..163713e 100644 ---- a/utils/statd/sm-notify.man -+++ b/utils/statd/sm-notify.man -@@ -1,166 +1,315 @@ --.\" --.\" sm-notify(8) -+.\"@(#)sm-notify.8" - .\" - .\" Copyright (C) 2004 Olaf Kirch --.TH sm-notify 8 "19 Mar 2007 -+.\" -+.\" Rewritten by Chuck Lever , 2009. -+.\" Copyright 2009 Oracle. All rights reserved. -+.\" -+.TH SM-NOTIFY 8 "1 November 2009 - .SH NAME --sm-notify \- Send out NSM reboot notifications -+sm-notify \- send reboot notifications to NFS peers - .SH SYNOPSIS --.BI "/sbin/sm-notify [-dfq] [-m " time "] [-p " port "] [-P " path "] [-v " my_name " ] -+.BI "/usr/sbin/sm-notify [-dfn] [-m " minutes "] [-v " name "] [-p " notify-port "] [-P " path "] - .SH DESCRIPTION --File locking over NFS (v2 and v3) requires a facility to notify peers in --case of a reboot, so that clients can reclaim locks after --a server crash, and/or --servers can release locks held by the rebooted client. -+File locks are not part of persistent file system state. -+Lock state is thus lost when a host reboots. -+.PP -+Network file systems must also detect when lock state is lost -+because a remote host has rebooted. -+After an NFS client reboots, an NFS server must release all file locks -+held by applications that were running on that client. -+After a server reboots, a client must remind the -+server of file locks held by applications running on that client. - .PP --This is a two-step process: during normal --operations, a mechanism is required to keep track of which --hosts need to be informed of a reboot. And of course, --notifications need to be sent out during reboot. --The protocol used for this is called NSM, for --.IR "Network Status Monitor" . -+For NFS version 2 and version 3, the -+.I Network Status Monitor -+protocol (or NSM for short) -+is used to notify NFS peers of reboots. -+On Linux, two separate user-space components constitute the NSM service: -+.TP -+.B sm-notify -+A helper program that notifies NFS peers after the local system reboots -+.TP -+.B rpc.statd -+A daemon that listens for reboot notifications from other hosts, and -+manages the list of hosts to be notified when the local system reboots - .PP --This implementation separates these into separate program. -+The local NFS lock manager alerts its local - .B rpc.statd --tracks hosts which need to be notified and this -+of each remote peer that should be monitored. -+When the local system reboots, the - .B sm-notify --performs the notification. When -+command notifies the NSM service on monitored peers of the reboot. -+When a remote reboots, that peer notifies the local -+.BR rpc.statd , -+which in turn passes the reboot notification -+back to the local NFS lock manager. -+.SH NSM OPERATION IN DETAIL -+The first file locking interaction between an NFS client and server causes -+the NFS lock managers on both peers to contact their local NSM service to -+store information about the opposite peer. -+On Linux, the local lock manager contacts -+.BR rpc.statd . -+.PP -+.B rpc.statd -+records information about each monitored NFS peer on persistent storage. -+This information describes how to contact a remote peer -+in case the local system reboots, -+how to recognize which monitored peer is reporting a reboot, -+and how to notify the local lock manager when a monitored peer -+indicates it has rebooted. -+.PP -+An NFS client sends a hostname, known as the client's -+.IR caller_name , -+in each file lock request. -+An NFS server can use this hostname to send asynchronous GRANT -+calls to a client, or to notify the client it has rebooted. -+.PP -+The Linux NFS server can provide the client's -+.I caller_name -+or the client's network address to -+.BR rpc.statd . -+For the purposes of the NSM protocol, -+this name or address is known as the monitored peer's -+.IR mon_name . -+In addition, the local lock manager tells - .B rpc.statd --is started it will typically started -+what it thinks its own hostname is. -+For the purposes of the NSM protocol, -+this hostname is known as -+.IR my_name . -+.PP -+There is no equivalent interaction between an NFS server and a client -+to inform the client of the server's -+.IR caller_name . -+Therefore NFS clients do not actually know what -+.I mon_name -+an NFS server might use in an SM_NOTIFY request. -+The Linux NFS client records the server's hostname used on the mount command -+to identify rebooting NFS servers. -+.SS Reboot notification -+When the local system reboots, the -+.B sm-notify -+command reads the list of monitored peers from persistent storage and -+sends an SM_NOTIFY request to the NSM service on each listed remote peer. -+It uses the -+.I mon_name -+string as the destination. -+To identify which host has rebooted, the - .B sm-notify --but this is configurable. --.SS Operation --For each NFS client or server machine to be monitored, -+command normally sends the results of -+.BR gethostname (3) -+as the -+.I my_name -+string. -+The remote - .B rpc.statd --creates a file in --.BR /var/lib/nfs/sm ", " --and removes the file if monitoring is no longer required. -+matches incoming SM_NOTIFY requests using this string, -+or the caller's network address, -+to one or more peers on its own monitor list. - .PP --When the machine is rebooted, -+If -+.B rpc.statd -+does not find a peer on its monitor list that matches -+an incoming SM_NOTIFY request, -+the notification is not forwarded to the local lock manager. -+In addition, each peer has its own -+.IR "NSM state number" , -+a 32-bit integer that is bumped after each reboot by the - .B sm-notify --iterates through these files and notifies the peer --.B statd --server on those machines. -+command. -+.B rpc.statd -+uses this number to distinguish between actual reboots -+and replayed notifications. - .PP --Each machine has an --.I "NSM state" , --which is basically an integer counter that is incremented --each time the machine reboots. This counter is stored --in --.BR /var/lib/nfs/state , --and updated by --.BR sm-notify . --.SS Security --.B sm-notify --has little need for root privileges and so drops them as soon as --possible. --It continues to need to make changes to the --.B sm --and --.B sm.bak --directories so to be able to drop privileges, these must be writable --by a non-privileged user. If these directories are owned by a --non-root user, --.B sm-notify --will drop privilege to match that user once it has created sockets for --sending out request (for which it needs privileged) but before it --processes any reply (which is the most likely source of possible --privilege abuse). -+Part of NFS lock recovery is rediscovering -+which peers need to be monitored again. -+The -+.B sm-notify -+command clears the monitor list on persistent storage after each reboot. - .SH OPTIONS - .TP --.BI -m " failtime --When notifying hosts, -+.B -d -+Keeps -+.B sm-notify -+attached to its controlling terminal and running in the foreground -+so that notification progress may be monitored directly. -+.TP -+.B -f -+Send notifications even if -+.B sm-notify -+has already run since the last system reboot. -+.TP -+.BI -m " retry-time -+Specifies the length of time, in minutes, to continue retrying -+notifications to unresponsive hosts. -+If this option is not specified, - .B sm-notify --will try to contact each host for up to 15 minutes, --and will give up if unable to reach it within this time --frame. -+attempts to send notifications for 15 minutes. -+Specifying a value of 0 causes -+.B sm-notify -+to continue sending notifications to unresponsive peers -+until it is manually killed. -+.IP -+Notifications are retried if sending fails, -+the remote does not respond, -+the remote's NSM service is not registered, -+or if there is a DNS failure -+which prevents the remote's -+.I mon_name -+from being resolved to an address. - .IP --Using the --.B -m --option, you can override this. A value of 0 tells --sm-notify to retry indefinitely; any other value is --interpreted as the maximum retry time in minutes. -+Hosts are not removed from the notification list until a valid -+reply has been received. -+However, the SM_NOTIFY procedure has a void result. -+There is no way for -+.B sm-notify -+to tell if the remote recognized the sender and has started -+appropriate lock recovery. - .TP --.BI -v " ipaddr-or-hostname --This option tells --.B sm-notify --to bind to the specified --.IR ipaddr , --(or the ipaddr of the given --.IR hostname ) --so that all notification packets originate from this address. --This is useful for NFS failover. The given name is also used as the --.I name --of this host in the NSM request. -+.B -n -+Prevents -+.B sm-notify -+from updating the local system's NSM state number. - .TP - .BI -p " port --instructs -+Specifies the source port number - .B sm-notify --to bind to the indicated IP --.IR port --number. If this option is not given, it will try to bind to --a randomly chosen privileged port below 1024. --.TP --.B -q --Be quiet. This suppresses all messages except error --messages while collecting the list of hosts. -+should use when sending reboot notifications. -+If this option is not specified, a randomly chosen ephemeral port is used. -+.IP -+This option can be used to traverse a firewall between client and server. - .TP --.BI -P " /path/to/state/directory --If -+.BI "\-P, " "" \-\-state\-directory\-path " pathname -+Specifies the pathname of the parent directory -+where NSM state information resides. -+If this option is not specified, -+.B sm-notify -+uses -+.I /var/lib/nfs -+by default. -+.IP -+After starting, - .B sm-notify --should look in a no-standard place of state file, the path can be --given here. The directories --.B sm --and --.B sm.bak --and the file --.B state --must exist in that directory with the standard names. -+attempts to set its effective UID and GID to the owner -+and group of this directory. - .TP --.B -f --If the state path has not been reset with --.BR -P , -+.BI -v " ipaddr " | " hostname -+Specifies the network address from which to send reboot notifications, -+and the -+.I mon_name -+argument to use when sending SM_NOTIFY requests. -+If this option is not specified, - .B sm-notify --will normally create a file in --.B /var/run --to indicate that it has been --run. If this file is found when -+uses a wildcard address as the transport bind address, -+and uses the results of -+.BR gethostname (3) -+as the -+.I mon_name -+argument. -+.IP -+The -+.I ipaddr -+form can be expressed as either an IPv4 or an IPv6 presentation address. -+.IP -+This option can be useful in multi-homed configurations where -+the remote requires notification from a specific network address. -+.SH SECURITY -+The - .B sm-notify --starts, it will not run again (as it is normally only needed once per --reboot). --If --.B -f --(for --.BR force ) --is given, -+command must be started as root to acquire privileges needed -+to access the state information database. -+It drops root privileges -+as soon as it starts up to reduce the risk of a privilege escalation attack. -+.PP -+During normal operation, -+the effective user ID it chooses is the owner of the state directory. -+This allows it to continue to access files in that directory after it -+has dropped its root privileges. -+To control which user ID -+.B rpc.statd -+chooses, simply use -+.BR chown (1) -+to set the owner of -+the state directory. -+.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 -+should be observed, including: -+.IP -+The UTS nodename of your systems should match the DNS names that NFS -+peers use to contact them -+.IP -+The UTS nodenames of your systems should always be fully qualified domain names -+.IP -+The forward and reverse DNS mapping of the UTS nodenames should be -+consistent -+.IP -+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. -+Both may continue monitoring each other for a time in case subsequent -+NFS traffic between the two results in fresh mounts and additional -+file locking. -+.PP -+On Linux, if the -+.B lockd -+kernel module is unloaded during normal operation, -+all remote NFS peers are unmonitored. -+This can happen on an NFS client, for example, -+if an automounter removes all NFS mount -+points due to inactivity. -+.SS IPv6 and TI-RPC support -+TI-RPC is a pre-requisite for supporting NFS on IPv6. -+If TI-RPC support is built into the - .B sm-notify --will run even if the file in --.B /var/run --is present. --.TP --.B -n --Do not update the NSM state. This is for testing only. Setting this --flag implies --.BR -f . --.TP --.B -d --Enables debugging. --By default, -+command ,it will choose an appropriate IPv4 or IPv6 transport -+based on the network address returned by DNS for each remote peer. -+It should be fully compatible with remote systems -+that do not support TI-RPC or IPv6. -+.PP -+Currently, the - .B sm-notify --forks and puts itself in the background after obtaining the --list of hosts from --.BR /var/lib/nfs/sm . -+command supports sending notification only via datagram transport protocols. - .SH FILES --.BR /var/lib/nfs/state --.br --.BR /var/lib/nfs/sm/* -+.TP 2.5i -+.I /var/lib/nfs/sm -+directory containing monitor list -+.TP 2.5i -+.I /var/lib/nfs/sm.bak -+directory containing notify list -+.TP 2.5i -+.I /var/lib/nfs/state -+NSM state number for this host -+.TP 2.5i -+.I /proc/sys/fs/nfs/nsm_local_state -+kernel's copy of the NSM state number -+.SH SEE ALSO -+.BR rpc.statd (8), -+.BR nfs (5), -+.BR uname (2), -+.BR hostname (7) -+.PP -+RFC 1094 - "NFS: Network File System Protocol Specification" - .br --.BR /var/lib/nfs/sm.bak/* -+RFC 1813 - "NFS Version 3 Protocol Specification" - .br --.BR /var/run/sm-notify.pid --.SH SEE ALSO --.BR rpc.nfsd(8), --.BR portmap(8) -+OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11 - .SH AUTHORS --.br - Olaf Kirch -+.br -+Chuck Lever -diff --git a/utils/statd/sm_inter.x b/utils/statd/sm_inter.x -deleted file mode 100644 -index d8e0ad7..0000000 ---- a/utils/statd/sm_inter.x -+++ /dev/null -@@ -1,131 +0,0 @@ --/* -- * Copyright (C) 1986 Sun Microsystems, Inc. -- * Modified by Jeffrey A. Uphoff, 1995, 1997-1999. -- * Modified by Olaf Kirch, 1996. -- * Modified by H.J. Lu, 1998. -- * -- * NSM for Linux. -- */ -- --/* -- * Copyright (c) 2009, Sun Microsystems, Inc. -- * All rights reserved. -- * -- * Redistribution and use in source and binary forms, with or without -- * modification, are permitted provided that the following conditions are met: -- * - Redistributions of source code must retain the above copyright notice, -- * this list of conditions and the following disclaimer. -- * - Redistributions in binary form must reproduce the above copyright notice, -- * this list of conditions and the following disclaimer in the documentation -- * and/or other materials provided with the distribution. -- * - Neither the name of Sun Microsystems, Inc. nor the names of its -- * contributors may be used to endorse or promote products derived -- * from this software without specific prior written permission. -- * -- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- * POSSIBILITY OF SUCH DAMAGE. -- */ -- --/* -- * Status monitor protocol specification -- */ -- --#ifdef RPC_CLNT --%#include --#endif -- --program SM_PROG { -- version SM_VERS { -- /* res_stat = stat_succ if status monitor agrees to monitor */ -- /* res_stat = stat_fail if status monitor cannot monitor */ -- /* if res_stat == stat_succ, state = state number of site sm_name */ -- struct sm_stat_res SM_STAT(struct sm_name) = 1; -- -- /* res_stat = stat_succ if status monitor agrees to monitor */ -- /* res_stat = stat_fail if status monitor cannot monitor */ -- /* stat consists of state number of local site */ -- struct sm_stat_res SM_MON(struct mon) = 2; -- -- /* stat consists of state number of local site */ -- struct sm_stat SM_UNMON(struct mon_id) = 3; -- -- /* stat consists of state number of local site */ -- struct sm_stat SM_UNMON_ALL(struct my_id) = 4; -- -- void SM_SIMU_CRASH(void) = 5; -- -- void SM_NOTIFY(struct stat_chge) = 6; -- -- } = 1; --} = 100024; -- --const SM_MAXSTRLEN = 1024; --const SM_PRIV_SIZE = 16; -- --struct sm_name { -- string mon_name; --}; -- --struct my_id { -- string my_name; /* name of the site iniates the monitoring request*/ -- int my_prog; /* rpc program # of the requesting process */ -- int my_vers; /* rpc version # of the requesting process */ -- int my_proc; /* rpc procedure # of the requesting process */ --}; -- --struct mon_id { -- string mon_name; /* name of the site to be monitored */ -- struct my_id my_id; --}; -- -- --struct mon { -- struct mon_id mon_id; -- opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */ --}; -- --struct stat_chge { -- string mon_name; /* name of the site that had the state change */ -- int state; --}; -- --/* -- * state # of status monitor monitonically increases each time -- * status of the site changes: -- * an even number (>= 0) indicates the site is down and -- * an odd number (> 0) indicates the site is up; -- */ --struct sm_stat { -- int state; /* state # of status monitor */ --}; -- --enum res { -- stat_succ = 0, /* status monitor agrees to monitor */ -- stat_fail = 1 /* status monitor cannot monitor */ --}; -- --struct sm_stat_res { -- res res_stat; -- int state; --}; -- --/* -- * structure of the status message sent back by the status monitor -- * when monitor site status changes -- */ --struct status { -- string mon_name; -- int state; -- opaque priv[SM_PRIV_SIZE]; /* stored private information */ --}; -- --%#define SM_INTER_X -diff --git a/utils/statd/stat.c b/utils/statd/stat.c -index 799239f..8d8b65e 100644 ---- a/utils/statd/stat.c -+++ b/utils/statd/stat.c -@@ -12,7 +12,7 @@ - #include - #include "statd.h" - --/* -+/* - * Services SM_STAT requests. - * - * According the the X/Open spec's on this procedure: "Implementations -@@ -37,18 +37,23 @@ - * other way to get it. - * 3/ That's what we always did in the past. - */ --struct sm_stat_res * --sm_stat_1_svc (struct sm_name *argp, struct svc_req *rqstp) -+struct sm_stat_res * -+sm_stat_1_svc(struct sm_name *argp, -+ __attribute__ ((unused)) struct svc_req *rqstp) - { - static sm_stat_res result; -+ char *name; -+ -+ xlog(D_CALL, "Received SM_STAT from %s", argp->mon_name); - -- if (gethostbyname (argp->mon_name) == NULL) { -- note (N_WARNING, "gethostbyname error for %s", argp->mon_name); -+ name = statd_canonical_name(argp->mon_name); -+ if (name == NULL) { - result.res_stat = STAT_FAIL; -- dprintf (N_DEBUG, "STAT_FAIL for %s", argp->mon_name); -+ xlog (D_GENERAL, "STAT_FAIL for %s", argp->mon_name); - } else { - result.res_stat = STAT_SUCC; -- dprintf (N_DEBUG, "STAT_SUCC for %s", argp->mon_name); -+ xlog (D_GENERAL, "STAT_SUCC for %s", argp->mon_name); -+ free(name); - } - result.state = MY_STATE; - return(&result); -diff --git a/utils/statd/statd.c b/utils/statd/statd.c -index 1c5247e..01fdb41 100644 ---- a/utils/statd/statd.c -+++ b/utils/statd/statd.c -@@ -25,33 +25,21 @@ - #include - #include - #include -+ - #include "statd.h" --#include "version.h" - #include "nfslib.h" -+#include "nsm.h" - - /* Socket operations */ - #include - #include - --/* Added to enable specification of state directory path at run-time -- * j_carlos_gomez@yahoo.com -- */ -- --char * DIR_BASE = DEFAULT_DIR_BASE; -- --char * SM_DIR = DEFAULT_SM_DIR; --char * SM_BAK_DIR = DEFAULT_SM_BAK_DIR; --char * SM_STAT_PATH = DEFAULT_SM_STAT_PATH; -- --/* ----- end of state directory path stuff ------- */ -- - int run_mode = 0; /* foreground logging mode */ - - /* LH - I had these local to main, but it seemed silly to have - * two copies of each - one in main(), one static in log.c... - * It also eliminates the 256-char static in log.c */ --char *name_p = NULL; --const char *version_p = NULL; -+static char *name_p = NULL; - - /* PRC: a high-availability callout program can be specified with -H - * When this is done, the program will receive callouts whenever clients -@@ -75,7 +63,6 @@ static struct option longopts[] = - }; - - extern void sm_prog_1 (struct svc_req *, register SVCXPRT *); --static void load_state_number(void); - - #ifdef SIMULATIONS - extern void simulator (int, char **); -@@ -88,11 +75,8 @@ extern void simulator (int, char **); - static void - sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp) - { -- struct sockaddr_in *sin = nfs_getrpccaller_in(transp); -- - /* remote host authorization check */ -- if (sin->sin_family == AF_INET && -- !check_default("statd", sin, rqstp->rq_proc, SM_PROG)) { -+ if (!check_default("statd", nfs_getrpccaller(transp), SM_PROG)) { - svcerr_auth (transp, AUTH_FAILED); - return; - } -@@ -103,23 +87,26 @@ sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp) - #define sm_prog_1 sm_prog_1_wrapper - #endif - -+static void -+statd_unregister(void) { -+ nfs_svc_unregister(SM_PROG, SM_VERS); -+} -+ - /* - * Signal handler. - */ - static void - killer (int sig) - { -- note (N_FATAL, "Caught signal %d, un-registering and exiting.", sig); -- pmap_unset (SM_PROG, SM_VERS); -- -- exit (0); -+ statd_unregister (); -+ xlog_err ("Caught signal %d, un-registering and exiting", sig); - } - - static void - sigusr (int sig) - { - extern void my_svc_exit (void); -- dprintf (N_DEBUG, "Caught signal %d, re-notifying (state %d).", sig, -+ xlog(D_GENERAL, "Caught signal %d, re-notifying (state %d)", sig, - MY_STATE); - my_svc_exit(); - } -@@ -140,8 +127,11 @@ static void log_modes(void) - strcat(buf,"No-Daemon "); - if (run_mode & MODE_LOG_STDERR) - strcat(buf,"Log-STDERR "); -+#ifdef HAVE_LIBTIRPC -+ strcat(buf, "TI-RPC "); -+#endif - -- note(N_WARNING,buf); -+ xlog_warn(buf); - } - - /* -@@ -175,13 +165,12 @@ static void create_pidfile(void) - unlink(pidfile); - fp = fopen(pidfile, "w"); - if (!fp) -- die("Opening %s failed: %s\n", -- pidfile, strerror(errno)); -+ xlog_err("Opening %s failed: %m\n", pidfile); - fprintf(fp, "%d\n", getpid()); - pidfd = dup(fileno(fp)); - if (fclose(fp) < 0) { -- note(N_WARNING, "Flushing pid file failed: errno %d (%s)\n", -- errno, strerror(errno)); -+ xlog_warn("Flushing pid file failed: errno %d (%m)\n", -+ errno); - } - } - -@@ -189,42 +178,10 @@ static void truncate_pidfile(void) - { - if (pidfd >= 0) { - if (ftruncate(pidfd, 0) < 0) { -- note(N_WARNING, "truncating pid file failed: errno %d (%s)\n", -- errno, strerror(errno)); -- } -- } --} -- --static void drop_privs(void) --{ -- struct stat st; -- -- if (stat(SM_DIR, &st) == -1 && -- stat(DIR_BASE, &st) == -1) { -- st.st_uid = 0; -- st.st_gid = 0; -- } -- -- if (st.st_uid == 0) { -- note(N_WARNING, "statd running as root. chown %s to choose different user\n", -- SM_DIR); -- return; -- } -- /* better chown the pid file before dropping, as if it -- * if over nfs we might loose access -- */ -- if (pidfd >= 0) { -- if (fchown(pidfd, st.st_uid, st.st_gid) < 0) { -- note(N_ERROR, "Unable to change owner of %s: %d (%s)", -- SM_DIR, strerror (errno)); -+ xlog_warn("truncating pid file failed: errno %d (%m)\n", -+ errno); - } - } -- setgroups(0, NULL); -- if (setgid(st.st_gid) == -1 -- || setuid(st.st_uid) == -1) { -- note(N_ERROR, "Fail to drop privileges"); -- exit(1); -- } - } - - static void run_sm_notify(int outport) -@@ -266,6 +223,8 @@ int main (int argc, char **argv) - - /* Default: daemon mode, no other options */ - run_mode = 0; -+ xlog_stderr(0); -+ xlog_syslog(1); - - /* Set the basename */ - if ((name_p = strrchr(argv[0],'/')) != NULL) { -@@ -274,13 +233,6 @@ int main (int argc, char **argv) - name_p = argv[0]; - } - -- /* Get the version */ -- if ((version_p = strrchr(VERSION,' ')) != NULL) { -- version_p++; -- } else { -- version_p = VERSION; -- } -- - /* Set hostname */ - MY_NAME = NULL; - -@@ -289,7 +241,7 @@ int main (int argc, char **argv) - switch (arg) { - case 'V': /* Version */ - case 'v': -- printf("%s version %s\n",name_p,version_p); -+ printf("%s version " VERSION "\n",name_p); - exit(0); - case 'F': /* Foreground/nodaemon mode */ - run_mode |= MODE_NODAEMON; -@@ -326,34 +278,8 @@ int main (int argc, char **argv) - MY_NAME = xstrdup(optarg); - break; - case 'P': -- -- if ((DIR_BASE = xstrdup(optarg)) == NULL) { -- fprintf(stderr, "%s: xstrdup(%s) failed!\n", -- argv[0], optarg); -- exit(1); -- } -- -- SM_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm")); -- SM_BAK_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm.bak")); -- SM_STAT_PATH = xmalloc(strlen(DIR_BASE) + 1 + sizeof("state")); -- -- if ((SM_DIR == NULL) -- || (SM_BAK_DIR == NULL) -- || (SM_STAT_PATH == NULL)) { -- -- fprintf(stderr, "%s: xmalloc() failed!\n", -- argv[0]); -+ if (!nsm_setup_pathnames(argv[0], optarg)) - exit(1); -- } -- if (DIR_BASE[strlen(DIR_BASE)-1] == '/') { -- sprintf(SM_DIR, "%ssm", DIR_BASE ); -- sprintf(SM_BAK_DIR, "%ssm.bak", DIR_BASE ); -- sprintf(SM_STAT_PATH, "%sstate", DIR_BASE ); -- } else { -- sprintf(SM_DIR, "%s/sm", DIR_BASE ); -- sprintf(SM_BAK_DIR, "%s/sm.bak", DIR_BASE ); -- sprintf(SM_STAT_PATH, "%s/state", DIR_BASE ); -- } - break; - case 'H': /* PRC: specify the ha-callout program */ - if ((ha_callout_prog = xstrdup(optarg)) == NULL) { -@@ -383,7 +309,6 @@ int main (int argc, char **argv) - run_sm_notify(out_port); - } - -- - if (!(run_mode & MODE_NODAEMON)) { - run_mode &= ~MODE_LOG_STDERR; /* Never log to console in - daemon mode. */ -@@ -432,10 +357,6 @@ int main (int argc, char **argv) - /* Child. */ - close(pipefds[0]); - setsid (); -- if (chdir (DIR_BASE) == -1) { -- perror("statd: Could not chdir"); -- exit(1); -- } - - while (pipefds[1] <= 2) { - pipefds[1] = dup(pipefds[1]); -@@ -455,7 +376,13 @@ int main (int argc, char **argv) - - /* Child. */ - -- log_init (/*name_p,version_p*/); -+ if (run_mode & MODE_LOG_STDERR) { -+ xlog_syslog(0); -+ xlog_stderr(1); -+ xlog_config(D_ALL, 1); -+ } -+ xlog_open(name_p); -+ xlog(L_NOTICE, "Version " VERSION " starting"); - - log_modes(); - -@@ -495,25 +422,48 @@ int main (int argc, char **argv) - * pass on any SM_NOTIFY that arrives - */ - load_state(); -- load_state_number(); -- pmap_unset (SM_PROG, SM_VERS); - -- /* this registers both UDP and TCP services */ -- rpc_init("statd", SM_PROG, SM_VERS, sm_prog_1, port); -+ MY_STATE = nsm_get_state(0); -+ if (MY_STATE == 0) -+ exit(1); -+ xlog(D_GENERAL, "Local NSM state number: %d", MY_STATE); -+ nsm_update_kernel_state(MY_STATE); -+ -+ /* -+ * ORDER -+ * Clear old listeners while still root, to override any -+ * permission checking done by rpcbind. -+ */ -+ statd_unregister(); -+ -+ /* -+ * ORDER -+ */ -+ if (!nsm_drop_privileges(pidfd)) -+ exit(1); -+ -+ /* -+ * ORDER -+ * Create RPC listeners after dropping privileges. This permits -+ * statd to unregister its own listeners when it exits. -+ */ -+ if (nfs_svc_create("statd", SM_PROG, SM_VERS, sm_prog_1, port) == 0) { -+ xlog(L_ERROR, "failed to create RPC listeners, exiting"); -+ exit(1); -+ } -+ atexit(statd_unregister); - - /* If we got this far, we have successfully started, so notify parent */ - if (pipefds[1] > 0) { - status = 0; - if (write(pipefds[1], &status, 1) != 1) { -- note(N_WARNING, "writing to parent pipe failed: errno %d (%s)\n", -+ xlog_warn("writing to parent pipe failed: errno %d (%s)\n", - errno, strerror(errno)); - } - close(pipefds[1]); - pipefds[1] = -1; - } - -- drop_privs(); -- - for (;;) { - /* - * Handle incoming requests: SM_NOTIFY socket requests, as -@@ -541,29 +491,3 @@ int main (int argc, char **argv) - } - return 0; - } -- --static void --load_state_number(void) --{ -- int fd; -- const char *file = "/proc/sys/fs/nfs/nsm_local_state"; -- -- if ((fd = open(SM_STAT_PATH, O_RDONLY)) == -1) -- return; -- -- if (read(fd, &MY_STATE, sizeof(MY_STATE)) != sizeof(MY_STATE)) { -- note(N_WARNING, "Unable to read state from '%s': errno %d (%s)", -- SM_STAT_PATH, errno, strerror(errno)); -- } -- close(fd); -- fd = open(file, O_WRONLY); -- if (fd >= 0) { -- char buf[20]; -- snprintf(buf, sizeof(buf), "%d", MY_STATE); -- if (write(fd, buf, strlen(buf)) != strlen(buf)) -- note(N_WARNING, "Writing to '%s' failed: errno %d (%s)", -- file, errno, strerror(errno)); -- close(fd); -- } -- --} -diff --git a/utils/statd/statd.h b/utils/statd/statd.h -index 88ba208..e89e666 100644 ---- a/utils/statd/statd.h -+++ b/utils/statd/statd.h -@@ -11,29 +11,7 @@ - - #include "sm_inter.h" - #include "system.h" --#include "log.h" -- --/* -- * Paths and filenames. -- */ --#if defined(NFS_STATEDIR) --# define DEFAULT_DIR_BASE NFS_STATEDIR "/" --#else --# define DEFAULT_DIR_BASE "/var/lib/nfs/" --#endif -- --#define DEFAULT_SM_DIR DEFAULT_DIR_BASE "sm" --#define DEFAULT_SM_BAK_DIR DEFAULT_DIR_BASE "sm.bak" --#define DEFAULT_SM_STAT_PATH DEFAULT_DIR_BASE "state" -- --/* Added to support run-time specification of state directory path. -- * j_carlos_gomez@yahoo.com -- */ -- --extern char * DIR_BASE; --extern char * SM_DIR; --extern char * SM_BAK_DIR; --extern char * SM_STAT_PATH; -+#include "xlog.h" - - /* - * Status definitions. -@@ -44,7 +22,12 @@ extern char * SM_STAT_PATH; - /* - * Function prototypes. - */ --extern void change_state(void); -+extern _Bool statd_matchhostname(const char *hostname1, const char *hostname2); -+extern _Bool statd_present_address(const struct sockaddr *sap, char *buf, -+ const size_t buflen); -+__attribute_malloc__ -+extern char * statd_canonical_name(const char *hostname); -+ - extern void my_svc_run(void); - extern void notify_hosts(void); - extern void shuffle_dirs(void); -@@ -53,7 +36,6 @@ extern int process_notify_list(void); - extern int process_reply(FD_SET_TYPE *); - extern char * xstrdup(const char *); - extern void * xmalloc(size_t); --extern void xunlink (char *, char *); - extern void load_state(void); - - /* -@@ -84,10 +66,3 @@ extern int run_mode; - * another host.... */ - #define STATIC_HOSTNAME 8 /* Always use the hostname set by -n */ - #define MODE_NO_NOTIFY 16 /* Don't notify peers of a reboot */ --/* -- * Program name and version pointers -- See statd.c for the reasoning -- * as to why they're global. -- */ --extern char *name_p; /* program basename */ --extern const char *version_p; /* program version */ -- -diff --git a/utils/statd/statd.man b/utils/statd/statd.man -index e8be9f3..ffc5e95 100644 ---- a/utils/statd/statd.man -+++ b/utils/statd/statd.man -@@ -1,191 +1,400 @@ --.\" --.\" statd(8) -+.\"@(#)rpc.statd.8" - .\" - .\" Copyright (C) 1999 Olaf Kirch - .\" Modified by Jeffrey A. Uphoff, 1999, 2002, 2005. - .\" Modified by Lon Hohberger, 2000. - .\" Modified by Paul Clements, 2004. --.TH rpc.statd 8 "31 Aug 2004" -+.\" -+.\" Rewritten by Chuck Lever , 2009. -+.\" Copyright 2009 Oracle. All rights reserved. -+.\" -+.TH RPC.STATD 8 "1 November 2009 - .SH NAME --rpc.statd \- NSM status monitor -+rpc.statd \- NSM service daemon - .SH SYNOPSIS --.B "rpc.statd [-FNL] [-d] [-?] [-n " name "] [-o " port "] [-p " port "] [-H " prog "] [-V]" -+.BI "rpc.statd [-dh?FLNvVw] [-H " prog "] [-n " my-name "] [-o " outgoing-port "] [-p " listener-port "] [-P " path " ] - .SH DESCRIPTION --The -+File locks are not part of persistent file system state. -+Lock state is thus lost when a host reboots. -+.PP -+Network file systems must also detect when lock state is lost -+because a remote host has rebooted. -+After an NFS client reboots, an NFS server must release all file locks -+held by applications that were running on that client. -+After a server reboots, a client must remind the -+server of file locks held by applications running on that client. -+.PP -+For NFS version 2 [RFC1094] and NFS version 3 [RFC1813], the -+.I Network Status Monitor -+protocol (or NSM for short) -+is used to notify NFS peers of reboots. -+On Linux, two separate user-space components constitute the NSM service: -+.TP - .B rpc.statd --server implements the NSM (Network Status Monitor) RPC protocol. --This service is somewhat misnamed, since it doesn't actually provide --active monitoring as one might suspect; instead, NSM implements a --reboot notification service. It is used by the NFS file locking service, --.BR rpc.lockd , --to implement lock recovery when the NFS server machine crashes and --reboots. --.SS Operation --For each NFS client or server machine to be monitored, --.B rpc.statd --creates a file in --.BR /var/lib/nfs/sm . --When starting, it normally runs -+A daemon that listens for reboot notifications from other hosts, and -+manages the list of hosts to be notified when the local system reboots -+.TP -+.B sm-notify -+A helper program that notifies NFS peers after the local system reboots -+.PP -+The local NFS lock manager alerts its local -+.B rpc.statd -+of each remote peer that should be monitored. -+When the local system reboots, the - .B sm-notify --to iterate through these files and notify the --peer -+command notifies the NSM service on monitored peers of the reboot. -+When a remote reboots, that peer notifies the local -+.BR rpc.statd , -+which in turn passes the reboot notification -+back to the local NFS lock manager. -+.SH NSM OPERATION IN DETAIL -+The first file locking interaction between an NFS client and server causes -+the NFS lock managers on both peers to contact their local NSM service to -+store information about the opposite peer. -+On Linux, the local lock manager contacts -+.BR rpc.statd . -+.PP -+.B rpc.statd -+records information about each monitored NFS peer on persistent storage. -+This information describes how to contact a remote peer -+in case the local system reboots, -+how to recognize which monitored peer is reporting a reboot, -+and how to notify the local lock manager when a monitored peer -+indicates it has rebooted. -+.PP -+An NFS client sends a hostname, known as the client's -+.IR caller_name , -+in each file lock request. -+An NFS server can use this hostname to send asynchronous GRANT -+calls to a client, or to notify the client it has rebooted. -+.PP -+The Linux NFS server can provide the client's -+.I caller_name -+or the client's network address to -+.BR rpc.statd . -+For the purposes of the NSM protocol, -+this name or address is known as the monitored peer's -+.IR mon_name . -+In addition, the local lock manager tells - .B rpc.statd --on those machines. -+what it thinks its own hostname is. -+For the purposes of the NSM protocol, -+this hostname is known as -+.IR my_name . -+.PP -+There is no equivalent interaction between an NFS server and a client -+to inform the client of the server's -+.IR caller_name . -+Therefore NFS clients do not actually know what -+.I mon_name -+an NFS server might use in an SM_NOTIFY request. -+The Linux NFS client uses the server hostname from the mount command -+to identify rebooting NFS servers. -+.SS Reboot notification -+When the local system reboots, the -+.B sm-notify -+command reads the list of monitored peers from persistent storage and -+sends an SM_NOTIFY request to the NSM service on each listed remote peer. -+It uses the -+.I mon_name -+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 -+.I my_name -+string. -+The remote -+.B rpc.statd -+matches incoming SM_NOTIFY requests using this string, -+or the caller's network address, -+to one or more peers on its own monitor list. -+.PP -+If -+.B rpc.statd -+does not find a peer on its monitor list that matches -+an incoming SM_NOTIFY request, -+the notification is not forwarded to the local lock manager. -+In addition, each peer has its own -+.IR "NSM state number" , -+a 32-bit integer that is bumped after each reboot by the -+.B sm-notify -+command. -+.B rpc.statd -+uses this number to distinguish between actual reboots -+and replayed notifications. -+.PP -+Part of NFS lock recovery is rediscovering -+which peers need to be monitored again. -+The -+.B sm-notify -+command clears the monitor list on persistent storage after each reboot. - .SH OPTIONS - .TP --.B -F --By default, -+.BR -d , " --no-syslog -+Causes - .B rpc.statd --forks and puts itself in the background when started. The --.B -F --argument tells it to remain in the foreground. This option is --mainly for debugging purposes. --.TP --.B -d --By default, --.B rpc.statd --sends logging messages via --.BR syslog (3) --to system log. The --.B -d --argument forces it to log verbose output to --.B stderr --instead. This option is mainly for debugging purposes, and may only --be used in conjunction with the -+to write log messages on -+.I stderr -+instead of to the system log, -+if the - .B -F --parameter. -+option was also specified. - .TP --.BI "\-n," "" " \-\-name " name --specify a name for --.B rpc.statd --to use as the local hostname. By default, --.BR rpc.statd --will call --.BR gethostname (2) --to get the local hostname. Specifying --a local hostname may be useful for machines with more than one --interfaces. -+.BR -F , " --foreground -+Keeps -+.B rpc.statd -+attached to its controlling terminal so that NSM -+operation can be monitored directly or run under a debugger. -+If this option is not specified, -+.B rpc.statd -+backgrounds itself soon after it starts. - .TP --.BI "\-o," "" " \-\-outgoing\-port " port --specify a port for --.B rpc.statd --to send outgoing status requests from. By default, --.BR rpc.statd --will ask --.BR portmap (8) --to assign it a port number. As of this writing, there is not --a standard port number that --.BR portmap --always or usually assigns. Specifying --a port may be useful when implementing a firewall. -+.BR -h , " -?" , " --help -+Causes -+.B rpc.statd -+to display usage information on -+.I stderr -+and then exit. - .TP --.BI "\-p," "" " \-\-port " port --specify a port for --.B rpc.statd --to listen on. By default, --.BR rpc.statd --will ask --.BR portmap (8) --to assign it a port number. As of this writing, there is not --a standard port number that --.BR portmap --always or usually assigns. Specifying --a port may be useful when implementing a firewall. -+.BI "\-H," "" " \-\-ha-callout " prog -+Specifies a high availability callout program. -+If this option is not specified, no callouts are performed. -+See the -+.B High-availability callouts -+section below for details. - .TP --.BI "\-P," "" " \-\-state\-directory\-path " directory --specify a directory in which to place statd state information. --If this option is not specified the default of --.BR /var/lib/nfs --is used. -+.BR -L , " --no-notify -+Prevents -+.B rpc.statd -+from running the -+.B sm-notify -+command when it starts up, -+preserving the existing NSM state number and monitor list. -+.IP -+Note: the -+.B sm-notify -+command contains a check to ensure it runs only once after each system reboot. -+This prevents spurious reboot notification if -+.B rpc.statd -+restarts without the -+.B -L -+option. - .TP --.B -N --Causes statd to run in the notify-only mode. When started in this mode, the --statd program will check its state directory, send notifications to any --monitored nodes, and exit once the notifications have been sent. This mode is --used to enable Highly Available NFS implementations (i.e. HA-NFS). --This mode is deprecated \- -+.BI "\-n, " "" "\-\-name " ipaddr " | " hostname -+Specifies the bind address used for RPC listener sockets. -+The -+.I ipaddr -+form can be expressed as either an IPv4 or an IPv6 presentation address. -+If this option is not specified, -+.B rpc.statd -+uses a wildcard address as the transport bind address. -+.IP -+This string is also passed to the - .B sm-notify --should be used directly instead. -+command to be used as the source address from which -+to send reboot notification requests. -+See -+.BR sm-notify (8) -+for details. - .TP --.BR -L , " --no-notify --Inhibits the running of --.BR sm-notify . --If -+.BR -N -+Causes -+.B rpc.statd -+to run the - .B sm-notify --is run by some other script at boot time, there is no need for --.B statd --to start sm-notify itself. This can be appropriate if starting of --statd needs to be delayed until it is actually need. In such cases -+command, and then exit. -+Since the -+.B sm-notify -+command can also be run directly, this option is deprecated. -+.TP -+.BI "\-o," "" " \-\-outgoing\-port " port -+Specifies the source port number the - .B sm-notify --should still be run at boot time. -+command should use when sending reboot notifications. -+See -+.BR sm-notify (8) -+for details. - .TP --.BI "\-H, " "" " \-\-ha-callout " prog --Specify a high availability callout program, which will receive callouts --for all client monitor and unmonitor requests. This allows -+.BI "\-p," "" " \-\-port " port -+Specifies the port number used for RPC listener sockets. -+If this option is not specified, - .B rpc.statd --to be used in a High Availability NFS (HA-NFS) environment. The --program will be run with 3 arguments: The first is either --.B add-client --or --.B del-client --depending on the reason for the callout. --The second will be the name of the client. --The third will be the name of the server as known to the client. -+chooses a random ephemeral port for each listener socket. -+.IP -+This option can be used to fix the port value of its listeners when -+SM_NOTIFY requests must traverse a firewall between clients and servers. - .TP --.B -? --Causes -+.BI "\-P, " "" \-\-state\-directory\-path " pathname -+Specifies the pathname of the parent directory -+where NSM state information resides. -+If this option is not specified, - .B rpc.statd --to print out command-line help and exit. -+uses -+.I /var/lib/nfs -+by default. -+.IP -+After starting, -+.B rpc.statd -+attempts to set its effective UID and GID to the owner -+and group of this directory. - .TP --.B -V -+.BR -v ", " -V ", " --version - Causes - .B rpc.statd --to print out version information and exit. -- -- -- --.SH TCP_WRAPPERS SUPPORT --This -+to display version information on -+.I stderr -+and then exit. -+.SH SECURITY -+The - .B rpc.statd --version is protected by the -+daemon must be started as root to acquire privileges needed -+to create sockets with privileged source ports, and to access the -+state information database. -+Because -+.B rpc.statd -+maintains a long-running network service, however, it drops root privileges -+as soon as it starts up to reduce the risk of a privilege escalation attack. -+.PP -+During normal operation, -+the effective user ID it chooses is the owner of the state directory. -+This allows it to continue to access files in that directory after it -+has dropped its root privileges. -+To control which user ID -+.B rpc.statd -+chooses, simply use -+.BR chown (1) -+to set the owner of -+the state directory. -+.PP -+You can also protect your -+.B rpc.statd -+listeners using the -+.B tcp_wrapper -+library or -+.BR iptables (8). -+To use the - .B tcp_wrapper --library. You have to give the clients access to --.B rpc.statd --if they should be allowed to use it. To allow connects from clients of --the .bar.com domain you could use the following line in /etc/hosts.allow: -- --statd: .bar.com -- --You have to use the daemon name -+library, add the hostnames of peers that should be allowed access to -+.IR /etc/hosts.allow . -+Use the daemon name - .B statd --for the daemon name (even if the binary has a different name). -- --For further information please have a look at the -+even if the -+.B rpc.statd -+binary has a different filename. -+.P -+For further information see the - .BR tcpd (8) - and - .BR hosts_access (5) --manual pages. -- --.SH SIGNALS --.BR SIGUSR1 --causes --.B rpc.statd --to re-read the notify list from disk --and send notifications to clients. This can be used in High Availability NFS --(HA-NFS) environments to notify clients to reacquire file locks upon takeover --of an NFS export from another server. -- -+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 -+should be observed, including: -+.IP -+The UTS nodename of your systems should match the DNS names that NFS -+peers use to contact them -+.IP -+The UTS nodenames of your systems should always be fully qualified domain names -+.IP -+The forward and reverse DNS mapping of the UTS nodenames should be -+consistent -+.IP -+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. -+Both may continue monitoring each other for a time in case subsequent -+NFS traffic between the two results in fresh mounts and additional -+file locking. -+.PP -+On Linux, if the -+.B lockd -+kernel module is unloaded during normal operation, -+all remote NFS peers are unmonitored. -+This can happen on an NFS client, for example, -+if an automounter removes all NFS mount -+points due to inactivity. -+.SS High-availability callouts -+.B rpc.statd -+can exec a special callout program during processing of -+successful SM_MON, SM_UNMON, and SM_UNMON_ALL requests. -+Such a program may be used in High Availability NFS (HA-NFS) -+environments to track lock state that may need to be migrated after -+a system reboot. -+.PP -+The name of the callout program is specified with the -+.B -H -+option. -+The program is run with 3 arguments: -+The first is either -+.B add-client -+or -+.B del-client -+depending on the reason for the callout. -+The second is the -+.I mon_name -+of the monitored peer. -+The third is the -+.I caller_name -+of the requesting lock manager. -+.SS IPv6 and TI-RPC support -+TI-RPC is a pre-requisite for supporting NFS on IPv6. -+If TI-RPC support is built into -+.BR rpc.statd , -+it attempts to start listeners on network transports marked -+'visible' in -+.IR /etc/netconfig . -+As long as at least one network transport listener starts successfully, -+.B rpc.statd -+will operate. - .SH FILES --.BR /var/lib/nfs/state -+.TP 2.5i -+.I /var/lib/nfs/sm -+directory containing monitor list -+.TP 2.5i -+.I /var/lib/nfs/sm.bak -+directory containing notify list -+.TP 2.5i -+.I /var/lib/nfs/state -+NSM state number for this host -+.TP 2.5i -+.I /var/run/run.statd.pid -+pid file -+.TP 2.5i -+.I /etc/netconfig -+network transport capability database -+.SH SEE ALSO -+.BR sm-notify (8), -+.BR nfs (5), -+.BR rpc.nfsd (8), -+.BR rpcbind (8), -+.BR tcpd (8), -+.BR hosts_access (5), -+.BR iptables (8), -+.BR netconfig (5) -+.sp -+RFC 1094 - "NFS: Network File System Protocol Specification" - .br --.BR /var/lib/nfs/sm/* -+RFC 1813 - "NFS Version 3 Protocol Specification" - .br --.BR /var/lib/nfs/sm.bak/* --.SH SEE ALSO --.BR rpc.nfsd(8), --.BR portmap(8) -+OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11 - .SH AUTHORS --.br - Jeff Uphoff - .br - Olaf Kirch -@@ -195,3 +404,5 @@ H.J. Lu - Lon Hohberger - .br - Paul Clements -+.br -+Chuck Lever -diff --git a/utils/statd/svc_run.c b/utils/statd/svc_run.c -index 14ee663..d98ecee 100644 ---- a/utils/statd/svc_run.c -+++ b/utils/statd/svc_run.c -@@ -101,12 +101,12 @@ my_svc_run(void) - - tv.tv_sec = NL_WHEN(notify) - now; - tv.tv_usec = 0; -- dprintf(N_DEBUG, "Waiting for reply... (timeo %d)", -+ xlog(D_GENERAL, "Waiting for reply... (timeo %d)", - tv.tv_sec); - selret = select(FD_SETSIZE, &readfds, - (void *) 0, (void *) 0, &tv); - } else { -- dprintf(N_DEBUG, "Waiting for client connections."); -+ xlog(D_GENERAL, "Waiting for client connections"); - selret = select(FD_SETSIZE, &readfds, - (void *) 0, (void *) 0, (struct timeval *) 0); - } -@@ -116,8 +116,7 @@ my_svc_run(void) - if (errno == EINTR || errno == ECONNREFUSED - || errno == ENETUNREACH || errno == EHOSTUNREACH) - continue; -- note(N_ERROR, "my_svc_run() - select: %s", -- strerror (errno)); -+ xlog(L_ERROR, "my_svc_run() - select: %m"); - return; - - case 0: -diff --git a/utils/statd/version.h b/utils/statd/version.h -deleted file mode 100644 -index 12f16bd..0000000 ---- a/utils/statd/version.h -+++ /dev/null -@@ -1,7 +0,0 @@ --/* -- * Copyright (C) 1997-1999 Jeffrey A. Uphoff -- * -- * NSM for Linux. -- */ -- --#define STATD_RELEASE "1.1.1" diff --git a/nfs-utils-1.2.2-rc9.patch b/nfs-utils-1.2.2-rc9.patch new file mode 100644 index 0000000..28685d2 --- /dev/null +++ b/nfs-utils-1.2.2-rc9.patch @@ -0,0 +1,12337 @@ +diff --git a/.gitignore b/.gitignore +index 632609e..4bff9e3 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -55,10 +55,15 @@ support/export/mount.h + support/export/mount_clnt.c + support/export/mount_xdr.c + support/include/mount.h +-utils/statd/sm_inter.h +-utils/statd/sm_inter_clnt.c +-utils/statd/sm_inter_svc.c +-utils/statd/sm_inter_xdr.c ++support/nsm/sm_inter.h ++support/nsm/sm_inter_clnt.c ++support/nsm/sm_inter_svc.c ++support/nsm/sm_inter_xdr.c ++support/include/sm_inter.h ++tests/nsm_client/nlm_sm_inter.h ++tests/nsm_client/nlm_sm_inter_clnt.c ++tests/nsm_client/nlm_sm_inter_svc.c ++tests/nsm_client/nlm_sm_inter_xdr.c + # cscope database files + cscope.* + # generic editor backup et al +diff --git a/Makefile.am b/Makefile.am +index b3a6e91..ae7cd16 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -2,7 +2,7 @@ + + AUTOMAKE_OPTIONS = foreign + +-SUBDIRS = tools support utils linux-nfs ++SUBDIRS = tools support utils linux-nfs tests + + MAINTAINERCLEANFILES = Makefile.in + +diff --git a/aclocal/ipv6.m4 b/aclocal/ipv6.m4 +index 2490f3d..5ee8fb6 100644 +--- a/aclocal/ipv6.m4 ++++ b/aclocal/ipv6.m4 +@@ -15,8 +15,8 @@ AC_DEFUN([AC_IPV6], [ + fi + + dnl IPv6-enabled networking functions required for IPv6 +- AC_CHECK_FUNCS([getnameinfo bindresvport_sa], , +- [AC_MSG_ERROR([Missing functions needed for IPv6.])]) ++ AC_CHECK_FUNCS([getifaddrs getnameinfo bindresvport_sa], , ++ [AC_MSG_ERROR([Missing library functions needed for IPv6.])]) + + dnl Need to detect presence of IPv6 networking at run time via + dnl getaddrinfo(3); old versions of glibc do not support ADDRCONFIG +diff --git a/aclocal/libcap.m4 b/aclocal/libcap.m4 +new file mode 100644 +index 0000000..eabe507 +--- /dev/null ++++ b/aclocal/libcap.m4 +@@ -0,0 +1,15 @@ ++dnl Checks for libcap.so ++dnl ++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_CHECK_HEADERS([sys/capability.h], , ++ [AC_MSG_ERROR([libcap headers not found.])]) ++ ++])dnl +diff --git a/configure.ac b/configure.ac +index 3ad415c..1dc4249 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -166,6 +166,9 @@ fi + dnl Check for TI-RPC library and headers + AC_LIBTIRPC + ++dnl Check for -lcap ++AC_LIBCAP ++ + # Check whether user wants TCP wrappers support + AC_TCP_WRAPPERS + +@@ -327,7 +330,7 @@ AC_FUNC_STAT + AC_FUNC_VPRINTF + AC_CHECK_FUNCS([alarm atexit dup2 fdatasync ftruncate getcwd \ + gethostbyaddr gethostbyname gethostname getmntent \ +- getnameinfo getrpcbyname \ ++ getnameinfo getrpcbyname getifaddrs \ + gettimeofday hasmntopt inet_ntoa innetgr memset mkdir pathconf \ + realpath rmdir select socket strcasecmp strchr strdup \ + strerror strrchr strtol strtoul sigprocmask]) +@@ -402,6 +405,7 @@ AC_CONFIG_FILES([ + support/include/Makefile + support/misc/Makefile + support/nfs/Makefile ++ support/nsm/Makefile + tools/Makefile + tools/locktest/Makefile + tools/nlmtest/Makefile +@@ -416,6 +420,8 @@ AC_CONFIG_FILES([ + utils/nfsd/Makefile + utils/nfsstat/Makefile + utils/showmount/Makefile +- utils/statd/Makefile]) ++ utils/statd/Makefile ++ tests/Makefile ++ tests/nsm_client/Makefile]) + AC_OUTPUT + +diff --git a/support/Makefile.am b/support/Makefile.am +index aa4d692..cb37733 100644 +--- a/support/Makefile.am ++++ b/support/Makefile.am +@@ -1,6 +1,6 @@ + ## Process this file with automake to produce Makefile.in + +-SUBDIRS = export include misc nfs ++SUBDIRS = export include misc nfs nsm + + MAINTAINERCLEANFILES = Makefile.in + +diff --git a/support/export/client.c b/support/export/client.c +index 5fcf355..6236561 100644 +--- a/support/export/client.c ++++ b/support/export/client.c +@@ -297,7 +297,7 @@ name_cmp(char *a, char *b) + /* compare strings a and b, but only upto ',' in a */ + while (*a && *b && *a != ',' && *a == *b) + a++, b++; +- if (!*b && (!*a || !a == ',') ) ++ if (!*b && (!*a || *a == ',')) + return 0; + if (!*b) return 1; + if (!*a || *a == ',') return -1; +diff --git a/support/export/export.c b/support/export/export.c +index e5e6cb0..2943466 100644 +--- a/support/export/export.c ++++ b/support/export/export.c +@@ -28,6 +28,22 @@ static int export_check(nfs_export *, struct hostent *, char *); + static nfs_export * + export_allowed_internal(struct hostent *hp, char *path); + ++static void warn_duplicated_exports(nfs_export *exp, struct exportent *eep) ++{ ++ if (exp->m_export.e_flags != eep->e_flags) { ++ xlog(L_ERROR, "incompatible duplicated export entries:"); ++ xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname, ++ eep->e_path, eep->e_flags); ++ xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname, ++ exp->m_export.e_path, exp->m_export.e_flags); ++ } else { ++ xlog(L_ERROR, "duplicated export entries:"); ++ xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path); ++ xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname, ++ exp->m_export.e_path); ++ } ++} ++ + int + export_read(char *fname) + { +@@ -36,27 +52,13 @@ export_read(char *fname) + + setexportent(fname, "r"); + while ((eep = getexportent(0,1)) != NULL) { +- exp = export_lookup(eep->e_hostname, eep->e_path, 0); +- if (!exp) +- export_create(eep,0); +- else { +- if (exp->m_export.e_flags != eep->e_flags) { +- xlog(L_ERROR, "incompatible duplicated export entries:"); +- xlog(L_ERROR, "\t%s:%s (0x%x) [IGNORED]", eep->e_hostname, +- eep->e_path, eep->e_flags); +- xlog(L_ERROR, "\t%s:%s (0x%x)", exp->m_export.e_hostname, +- exp->m_export.e_path, exp->m_export.e_flags); +- } +- else { +- xlog(L_ERROR, "duplicated export entries:"); +- xlog(L_ERROR, "\t%s:%s", eep->e_hostname, eep->e_path); +- xlog(L_ERROR, "\t%s:%s", exp->m_export.e_hostname, +- exp->m_export.e_path); +- } +- } ++ exp = export_lookup(eep->e_hostname, eep->e_path, 0); ++ if (!exp) ++ export_create(eep, 0); ++ else ++ warn_duplicated_exports(exp, eep); + } + endexportent(); +- + return 0; + } + +diff --git a/support/export/xtab.c b/support/export/xtab.c +index 3b1dcce..2a43193 100644 +--- a/support/export/xtab.c ++++ b/support/export/xtab.c +@@ -19,7 +19,9 @@ + #include "exportfs.h" + #include "xio.h" + #include "xlog.h" ++#include "v4root.h" + ++int v4root_needed; + static void cond_rename(char *newfile, char *oldfile); + + static int +@@ -36,6 +38,8 @@ xtab_read(char *xtab, char *lockfn, int is_export) + if ((lockid = xflock(lockfn, "r")) < 0) + return 0; + setexportent(xtab, "r"); ++ if (is_export == 1) ++ v4root_needed = 1; + while ((xp = getexportent(is_export==0, 0)) != NULL) { + if (!(exp = export_lookup(xp->e_hostname, xp->e_path, is_export != 1)) && + !(exp = export_create(xp, is_export!=1))) { +@@ -48,6 +52,8 @@ xtab_read(char *xtab, char *lockfn, int is_export) + case 1: + exp->m_xtabent = 1; + exp->m_mayexport = 1; ++ if ((xp->e_flags & NFSEXP_FSID) && xp->e_fsid == 0) ++ v4root_needed = 0; + break; + case 2: + exp->m_exported = -1;/* may be exported */ +diff --git a/support/include/Makefile.am b/support/include/Makefile.am +index f5a77ec..4b33ee9 100644 +--- a/support/include/Makefile.am ++++ b/support/include/Makefile.am +@@ -9,6 +9,8 @@ noinst_HEADERS = \ + nfs_mntent.h \ + nfs_paths.h \ + nfslib.h \ ++ nfsrpc.h \ ++ nsm.h \ + rpcmisc.h \ + tcpwrapper.h \ + xio.h \ +diff --git a/support/include/exportfs.h b/support/include/exportfs.h +index a5cf482..470b2ec 100644 +--- a/support/include/exportfs.h ++++ b/support/include/exportfs.h +@@ -99,10 +99,19 @@ int xtab_mount_write(void); + int xtab_export_write(void); + void xtab_append(nfs_export *); + ++int secinfo_addflavor(struct flav_info *, struct exportent *); ++ + int rmtab_read(void); + + struct nfskey * key_lookup(char *hname); + ++struct export_features { ++ unsigned int flags; ++ unsigned int secinfo_flags; ++}; ++ ++struct export_features *get_export_features(void); ++ + /* Record export error. */ + extern int export_errno; + +diff --git a/support/include/ha-callout.h b/support/include/ha-callout.h +index efb70fb..1164336 100644 +--- a/support/include/ha-callout.h ++++ b/support/include/ha-callout.h +@@ -53,11 +53,7 @@ ha_callout(char *event, char *arg1, char *arg2, int arg3) + default: pid = waitpid(pid, &ret, 0); + } + sigaction(SIGCHLD, &oldact, &newact); +-#ifdef dprintf +- dprintf(N_DEBUG, "ha callout returned %d\n", WEXITSTATUS(ret)); +-#else + xlog(D_GENERAL, "ha callout returned %d\n", WEXITSTATUS(ret)); +-#endif + } + + #endif +diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h +index f7a99ba..1547a87 100644 +--- a/support/include/nfs/export.h ++++ b/support/include/nfs/export.h +@@ -24,6 +24,17 @@ + #define NFSEXP_FSID 0x2000 + #define NFSEXP_CROSSMOUNT 0x4000 + #define NFSEXP_NOACL 0x8000 /* reserved for possible ACL related use */ +-#define NFSEXP_ALLFLAGS 0xFFFF ++#define NFSEXP_V4ROOT 0x10000 ++/* ++ * All flags supported by the kernel before addition of the ++ * export_features interface: ++ */ ++#define NFSEXP_OLDFLAGS 0x7E3F ++/* ++ * Flags that can vary per flavor, for kernels before addition of the ++ * export_features interface: ++ */ ++#define NFSEXP_OLD_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \ ++ | NFSEXP_ALLSQUASH) + + #endif /* _NSF_EXPORT_H */ +diff --git a/support/include/nfsrpc.h b/support/include/nfsrpc.h +index dff6af7..4db35ab 100644 +--- a/support/include/nfsrpc.h ++++ b/support/include/nfsrpc.h +@@ -59,16 +59,6 @@ static inline void nfs_clear_rpc_createerr(void) + } + + /* +- * Extract port value from a socket address +- */ +-extern uint16_t nfs_get_port(const struct sockaddr *); +- +-/* +- * Set port value in a socket address +- */ +-extern void nfs_set_port(struct sockaddr *, const uint16_t); +- +-/* + * Look up an RPC program name in /etc/rpc + */ + extern rpcprog_t nfs_getrpcbyname(const rpcprog_t, const char *table[]); +@@ -90,6 +80,18 @@ extern CLIENT *nfs_get_priv_rpcclient( const struct sockaddr *, + struct timeval *); + + /* ++ * Convert a netid to a protocol number and protocol family ++ */ ++extern int nfs_get_proto(const char *netid, sa_family_t *family, ++ unsigned long *protocol); ++ ++/* ++ * Convert a protocol family and protocol name to a netid ++ */ ++extern char *nfs_get_netid(const sa_family_t family, ++ const unsigned long protocol); ++ ++/* + * Convert a socket address to a universal address + */ + extern char *nfs_sockaddr2universal(const struct sockaddr *); +@@ -158,4 +160,4 @@ extern int nfs_rpc_ping(const struct sockaddr *sap, + const unsigned short protocol, + const struct timeval *timeout); + +-#endif /* __NFS_UTILS_NFSRPC_H */ ++#endif /* !__NFS_UTILS_NFSRPC_H */ +diff --git a/support/include/nsm.h b/support/include/nsm.h +new file mode 100644 +index 0000000..fb4d823 +--- /dev/null ++++ b/support/include/nsm.h +@@ -0,0 +1,93 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++/* ++ * NSM for Linux. ++ */ ++ ++#ifndef NFS_UTILS_SUPPORT_NSM_H ++#define NFS_UTILS_SUPPORT_NSM_H ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "sm_inter.h" ++ ++typedef unsigned int ++ (*nsm_populate_t)(const char *hostname, ++ const struct sockaddr *sap, ++ const struct mon *mon, ++ const time_t timestamp); ++ ++/* file.c */ ++ ++extern _Bool nsm_setup_pathnames(const char *progname, ++ const char *parentdir); ++extern _Bool nsm_is_default_parentdir(void); ++extern _Bool nsm_drop_privileges(const int pidfd); ++ ++extern int nsm_get_state(_Bool update); ++extern void nsm_update_kernel_state(const int state); ++ ++extern unsigned int ++ nsm_retire_monitored_hosts(void); ++extern unsigned int ++ nsm_load_monitor_list(nsm_populate_t func); ++extern unsigned int ++ nsm_load_notify_list(nsm_populate_t func); ++ ++extern _Bool nsm_insert_monitored_host(const char *hostname, ++ const struct sockaddr *sap, const struct mon *m); ++extern void nsm_delete_monitored_host(const char *hostname, ++ const char *mon_name, const char *my_name); ++extern void nsm_delete_notified_host(const char *hostname, ++ const char *mon_name, const char *my_name); ++extern size_t nsm_priv_to_hex(const char *priv, char *buf, ++ const size_t buflen); ++ ++/* rpc.c */ ++ ++#define NSM_MAXMSGSIZE (2048u) ++ ++extern uint32_t nsm_xmit_getport(const int sock, ++ const struct sockaddr_in *sin, ++ const unsigned long program, ++ const unsigned long version); ++extern uint32_t nsm_xmit_getaddr(const int sock, ++ const struct sockaddr_in6 *sin6, ++ const rpcprog_t program, const rpcvers_t version); ++extern uint32_t nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap, ++ const rpcprog_t program, const rpcvers_t version); ++extern uint32_t nsm_xmit_notify(const int sock, const struct sockaddr *sap, ++ const socklen_t salen, const rpcprog_t program, ++ const char *mon_name, const int state); ++extern uint32_t nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap, ++ const socklen_t salen, const struct mon *m, ++ const int state); ++extern uint32_t nsm_parse_reply(XDR *xdrs); ++extern unsigned long ++ nsm_recv_getport(XDR *xdrs); ++extern uint16_t nsm_recv_getaddr(XDR *xdrs); ++extern uint16_t nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs); ++ ++#endif /* !NFS_UTILS_SUPPORT_NSM_H */ +diff --git a/support/include/rpcmisc.h b/support/include/rpcmisc.h +index f551a85..1b8f411 100644 +--- a/support/include/rpcmisc.h ++++ b/support/include/rpcmisc.h +@@ -41,7 +41,12 @@ struct rpc_dtable { + (xdrproc_t)xdr_##res_type, sizeof(res_type), \ + } + +- ++void nfs_svc_unregister(const rpcprog_t program, ++ const rpcvers_t version); ++unsigned int nfs_svc_create(char *name, const rpcprog_t program, ++ const rpcvers_t version, ++ void (*dispatch)(struct svc_req *, SVCXPRT *), ++ const uint16_t port); + void rpc_init(char *name, int prog, int vers, + void (*dispatch)(struct svc_req *, SVCXPRT *), + int defport); +diff --git a/support/include/sockaddr.h b/support/include/sockaddr.h +new file mode 100644 +index 0000000..732514b +--- /dev/null ++++ b/support/include/sockaddr.h +@@ -0,0 +1,237 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++#ifndef NFS_UTILS_SOCKADDR_H ++#define NFS_UTILS_SOCKADDR_H ++ ++#include ++#include ++#include ++ ++/* ++ * This type is for defining buffers that contain network socket ++ * addresses. ++ * ++ * Casting a "struct sockaddr *" to the address of a "struct ++ * sockaddr_storage" breaks C aliasing rules. The "union ++ * nfs_sockaddr" type follows C aliasing rules yet specifically ++ * allows converting pointers to it between "struct sockaddr *" ++ * and a few other network sockaddr-related pointer types. ++ * ++ * Note that this union is much smaller than a sockaddr_storage. ++ * It should be used only for AF_INET or AF_INET6 socket addresses. ++ * An AF_LOCAL sockaddr_un, for example, will clearly not fit into ++ * a buffer of this type. ++ */ ++union nfs_sockaddr { ++ struct sockaddr sa; ++ struct sockaddr_in s4; ++ struct sockaddr_in6 s6; ++}; ++ ++#if SIZEOF_SOCKLEN_T - 0 == 0 ++#define socklen_t unsigned int ++#endif ++ ++#define SIZEOF_SOCKADDR_UNKNOWN (socklen_t)0 ++#define SIZEOF_SOCKADDR_IN (socklen_t)sizeof(struct sockaddr_in) ++ ++#ifdef IPV6_SUPPORTED ++#define SIZEOF_SOCKADDR_IN6 (socklen_t)sizeof(struct sockaddr_in6) ++#else /* !IPV6_SUPPORTED */ ++#define SIZEOF_SOCKADDR_IN6 SIZEOF_SOCKADDR_UNKNOWN ++#endif /* !IPV6_SUPPORTED */ ++ ++/** ++ * nfs_sockaddr_length - return the size in bytes of a socket address ++ * @sap: pointer to socket address ++ * ++ * Returns the size in bytes of @sap, or zero if the family is ++ * not recognized. ++ */ ++static inline socklen_t ++nfs_sockaddr_length(const struct sockaddr *sap) ++{ ++ switch (sap->sa_family) { ++ case AF_INET: ++ return SIZEOF_SOCKADDR_IN; ++ case AF_INET6: ++ return SIZEOF_SOCKADDR_IN6; ++ } ++ return SIZEOF_SOCKADDR_UNKNOWN; ++} ++ ++static inline uint16_t ++get_port4(const struct sockaddr *sap) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ return ntohs(sin->sin_port); ++} ++ ++#ifdef IPV6_SUPPORTED ++static inline uint16_t ++get_port6(const struct sockaddr *sap) ++{ ++ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; ++ return ntohs(sin6->sin6_port); ++} ++#else /* !IPV6_SUPPORTED */ ++static inline uint16_t ++get_port6(__attribute__ ((unused)) const struct sockaddr *sap) ++{ ++ return 0; ++} ++#endif /* !IPV6_SUPPORTED */ ++ ++/** ++ * nfs_get_port - extract port value from a socket address ++ * @sap: pointer to socket address ++ * ++ * Returns port value in host byte order, or zero if the ++ * socket address contains an unrecognized family. ++ */ ++static inline uint16_t ++nfs_get_port(const struct sockaddr *sap) ++{ ++ switch (sap->sa_family) { ++ case AF_INET: ++ return get_port4(sap); ++ case AF_INET6: ++ return get_port6(sap); ++ } ++ return 0; ++} ++ ++static inline void ++set_port4(struct sockaddr *sap, const uint16_t port) ++{ ++ struct sockaddr_in *sin = (struct sockaddr_in *)sap; ++ sin->sin_port = htons(port); ++} ++ ++#ifdef IPV6_SUPPORTED ++static inline void ++set_port6(struct sockaddr *sap, const uint16_t port) ++{ ++ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; ++ sin6->sin6_port = htons(port); ++} ++#else /* !IPV6_SUPPORTED */ ++static inline void ++set_port6(__attribute__ ((unused)) struct sockaddr *sap, ++ __attribute__ ((unused)) const uint16_t port) ++{ ++} ++#endif /* !IPV6_SUPPORTED */ ++ ++/** ++ * nfs_set_port - set port value in a socket address ++ * @sap: pointer to socket address ++ * @port: port value to set ++ * ++ */ ++static inline void ++nfs_set_port(struct sockaddr *sap, const uint16_t port) ++{ ++ switch (sap->sa_family) { ++ case AF_INET: ++ set_port4(sap, port); ++ break; ++ case AF_INET6: ++ set_port6(sap, port); ++ break; ++ } ++} ++ ++/** ++ * nfs_is_v4_loopback - test to see if socket address is AF_INET loopback ++ * @sap: pointer to socket address ++ * ++ * Returns true if the socket address is the standard IPv4 loopback ++ * address; otherwise false is returned. ++ */ ++static inline _Bool ++nfs_is_v4_loopback(const struct sockaddr *sap) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ ++ if (sin->sin_family != AF_INET) ++ return false; ++ if (sin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) ++ return false; ++ return true; ++} ++ ++static inline _Bool ++compare_sockaddr4(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; ++} ++ ++#ifdef IPV6_SUPPORTED ++static inline _Bool ++compare_sockaddr6(const struct sockaddr *sa1, const struct sockaddr *sa2) ++{ ++ const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)sa1; ++ const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)sa2; ++ ++ if ((IN6_IS_ADDR_LINKLOCAL((char *)&sin1->sin6_addr) && ++ IN6_IS_ADDR_LINKLOCAL((char *)&sin2->sin6_addr)) || ++ (IN6_IS_ADDR_SITELOCAL((char *)&sin1->sin6_addr) && ++ IN6_IS_ADDR_SITELOCAL((char *)&sin2->sin6_addr))) ++ if (sin1->sin6_scope_id != sin2->sin6_scope_id) ++ return false; ++ ++ return IN6_ARE_ADDR_EQUAL((char *)&sin1->sin6_addr, ++ (char *)&sin2->sin6_addr); ++} ++#else /* !IPV6_SUPPORTED */ ++static inline _Bool ++compare_sockaddr6(__attribute__ ((unused)) const struct sockaddr *sa1, ++ __attribute__ ((unused)) const struct sockaddr *sa2) ++{ ++ return false; ++} ++#endif /* !IPV6_SUPPORTED */ ++ ++/** ++ * nfs_compare_sockaddr - compare two socket addresses for equality ++ * @sa1: pointer to a socket address ++ * @sa2: pointer to a socket address ++ * ++ * Returns true if the two socket addresses contain equivalent ++ * network addresses; otherwise false is returned. ++ */ ++static inline _Bool ++nfs_compare_sockaddr(const struct sockaddr *sa1, const struct sockaddr *sa2) ++{ ++ if (sa1->sa_family == sa2->sa_family) ++ switch (sa1->sa_family) { ++ case AF_INET: ++ return compare_sockaddr4(sa1, sa2); ++ case AF_INET6: ++ return compare_sockaddr6(sa1, sa2); ++ } ++ ++ return false; ++} ++ ++#endif /* !NFS_UTILS_SOCKADDR_H */ +diff --git a/support/include/tcpwrapper.h b/support/include/tcpwrapper.h +index 98cf806..f735106 100644 +--- a/support/include/tcpwrapper.h ++++ b/support/include/tcpwrapper.h +@@ -5,14 +5,8 @@ + #include + #include + +-extern int verboselog; +- +-extern int allow_severity; +-extern int deny_severity; +- +-extern int good_client(char *daemon, struct sockaddr_in *addr); +-extern int from_local (struct sockaddr_in *addr); +-extern int check_default(char *daemon, struct sockaddr_in *addr, +- u_long proc, u_long prog); ++extern int from_local(const struct sockaddr *sap); ++extern int check_default(char *name, struct sockaddr *sap, ++ const unsigned long program); + + #endif /* TCP_WRAPPER_H */ +diff --git a/support/include/v4root.h b/support/include/v4root.h +new file mode 100644 +index 0000000..706c15c +--- /dev/null ++++ b/support/include/v4root.h +@@ -0,0 +1,15 @@ ++/* ++ * Copyright (C) 2009 Red Hat ++ * support/include/v4root.h ++ * ++ * Support routines for dynamic pseudo roots. ++ * ++ */ ++ ++#ifndef V4ROOT_H ++#define V4ROOT_H ++ ++extern int v4root_needed; ++extern void v4root_set(void); ++ ++#endif /* V4ROOT_H */ +diff --git a/support/misc/from_local.c b/support/misc/from_local.c +index 89ccc4a..e2de969 100644 +--- a/support/misc/from_local.c ++++ b/support/misc/from_local.c +@@ -37,32 +37,100 @@ + static char sccsid[] = "@(#) from_local.c 1.3 96/05/31 15:52:57"; + #endif + +-#ifdef TEST +-#undef perror ++#ifdef HAVE_CONFIG_H ++#include + #endif + + #include + #include ++#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include + ++#include "sockaddr.h" ++#include "tcpwrapper.h" ++#include "xlog.h" ++ + #ifndef TRUE + #define TRUE 1 + #define FALSE 0 + #endif + +- /* +- * With virtual hosting, each hardware network interface can have multiple +- * network addresses. On such machines the number of machine addresses can +- * be surprisingly large. +- */ ++#ifdef HAVE_GETIFADDRS ++ ++#include ++#include ++ ++/** ++ * from_local - determine whether request comes from the local system ++ * @sap: pointer to socket address to check ++ * ++ * With virtual hosting, each hardware network interface can have ++ * multiple network addresses. On such machines the number of machine ++ * addresses can be surprisingly large. ++ * ++ * We also expect the local network configuration to change over time, ++ * so call getifaddrs(3) more than once, but not too often. ++ * ++ * Returns TRUE if the sockaddr contains an address of one of the local ++ * network interfaces. Otherwise FALSE is returned. ++ */ ++int ++from_local(const struct sockaddr *sap) ++{ ++ static struct ifaddrs *ifaddr = NULL; ++ static time_t last_update = 0; ++ struct ifaddrs *ifa; ++ unsigned int count; ++ time_t now; ++ ++ if (time(&now) == ((time_t)-1)) { ++ xlog(L_ERROR, "%s: time(2): %m", __func__); ++ ++ /* If we don't know what time it is, use the ++ * existing ifaddr list, if one exists */ ++ now = last_update; ++ if (ifaddr == NULL) ++ now++; ++ } ++ if (now != last_update) { ++ xlog(D_GENERAL, "%s: updating local if addr list", __func__); ++ ++ if (ifaddr) ++ freeifaddrs(ifaddr); ++ ++ if (getifaddrs(&ifaddr) == -1) { ++ xlog(L_ERROR, "%s: getifaddrs(3): %m", __func__); ++ return FALSE; ++ } ++ ++ last_update = now; ++ } ++ ++ count = 0; ++ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { ++ if ((ifa->ifa_flags & IFF_UP) && ++ nfs_compare_sockaddr(sap, ifa->ifa_addr)) { ++ xlog(D_GENERAL, "%s: incoming address matches " ++ "local interface address", __func__); ++ return TRUE; ++ } else ++ count++; ++ } ++ ++ xlog(D_GENERAL, "%s: checked %u local if addrs; " ++ "incoming address not found", __func__, count); ++ return FALSE; ++} ++ ++#else /* !HAVE_GETIFADDRS */ ++ + static int num_local; + static int num_addrs; + static struct in_addr *addrs; +@@ -81,7 +149,7 @@ static int grow_addrs(void) + new_num = (addrs == 0) ? 1 : num_addrs + num_addrs; + new_addrs = (struct in_addr *) malloc(sizeof(*addrs) * new_num); + if (new_addrs == 0) { +- perror("portmap: out of memory"); ++ xlog_warn("%s: out of memory", __func__); + return (0); + } else { + if (addrs != 0) { +@@ -112,13 +180,13 @@ find_local(void) + */ + + if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { +- perror("socket"); ++ xlog_warn("%s: socket(2): %m", __func__); + return (0); + } + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { +- perror("SIOCGIFCONF"); ++ xlog_warn("%s: ioctl(SIOCGIFCONF): %m", __func__); + (void) close(sock); + return (0); + } +@@ -130,10 +198,10 @@ find_local(void) + if (ifr->ifr_addr.sa_family == AF_INET) { /* IP net interface */ + ifreq = *ifr; + if (ioctl(sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) { +- perror("SIOCGIFFLAGS"); ++ xlog_warn("%s: ioctl(SIOCGIFFLAGS): %m", __func__); + } else if (ifreq.ifr_flags & IFF_UP) { /* active interface */ + if (ioctl(sock, SIOCGIFADDR, (char *) &ifreq) < 0) { +- perror("SIOCGIFADDR"); ++ xlog_warn("%s: ioctl(SIOCGIFADDR): %m", __func__); + } else { + if (num_local >= num_addrs) + if (grow_addrs() == 0) +@@ -153,14 +221,28 @@ find_local(void) + return (num_local); + } + +-/* from_local - determine whether request comes from the local system */ ++/** ++ * from_local - determine whether request comes from the local system ++ * @sap: pointer to socket address to check ++ * ++ * With virtual hosting, each hardware network interface can have ++ * multiple network addresses. On such machines the number of machine ++ * addresses can be surprisingly large. ++ * ++ * Returns TRUE if the sockaddr contains an address of one of the local ++ * network interfaces. Otherwise FALSE is returned. ++ */ + int +-from_local(struct sockaddr_in *addr) ++from_local(const struct sockaddr *sap) + { ++ const struct sockaddr_in *addr = (const struct sockaddr_in *)sap; + int i; + ++ if (sap->sa_family != AF_INET) ++ return (FALSE); ++ + if (addrs == 0 && find_local() == 0) +- syslog(LOG_ERR, "cannot find any active local network interfaces"); ++ xlog(L_ERROR, "Cannot find any active local network interfaces"); + + for (i = 0; i < num_local; i++) { + if (memcmp((char *) &(addr->sin_addr), (char *) &(addrs[i]), +@@ -172,9 +254,8 @@ from_local(struct sockaddr_in *addr) + + #ifdef TEST + +-main() ++int main(void) + { +- char *inet_ntoa(); + int i; + + find_local(); +@@ -182,4 +263,6 @@ main() + printf("%s\n", inet_ntoa(addrs[i])); + } + +-#endif ++#endif /* TEST */ ++ ++#endif /* !HAVE_GETIFADDRS */ +diff --git a/support/misc/tcpwrapper.c b/support/misc/tcpwrapper.c +index 1da6020..06b0a46 100644 +--- a/support/misc/tcpwrapper.c ++++ b/support/misc/tcpwrapper.c +@@ -34,13 +34,12 @@ + #ifdef HAVE_CONFIG_H + #include + #endif ++ + #ifdef HAVE_LIBWRAP +-#include + #include + #include + #include + #include +-#include + #include + #include + #include +@@ -49,108 +48,146 @@ + #include + #include + ++#include "sockaddr.h" ++#include "tcpwrapper.h" + #include "xlog.h" + + #ifdef SYSV40 + #include + #include +-#endif ++#endif /* SYSV40 */ + +-static void logit(int severity, struct sockaddr_in *addr, +- u_long procnum, u_long prognum, char *text); +-static int check_files(void); ++#define ALLOW 1 ++#define DENY 0 + +-/* +- * These need to exist since they are externed +- * public header files. +- */ +-int verboselog = 0; +-int allow_severity = LOG_INFO; +-int deny_severity = LOG_WARNING; ++#ifdef IPV6_SUPPORTED ++static void ++present_address(const struct sockaddr *sap, char *buf, const size_t buflen) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; ++ socklen_t len = (socklen_t)buflen; ++ ++ switch (sap->sa_family) { ++ case AF_INET: ++ if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0) ++ return; ++ case AF_INET6: ++ if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, len) != 0) ++ return; ++ } + +-#define log_bad_host(addr, proc, prog) \ +- logit(deny_severity, addr, proc, prog, "request from unauthorized host") ++ memset(buf, 0, buflen); ++ strncpy(buf, "unrecognized caller", buflen); ++} ++#else /* !IPV6_SUPPORTED */ ++static void ++present_address(const struct sockaddr *sap, char *buf, const size_t buflen) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ socklen_t len = (socklen_t)buflen; + +-#define ALLOW 1 +-#define DENY 0 ++ if (sap->sa_family == AF_INET) ++ if (inet_ntop(AF_INET, &sin->sin_addr, buf, len) != 0) ++ return; ++ ++ memset(buf, 0, buflen); ++ strncpy(buf, "unrecognized caller", (size_t)buflen); ++} ++#endif /* !IPV6_SUPPORTED */ + + typedef struct _haccess_t { +- TAILQ_ENTRY(_haccess_t) list; +- int access; +- struct in_addr addr; ++ TAILQ_ENTRY(_haccess_t) list; ++ int allowed; ++ union nfs_sockaddr address; + } haccess_t; + + #define HASH_TABLE_SIZE 1021 + typedef struct _hash_head { + TAILQ_HEAD(host_list, _haccess_t) h_head; + } hash_head; +-hash_head haccess_tbl[HASH_TABLE_SIZE]; +-static haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long); +-static void haccess_add(struct sockaddr_in *addr, u_long, int); + +-inline unsigned int strtoint(char *str) ++static hash_head haccess_tbl[HASH_TABLE_SIZE]; ++ ++static unsigned long ++strtoint(const char *str) + { +- unsigned int n = 0; +- int len = strlen(str); +- int i; ++ unsigned long i, n = 0; ++ size_t len = strlen(str); + +- for (i=0; i < len; i++) +- n+=((int)str[i])*i; ++ for (i = 0; i < len; i++) ++ n += (unsigned char)str[i] * i; + + return n; + } +-static inline int hashint(unsigned int num) ++ ++static unsigned int ++hashint(const unsigned long num) + { +- return num % HASH_TABLE_SIZE; ++ return (unsigned int)(num % HASH_TABLE_SIZE); + } +-#define HASH(_addr, _prog) \ +- hashint((strtoint((_addr))+(_prog))) + +-void haccess_add(struct sockaddr_in *addr, u_long prog, int access) ++static unsigned int ++HASH(const char *addr, const unsigned long program) ++{ ++ return hashint(strtoint(addr) + program); ++} ++ ++static void ++haccess_add(const struct sockaddr *sap, const char *address, ++ const unsigned long program, const int allowed) + { + hash_head *head; +- haccess_t *hptr; +- int hash; ++ haccess_t *hptr; ++ unsigned int hash; + + hptr = (haccess_t *)malloc(sizeof(haccess_t)); + if (hptr == NULL) + return; + +- hash = HASH(inet_ntoa(addr->sin_addr), prog); ++ hash = HASH(address, program); + head = &(haccess_tbl[hash]); + +- hptr->access = access; +- hptr->addr.s_addr = addr->sin_addr.s_addr; ++ hptr->allowed = allowed; ++ memcpy(&hptr->address, sap, (size_t)nfs_sockaddr_length(sap)); + + if (TAILQ_EMPTY(&head->h_head)) + TAILQ_INSERT_HEAD(&head->h_head, hptr, list); + else + TAILQ_INSERT_TAIL(&head->h_head, hptr, list); + } +-haccess_t *haccess_lookup(struct sockaddr_in *addr, u_long prog) ++ ++static haccess_t * ++haccess_lookup(const struct sockaddr *sap, const char *address, ++ const unsigned long program) + { + hash_head *head; +- haccess_t *hptr; +- int hash; ++ haccess_t *hptr; ++ unsigned int hash; + +- hash = HASH(inet_ntoa(addr->sin_addr), prog); ++ hash = HASH(address, program); + head = &(haccess_tbl[hash]); + + TAILQ_FOREACH(hptr, &head->h_head, list) { +- if (hptr->addr.s_addr == addr->sin_addr.s_addr) ++ if (nfs_compare_sockaddr(&hptr->address.sa, sap)) + return hptr; + } + return NULL; + } + +-int +-good_client(daemon, addr) +-char *daemon; +-struct sockaddr_in *addr; ++static void ++logit(const char *address) ++{ ++ xlog_warn("connect from %s denied: request from unauthorized host", ++ address); ++} ++ ++static int ++good_client(char *name, struct sockaddr *sap) + { + struct request_info req; + +- request_init(&req, RQ_DAEMON, daemon, RQ_CLIENT_SIN, addr, 0); ++ request_init(&req, RQ_DAEMON, name, RQ_CLIENT_SIN, sap, 0); + sock_methods(&req); + + if (hosts_access(&req)) +@@ -159,9 +196,8 @@ struct sockaddr_in *addr; + return DENY; + } + +-/* check_files - check to see if either access files have changed */ +- +-static int check_files() ++static int ++check_files(void) + { + static time_t allow_mtime, deny_mtime; + struct stat astat, dstat; +@@ -186,45 +222,48 @@ static int check_files() + return changed; + } + +-/* check_default - additional checks for NULL, DUMP, GETPORT and unknown */ +- ++/** ++ * check_default - additional checks for NULL, DUMP, GETPORT and unknown ++ * @name: pointer to '\0'-terminated ASCII string containing name of the ++ * daemon requesting the access check ++ * @sap: pointer to sockaddr containing network address of caller ++ * @program: RPC program number caller is attempting to access ++ * ++ * Returns TRUE if the caller is allowed access; otherwise FALSE is returned. ++ */ + int +-check_default(daemon, addr, proc, prog) +-char *daemon; +-struct sockaddr_in *addr; +-u_long proc; +-u_long prog; ++check_default(char *name, struct sockaddr *sap, const unsigned long program) + { + haccess_t *acc = NULL; + int changed = check_files(); ++ char buf[INET6_ADDRSTRLEN]; ++ ++ present_address(sap, buf, sizeof(buf)); + +- acc = haccess_lookup(addr, prog); +- if (acc && changed == 0) +- return (acc->access); ++ acc = haccess_lookup(sap, buf, program); ++ if (acc != NULL && changed == 0) { ++ xlog(D_GENERAL, "%s: access by %s %s (cached)", __func__, ++ buf, acc->allowed ? "ALLOWED" : "DENIED"); ++ return acc->allowed; ++ } + +- if (!(from_local(addr) || good_client(daemon, addr))) { +- log_bad_host(addr, proc, prog); +- if (acc) +- acc->access = FALSE; +- else +- haccess_add(addr, prog, FALSE); ++ if (!(from_local(sap) || good_client(name, sap))) { ++ logit(buf); ++ if (acc != NULL) ++ acc->allowed = FALSE; ++ else ++ haccess_add(sap, buf, program, FALSE); ++ xlog(D_GENERAL, "%s: access by %s DENIED", __func__, buf); + return (FALSE); + } + +- if (acc) +- acc->access = TRUE; +- else +- haccess_add(addr, prog, TRUE); ++ if (acc != NULL) ++ acc->allowed = TRUE; ++ else ++ haccess_add(sap, buf, program, TRUE); ++ xlog(D_GENERAL, "%s: access by %s ALLOWED", __func__, buf); + +- return (TRUE); ++ return (TRUE); + } + +-/* logit - report events of interest via the syslog daemon */ +- +-static void logit(int severity, struct sockaddr_in *addr, +- u_long procnum, u_long prognum, char *text) +-{ +- syslog(severity, "connect from %s denied: %s", +- inet_ntoa(addr->sin_addr), text); +-} +-#endif ++#endif /* HAVE_LIBWRAP */ +diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am +index e9462fc..60400b2 100644 +--- a/support/nfs/Makefile.am ++++ b/support/nfs/Makefile.am +@@ -4,7 +4,8 @@ noinst_LIBRARIES = libnfs.a + libnfs_a_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \ + xlog.c xcommon.c wildmat.c nfsclient.c \ + nfsexport.c getfh.c nfsctl.c rpc_socket.c getport.c \ +- svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c ++ svc_socket.c cacheio.c closeall.c nfs_mntent.c conffile.c \ ++ svc_create.c + + MAINTAINERCLEANFILES = Makefile.in + +diff --git a/support/nfs/exports.c b/support/nfs/exports.c +index 1aaebf4..a93941c 100644 +--- a/support/nfs/exports.c ++++ b/support/nfs/exports.c +@@ -84,6 +84,31 @@ setexportent(char *fname, char *type) + first = 1; + } + ++static void init_exportent (struct exportent *ee, int fromkernel) ++{ ++ ee->e_flags = EXPORT_DEFAULT_FLAGS; ++ /* some kernels assume the default is sync rather than ++ * async. More recent kernels always report one or other, ++ * but this test makes sure we assume same as kernel ++ * Ditto for wgather ++ */ ++ if (fromkernel) { ++ ee->e_flags &= ~NFSEXP_ASYNC; ++ ee->e_flags &= ~NFSEXP_GATHERED_WRITES; ++ } ++ ee->e_anonuid = 65534; ++ ee->e_anongid = 65534; ++ ee->e_squids = NULL; ++ ee->e_sqgids = NULL; ++ ee->e_mountpoint = NULL; ++ ee->e_fslocmethod = FSLOC_NONE; ++ ee->e_fslocdata = NULL; ++ ee->e_secinfo[0].flav = NULL; ++ ee->e_nsquids = 0; ++ ee->e_nsqgids = 0; ++ ee->e_uuid = NULL; ++} ++ + struct exportent * + getexportent(int fromkernel, int fromexports) + { +@@ -102,26 +127,7 @@ getexportent(int fromkernel, int fromexports) + has_default_opts = 0; + has_default_subtree_opts = 0; + +- def_ee.e_flags = EXPORT_DEFAULT_FLAGS; +- /* some kernels assume the default is sync rather than +- * async. More recent kernels always report one or other, +- * but this test makes sure we assume same as kernel +- * Ditto for wgather +- */ +- if (fromkernel) { +- def_ee.e_flags &= ~NFSEXP_ASYNC; +- def_ee.e_flags &= ~NFSEXP_GATHERED_WRITES; +- } +- def_ee.e_anonuid = 65534; +- def_ee.e_anongid = 65534; +- def_ee.e_squids = NULL; +- def_ee.e_sqgids = NULL; +- def_ee.e_mountpoint = NULL; +- def_ee.e_fslocmethod = FSLOC_NONE; +- def_ee.e_fslocdata = NULL; +- def_ee.e_secinfo[0].flav = NULL; +- def_ee.e_nsquids = 0; +- def_ee.e_nsqgids = 0; ++ init_exportent(&def_ee, fromkernel); + + ok = getpath(def_ee.e_path, sizeof(def_ee.e_path)); + if (ok <= 0) +@@ -334,18 +340,7 @@ mkexportent(char *hname, char *path, char *options) + { + static struct exportent ee; + +- ee.e_flags = EXPORT_DEFAULT_FLAGS; +- ee.e_anonuid = 65534; +- ee.e_anongid = 65534; +- ee.e_squids = NULL; +- ee.e_sqgids = NULL; +- ee.e_mountpoint = NULL; +- ee.e_fslocmethod = FSLOC_NONE; +- ee.e_fslocdata = NULL; +- ee.e_secinfo[0].flav = NULL; +- ee.e_nsquids = 0; +- ee.e_nsqgids = 0; +- ee.e_uuid = NULL; ++ init_exportent(&ee, 0); + + xfree(ee.e_hostname); + ee.e_hostname = xstrdup(hname); +@@ -385,7 +380,7 @@ static int valid_uuid(char *uuid) + * do nothing if it's already there. Returns the index of flavor + * in the resulting array in any case. + */ +-static int secinfo_addflavor(struct flav_info *flav, struct exportent *ep) ++int secinfo_addflavor(struct flav_info *flav, struct exportent *ep) + { + struct sec_entry *p; + +@@ -467,9 +462,20 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep) + } + } + +-/* options that can vary per flavor: */ +-#define NFSEXP_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \ +- | NFSEXP_ALLSQUASH) ++/* ++ * For those flags which are not allowed to vary by pseudoflavor, ++ * ensure that the export flags agree with the flags on each ++ * pseudoflavor: ++ */ ++static void fix_pseudoflavor_flags(struct exportent *ep) ++{ ++ struct export_features *ef; ++ struct sec_entry *p; ++ ++ ef = get_export_features(); ++ for (p = ep->e_secinfo; p->flav; p++) ++ p->flags |= ep->e_flags & ~ef->secinfo_flags; ++} + + /* + * Parse option string pointed to by cp and set mount options accordingly. +@@ -477,7 +483,6 @@ static void clearflags(int mask, unsigned int active, struct exportent *ep) + static int + parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) + { +- struct sec_entry *p; + int had_subtree_opt = 0; + char *flname = efname?efname:"command line"; + int flline = efp?efp->x_line:0; +@@ -507,25 +512,25 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) + else if (strcmp(opt, "rw") == 0) + clearflags(NFSEXP_READONLY, active, ep); + else if (!strcmp(opt, "secure")) +- ep->e_flags &= ~NFSEXP_INSECURE_PORT; ++ clearflags(NFSEXP_INSECURE_PORT, active, ep); + else if (!strcmp(opt, "insecure")) +- ep->e_flags |= NFSEXP_INSECURE_PORT; ++ setflags(NFSEXP_INSECURE_PORT, active, ep); + else if (!strcmp(opt, "sync")) +- ep->e_flags &= ~NFSEXP_ASYNC; ++ clearflags(NFSEXP_ASYNC, active, ep); + else if (!strcmp(opt, "async")) +- ep->e_flags |= NFSEXP_ASYNC; ++ setflags(NFSEXP_ASYNC, active, ep); + else if (!strcmp(opt, "nohide")) +- ep->e_flags |= NFSEXP_NOHIDE; ++ setflags(NFSEXP_NOHIDE, active, ep); + else if (!strcmp(opt, "hide")) +- ep->e_flags &= ~NFSEXP_NOHIDE; ++ clearflags(NFSEXP_NOHIDE, active, ep); + else if (!strcmp(opt, "crossmnt")) +- ep->e_flags |= NFSEXP_CROSSMOUNT; ++ setflags(NFSEXP_CROSSMOUNT, active, ep); + else if (!strcmp(opt, "nocrossmnt")) +- ep->e_flags &= ~NFSEXP_CROSSMOUNT; ++ clearflags(NFSEXP_CROSSMOUNT, active, ep); + else if (!strcmp(opt, "wdelay")) +- ep->e_flags |= NFSEXP_GATHERED_WRITES; ++ setflags(NFSEXP_GATHERED_WRITES, active, ep); + else if (!strcmp(opt, "no_wdelay")) +- ep->e_flags &= ~NFSEXP_GATHERED_WRITES; ++ clearflags(NFSEXP_GATHERED_WRITES, active, ep); + else if (strcmp(opt, "root_squash") == 0) + setflags(NFSEXP_ROOTSQUASH, active, ep); + else if (!strcmp(opt, "no_root_squash")) +@@ -536,22 +541,22 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) + clearflags(NFSEXP_ALLSQUASH, active, ep); + else if (strcmp(opt, "subtree_check") == 0) { + had_subtree_opt = 1; +- ep->e_flags &= ~NFSEXP_NOSUBTREECHECK; ++ clearflags(NFSEXP_NOSUBTREECHECK, active, ep); + } else if (strcmp(opt, "no_subtree_check") == 0) { + had_subtree_opt = 1; +- ep->e_flags |= NFSEXP_NOSUBTREECHECK; ++ setflags(NFSEXP_NOSUBTREECHECK, active, ep); + } else if (strcmp(opt, "auth_nlm") == 0) +- ep->e_flags &= ~NFSEXP_NOAUTHNLM; ++ clearflags(NFSEXP_NOAUTHNLM, active, ep); + else if (strcmp(opt, "no_auth_nlm") == 0) +- ep->e_flags |= NFSEXP_NOAUTHNLM; ++ setflags(NFSEXP_NOAUTHNLM, active, ep); + else if (strcmp(opt, "secure_locks") == 0) +- ep->e_flags &= ~NFSEXP_NOAUTHNLM; ++ clearflags(NFSEXP_NOAUTHNLM, active, ep); + else if (strcmp(opt, "insecure_locks") == 0) +- ep->e_flags |= NFSEXP_NOAUTHNLM; ++ setflags(NFSEXP_NOAUTHNLM, active, ep); + else if (strcmp(opt, "acl") == 0) +- ep->e_flags &= ~NFSEXP_NOACL; ++ clearflags(NFSEXP_NOACL, active, ep); + else if (strcmp(opt, "no_acl") == 0) +- ep->e_flags |= NFSEXP_NOACL; ++ setflags(NFSEXP_NOACL, active, ep); + else if (strncmp(opt, "anonuid=", 8) == 0) { + char *oe; + ep->e_anonuid = strtol(opt+8, &oe, 10); +@@ -583,11 +588,11 @@ bad_option: + char *oe; + if (strcmp(opt+5, "root") == 0) { + ep->e_fsid = 0; +- ep->e_flags |= NFSEXP_FSID; ++ setflags(NFSEXP_FSID, active, ep); + } else { + ep->e_fsid = strtoul(opt+5, &oe, 0); + if (opt[5]!='\0' && *oe == '\0') +- ep->e_flags |= NFSEXP_FSID; ++ setflags(NFSEXP_FSID, active, ep); + else if (valid_uuid(opt+5)) + ep->e_uuid = strdup(opt+5); + else { +@@ -628,22 +633,15 @@ bad_option: + } else { + xlog(L_ERROR, "%s:%d: unknown keyword \"%s\"\n", + flname, flline, opt); +- ep->e_flags |= NFSEXP_ALLSQUASH | NFSEXP_READONLY; ++ setflags(NFSEXP_ALLSQUASH | NFSEXP_READONLY, active, ep); + goto bad_option; + } + free(opt); + while (isblank(*cp)) + cp++; + } +- /* +- * Turn on nohide which will allow this export to cross over +- * the 'mount --bind' mount point. +- */ +- if (ep->e_fslocdata) +- ep->e_flags |= NFSEXP_NOHIDE; + +- for (p = ep->e_secinfo; p->flav; p++) +- p->flags |= ep->e_flags & ~NFSEXP_SECINFO_FLAGS; ++ fix_pseudoflavor_flags(ep); + ep->e_squids = squids; + ep->e_sqgids = sqgids; + ep->e_nsquids = nsquids; +@@ -760,4 +758,34 @@ syntaxerr(char *msg) + xlog(L_ERROR, "%s:%d: syntax error: %s", + efname, efp?efp->x_line:0, msg); + } +- ++struct export_features *get_export_features(void) ++{ ++ static char *path = "/proc/fs/nfsd/export_features"; ++ static struct export_features ef; ++ static int cached = 0; ++ char buf[50]; ++ int c; ++ int fd; ++ ++ if (cached) ++ return &ef; ++ ++ ef.flags = NFSEXP_OLDFLAGS; ++ ef.secinfo_flags = NFSEXP_OLD_SECINFO_FLAGS; ++ ++ fd = open(path, O_RDONLY); ++ if (fd == -1) ++ goto good; ++ fd = read(fd, buf, 50); ++ if (fd == -1) ++ goto err; ++ c = sscanf(buf, "%x %x", &ef.flags, &ef.secinfo_flags); ++ if (c != 2) ++ goto err; ++good: ++ cached = 1; ++ return &ef; ++err: ++ xlog(L_WARNING, "unexpected error reading %s", path); ++ return &ef; ++} +diff --git a/support/nfs/getport.c b/support/nfs/getport.c +index 4bdf556..c930539 100644 +--- a/support/nfs/getport.c ++++ b/support/nfs/getport.c +@@ -45,6 +45,7 @@ + #include + #endif + ++#include "sockaddr.h" + #include "nfsrpc.h" + + /* +@@ -199,7 +200,63 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap, + return clnt; + } + +-/* ++/** ++ * nfs_get_proto - Convert a netid to an address family and protocol number ++ * @netid: C string containing a netid ++ * @family: OUT: address family ++ * @protocol: OUT: protocol number ++ * ++ * Returns 1 and fills in @protocol if the netid was recognized; ++ * otherwise zero is returned. ++ */ ++#ifdef HAVE_LIBTIRPC ++int ++nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol) ++{ ++ struct netconfig *nconf; ++ struct protoent *proto; ++ ++ nconf = getnetconfigent(netid); ++ if (nconf == NULL) ++ return 0; ++ ++ proto = getprotobyname(nconf->nc_proto); ++ if (proto == NULL) { ++ freenetconfigent(nconf); ++ return 0; ++ } ++ ++ *family = AF_UNSPEC; ++ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ++ *family = AF_INET; ++ if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ++ *family = AF_INET6; ++ freenetconfigent(nconf); ++ ++ *protocol = (unsigned long)proto->p_proto; ++ return 1; ++} ++#else /* !HAVE_LIBTIRPC */ ++int ++nfs_get_proto(const char *netid, sa_family_t *family, unsigned long *protocol) ++{ ++ struct protoent *proto; ++ ++ proto = getprotobyname(netid); ++ if (proto == NULL) ++ return 0; ++ ++ *family = AF_INET; ++ *protocol = (unsigned long)proto->p_proto; ++ return 1; ++} ++#endif /* !HAVE_LIBTIRPC */ ++ ++/** ++ * nfs_get_netid - Convert a protocol family and protocol name to a netid ++ * @family: protocol family ++ * @protocol: protocol number ++ * + * One of the arguments passed when querying remote rpcbind services + * via rpcbind v3 or v4 is a netid string. This replaces the pm_prot + * field used in legacy PMAP_GETPORT calls. +@@ -213,13 +270,12 @@ static CLIENT *nfs_gp_get_rpcbclient(struct sockaddr *sap, + * first entry that matches @family and @protocol and whose netid string + * fits in the provided buffer. + * +- * Returns a '\0'-terminated string if successful; otherwise NULL. ++ * Returns a '\0'-terminated string if successful. Caller must ++ * free the returned string. Otherwise NULL is returned, and + * rpc_createerr.cf_stat is set to reflect the error. + */ + #ifdef HAVE_LIBTIRPC +- +-static char *nfs_gp_get_netid(const sa_family_t family, +- const unsigned short protocol) ++char *nfs_get_netid(const sa_family_t family, const unsigned long protocol) + { + char *nc_protofmly, *nc_proto, *nc_netid; + struct netconfig *nconf; +@@ -255,6 +311,9 @@ static char *nfs_gp_get_netid(const sa_family_t family, + + nc_netid = strdup(nconf->nc_netid); + endnetconfig(handle); ++ ++ if (nc_netid == NULL) ++ rpc_createerr.cf_stat = RPC_SYSTEMERROR; + return nc_netid; + } + endnetconfig(handle); +@@ -263,8 +322,28 @@ out: + rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; + return NULL; + } ++#else /* !HAVE_LIBTIRPC */ ++char *nfs_get_netid(const sa_family_t family, const unsigned long protocol) ++{ ++ struct protoent *proto; ++ char *netid; + +-#endif /* HAVE_LIBTIRPC */ ++ if (family != AF_INET) ++ goto out; ++ proto = getprotobynumber((int)protocol); ++ if (proto == NULL) ++ goto out; ++ ++ netid = strdup(proto->p_name); ++ if (netid == NULL) ++ rpc_createerr.cf_stat = RPC_SYSTEMERROR; ++ return netid; ++ ++out: ++ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; ++ return NULL; ++} ++#endif /* !HAVE_LIBTIRPC */ + + /* + * Extract a port number from a universal address, and terminate the +@@ -421,7 +500,7 @@ static int nfs_gp_init_rpcb_parms(const struct sockaddr *sap, + { + char *netid, *addr; + +- netid = nfs_gp_get_netid(sap->sa_family, protocol); ++ netid = nfs_get_netid(sap->sa_family, protocol); + if (netid == NULL) + return 0; + +@@ -627,8 +706,8 @@ int nfs_rpc_ping(const struct sockaddr *sap, const socklen_t salen, + const rpcprog_t program, const rpcvers_t version, + const unsigned short protocol, const struct timeval *timeout) + { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + CLIENT *client; + struct timeval tout = { -1, 0 }; + int result = 0; +@@ -696,8 +775,8 @@ unsigned short nfs_getport(const struct sockaddr *sap, + const rpcvers_t version, + const unsigned short protocol) + { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + struct timeval timeout = { -1, 0 }; + unsigned short port = 0; + CLIENT *client; +@@ -755,8 +834,8 @@ int nfs_getport_ping(struct sockaddr *sap, const socklen_t salen, + } + + if (port != 0) { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + + memcpy(saddr, sap, (size_t)salen); + nfs_set_port(saddr, port); +@@ -807,8 +886,8 @@ unsigned short nfs_getlocalport(const rpcprot_t program, + const rpcvers_t version, + const unsigned short protocol) + { +- struct sockaddr_storage address; +- struct sockaddr *lb_addr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *lb_addr = &address.sa; + socklen_t lb_len = sizeof(*lb_addr); + unsigned short port = 0; + +@@ -891,8 +970,8 @@ unsigned short nfs_rpcb_getaddr(const struct sockaddr *sap, + const unsigned short protocol, + const struct timeval *timeout) + { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + CLIENT *client; + struct rpcb parms; + struct timeval tout = { -1, 0 }; +diff --git a/support/nfs/rpc_socket.c b/support/nfs/rpc_socket.c +index 9c20f61..0e20824 100644 +--- a/support/nfs/rpc_socket.c ++++ b/support/nfs/rpc_socket.c +@@ -26,6 +26,8 @@ + + #include + #include ++ ++#include + #include + #include + #include +@@ -38,6 +40,7 @@ + #include + #include + ++#include "sockaddr.h" + #include "nfsrpc.h" + + #ifdef HAVE_LIBTIRPC +@@ -51,6 +54,7 @@ + #define NFSRPC_TIMEOUT_UDP (3) + #define NFSRPC_TIMEOUT_TCP (10) + ++ + /* + * Set up an RPC client for communicating via a AF_LOCAL socket. + * +@@ -121,10 +125,10 @@ static int nfs_bind(const int sock, const sa_family_t family) + + switch (family) { + case AF_INET: +- return bind(sock, (struct sockaddr *)&sin, ++ return bind(sock, (struct sockaddr *)(char *)&sin, + (socklen_t)sizeof(sin)); + case AF_INET6: +- return bind(sock, (struct sockaddr *)&sin6, ++ return bind(sock, (struct sockaddr *)(char *)&sin6, + (socklen_t)sizeof(sin6)); + } + +@@ -153,9 +157,9 @@ static int nfs_bindresvport(const int sock, const sa_family_t family) + + switch (family) { + case AF_INET: +- return bindresvport_sa(sock, (struct sockaddr *)&sin); ++ return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin); + case AF_INET6: +- return bindresvport_sa(sock, (struct sockaddr *)&sin6); ++ return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin6); + } + + errno = EAFNOSUPPORT; +@@ -416,49 +420,6 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap, + } + + /** +- * nfs_get_port - extract port value from a socket address +- * @sap: pointer to socket address +- * +- * Returns port value in host byte order. +- */ +-uint16_t +-nfs_get_port(const struct sockaddr *sap) +-{ +- const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; +- const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sap; +- +- switch (sap->sa_family) { +- case AF_INET: +- return ntohs(sin->sin_port); +- case AF_INET6: +- return ntohs(sin6->sin6_port); +- } +- return 0; +-} +- +-/** +- * nfs_set_port - set port value in a socket address +- * @sap: pointer to socket address +- * @port: port value to set +- * +- */ +-void +-nfs_set_port(struct sockaddr *sap, const uint16_t port) +-{ +- struct sockaddr_in *sin = (struct sockaddr_in *)sap; +- struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; +- +- switch (sap->sa_family) { +- case AF_INET: +- sin->sin_port = htons(port); +- break; +- case AF_INET6: +- sin6->sin6_port = htons(port); +- break; +- } +-} +- +-/** + * nfs_get_rpcclient - acquire an RPC client + * @sap: pointer to socket address of RPC server + * @salen: length of socket address +diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c +new file mode 100644 +index 0000000..59ba505 +--- /dev/null ++++ b/support/nfs/svc_create.c +@@ -0,0 +1,246 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++ ++#include ++#include ++ ++#ifdef HAVE_TCP_WRAPPER ++#include "tcpwrapper.h" ++#endif ++ ++#include "rpcmisc.h" ++#include "xlog.h" ++ ++#ifdef HAVE_LIBTIRPC ++ ++/* ++ * Set up an appropriate bind address, given @port and @nconf. ++ * ++ * Returns getaddrinfo(3) results if successful. Caller must ++ * invoke freeaddrinfo(3) on these results. ++ * ++ * Otherwise NULL is returned if an error occurs. ++ */ ++__attribute_malloc__ ++static struct addrinfo * ++svc_create_bindaddr(struct netconfig *nconf, const uint16_t port) ++{ ++ struct addrinfo *ai = NULL; ++ struct addrinfo hint = { ++ .ai_flags = AI_PASSIVE | AI_NUMERICSERV, ++ }; ++ char buf[8]; ++ int error; ++ ++ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) ++ hint.ai_family = AF_INET; ++#ifdef IPV6_SUPPORTED ++ else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) ++ hint.ai_family = AF_INET6; ++#endif /* IPV6_SUPPORTED */ ++ else { ++ xlog(D_GENERAL, "Unrecognized bind address family: %s", ++ nconf->nc_protofmly); ++ return NULL; ++ } ++ ++ if (strcmp(nconf->nc_proto, NC_UDP) == 0) ++ hint.ai_protocol = (int)IPPROTO_UDP; ++ else if (strcmp(nconf->nc_proto, NC_TCP) == 0) ++ hint.ai_protocol = (int)IPPROTO_TCP; ++ else { ++ xlog(D_GENERAL, "Unrecognized bind address protocol: %s", ++ nconf->nc_proto); ++ return NULL; ++ } ++ ++ (void)snprintf(buf, sizeof(buf), "%u", port); ++ error = getaddrinfo(NULL, buf, &hint, &ai); ++ if (error != 0) { ++ xlog(L_ERROR, "Failed to construct bind address: %s", ++ gai_strerror(error)); ++ return NULL; ++ } ++ ++ return ai; ++} ++ ++static unsigned int ++svc_create_nconf(const char *name, const rpcprog_t program, ++ const rpcvers_t version, ++ void (*dispatch)(struct svc_req *, SVCXPRT *), ++ const uint16_t port, struct netconfig *nconf) ++{ ++ struct t_bind bindaddr; ++ struct addrinfo *ai; ++ SVCXPRT *xprt; ++ ++ ai = svc_create_bindaddr(nconf, port); ++ if (ai == NULL) ++ return 0; ++ ++ bindaddr.addr.buf = ai->ai_addr; ++ bindaddr.qlen = SOMAXCONN; ++ ++ xprt = svc_tli_create(RPC_ANYFD, nconf, &bindaddr, 0, 0); ++ freeaddrinfo(ai); ++ if (xprt == NULL) { ++ xlog(D_GENERAL, "Failed to create listener xprt " ++ "(%s, %u, %s)", name, version, nconf->nc_netid); ++ return 0; ++ } ++ ++ if (!svc_reg(xprt, program, version, dispatch, nconf)) { ++ /* svc_reg(3) destroys @xprt in this case */ ++ xlog(D_GENERAL, "Failed to register (%s, %u, %s)", ++ name, version, nconf->nc_netid); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++/** ++ * nfs_svc_create - start up RPC svc listeners ++ * @name: C string containing name of new service ++ * @program: RPC program number to register ++ * @version: RPC version number to register ++ * @dispatch: address of function that handles incoming RPC requests ++ * @port: if not zero, transport listens on this port ++ * ++ * Sets up network transports for receiving RPC requests, and starts ++ * the RPC dispatcher. Returns the number of started network transports. ++ */ ++unsigned int ++nfs_svc_create(__attribute__((unused)) char *name, ++ const rpcprog_t program, const rpcvers_t version, ++ void (*dispatch)(struct svc_req *, SVCXPRT *), ++ const uint16_t port) ++{ ++ const struct sigaction create_sigaction = { ++ .sa_handler = SIG_IGN, ++ }; ++ unsigned int visible, up; ++ struct netconfig *nconf; ++ void *handlep; ++ ++ /* ++ * Ignore SIGPIPE to avoid exiting sideways when peers ++ * close their TCP connection while we're trying to reply ++ * to them. ++ */ ++ (void)sigaction(SIGPIPE, &create_sigaction, NULL); ++ ++ handlep = setnetconfig(); ++ if (handlep == NULL) { ++ xlog(L_ERROR, "Failed to access local netconfig database: %s", ++ nc_sperror()); ++ return 0; ++ } ++ ++ visible = 0; ++ up = 0; ++ while ((nconf = getnetconfig(handlep)) != NULL) { ++ if (!(nconf->nc_flag & NC_VISIBLE)) ++ continue; ++ visible++; ++ up += svc_create_nconf(name, program, version, dispatch, ++ port, nconf); ++ } ++ ++ if (visible == 0) ++ xlog(L_ERROR, "Failed to find any visible netconfig entries"); ++ ++ if (endnetconfig(handlep) == -1) ++ xlog(L_ERROR, "Failed to close local netconfig database: %s", ++ nc_sperror()); ++ ++ return up; ++} ++ ++/** ++ * nfs_svc_unregister - remove service registrations from local rpcbind database ++ * @program: RPC program number to unregister ++ * @version: RPC version number to unregister ++ * ++ * Removes all registrations for [ @program, @version ] . ++ */ ++void ++nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) ++{ ++ if (rpcb_unset(program, version, NULL) == FALSE) ++ xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", ++ (unsigned long)program, (unsigned long)version); ++} ++ ++#else /* !HAVE_LIBTIRPC */ ++ ++/** ++ * nfs_svc_create - start up RPC svc listeners ++ * @name: C string containing name of new service ++ * @program: RPC program number to register ++ * @version: RPC version number to register ++ * @dispatch: address of function that handles incoming RPC requests ++ * @port: if not zero, transport listens on this port ++ * ++ * Sets up network transports for receiving RPC requests, and starts ++ * the RPC dispatcher. Returns the number of started network transports. ++ */ ++unsigned int ++nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version, ++ void (*dispatch)(struct svc_req *, SVCXPRT *), ++ const uint16_t port) ++{ ++ rpc_init(name, (int)program, (int)version, dispatch, (int)port); ++ return 1; ++} ++ ++/** ++ * nfs_svc_unregister - remove service registrations from local rpcbind database ++ * @program: RPC program number to unregister ++ * @version: RPC version number to unregister ++ * ++ * Removes all registrations for [ @program, @version ] . ++ */ ++void ++nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) ++{ ++ if (pmap_unset((unsigned long)program, (unsigned long)version) == FALSE) ++ xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", ++ (unsigned long)program, (unsigned long)version); ++} ++ ++#endif /* !HAVE_LIBTIRPC */ +diff --git a/support/nsm/Makefile.am b/support/nsm/Makefile.am +new file mode 100644 +index 0000000..2038e68 +--- /dev/null ++++ b/support/nsm/Makefile.am +@@ -0,0 +1,45 @@ ++## Process this file with automake to produce Makefile.in ++ ++GENFILES_CLNT = sm_inter_clnt.c ++GENFILES_SVC = sm_inter_svc.c ++GENFILES_XDR = sm_inter_xdr.c ++GENFILES_H = sm_inter.h ++ ++GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) ++ ++EXTRA_DIST = sm_inter.x ++ ++noinst_LIBRARIES = libnsm.a ++libnsm_a_SOURCES = $(GENFILES) file.c rpc.c ++ ++BUILT_SOURCES = $(GENFILES) ++ ++if CONFIG_RPCGEN ++RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen ++$(RPCGEN): ++ make -C ../../tools/rpcgen all ++else ++RPCGEN = @RPCGEN_PATH@ ++endif ++ ++$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -l -o $@ $< ++ ++$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -m -o $@ $< ++ ++$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -c -o $@ $< ++ ++$(GENFILES_H): %.h: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -h -o $@ $< ++ rm -f $(top_builddir)/support/include/sm_inter.h ++ $(LN_S) ../nsm/sm_inter.h $(top_builddir)/support/include/sm_inter.h ++ ++MAINTAINERCLEANFILES = Makefile.in ++ ++CLEANFILES = $(GENFILES) $(top_builddir)/support/include/sm_inter.h +diff --git a/support/nsm/file.c b/support/nsm/file.c +new file mode 100644 +index 0000000..d469219 +--- /dev/null ++++ b/support/nsm/file.c +@@ -0,0 +1,1067 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++/* ++ * NSM for Linux. ++ * ++ * Callback information and NSM state is stored in files, usually ++ * under /var/lib/nfs. A database of information contained in local ++ * files stores NLM callback data and what remote peers to notify of ++ * reboots. ++ * ++ * For each monitored remote peer, a text file is created under the ++ * directory specified by NSM_MONITOR_DIR. The name of the file ++ * is a valid DNS hostname. The hostname string must be a valid ++ * ASCII DNS name, and must not contain slash characters, white space, ++ * or '\0' (ie. anything that might have some special meaning in a ++ * path name). ++ * ++ * The contents of each file include seven blank-separated fields of ++ * text, finished with '\n'. The first field contains the network ++ * address of the NLM service to call back. The current implementation ++ * supports using only IPv4 addresses, so the only contents of this ++ * field are a network order IPv4 address expressed in 8 hexadecimal ++ * characters. ++ * ++ * The next four fields are text strings of hexadecimal characters, ++ * representing: ++ * ++ * 2. A 4 byte RPC program number of the NLM service to call back ++ * 3. A 4 byte RPC version number of the NLM service to call back ++ * 4. A 4 byte RPC procedure number of the NLM service to call back ++ * 5. A 16 byte opaque cookie that the NLM service uses to identify ++ * the monitored host ++ * ++ * The sixth field is the monitored host's mon_name, passed to statd ++ * via an SM_MON request. ++ * ++ * The seventh field is the my_name for this peer, which is the ++ * hostname of the local NLM (currently on Linux, the result of ++ * `uname -n`). This can be used as the source address/hostname ++ * when sending SM_NOTIFY requests. ++ * ++ * The NSM protocol does not limit the contents of these strings ++ * in any way except that they must fit into 1024 bytes. Our ++ * implementation requires that these strings not contain ++ * white space or '\0'. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#ifndef S_SPLINT_S ++#include ++#endif ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "xlog.h" ++#include "nsm.h" ++ ++#define RPCARGSLEN (4 * (8 + 1)) ++#define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1) ++ ++#define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state" ++ ++/* ++ * Some distributions place statd's files in a subdirectory ++ */ ++#define NSM_PATH_EXTENSION ++/* #define NSM_PATH_EXTENSION "/statd" */ ++ ++#define NSM_DEFAULT_STATEDIR NFS_STATEDIR NSM_PATH_EXTENSION ++ ++static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR; ++ ++#define NSM_MONITOR_DIR "sm" ++#define NSM_NOTIFY_DIR "sm.bak" ++#define NSM_STATE_FILE "state" ++ ++ ++static _Bool ++error_check(const int len, const size_t buflen) ++{ ++ return (len < 0) || ((size_t)len >= buflen); ++} ++ ++static _Bool ++exact_error_check(const ssize_t len, const size_t buflen) ++{ ++ return (len < 0) || ((size_t)len != buflen); ++} ++ ++/* ++ * Returns a dynamically allocated, '\0'-terminated buffer ++ * containing an appropriate pathname, or NULL if an error ++ * occurs. Caller must free the returned result with free(3). ++ */ ++__attribute_malloc__ ++static char * ++nsm_make_record_pathname(const char *directory, const char *hostname) ++{ ++ const char *c; ++ size_t size; ++ char *path; ++ int len; ++ ++ /* ++ * Block hostnames that contain characters that have ++ * meaning to the file system (like '/'), or that can ++ * be confusing on visual inspection (like ' '). ++ */ ++ for (c = hostname; *c != '\0'; c++) ++ if (*c == '/' || isspace((int)*c) != 0) { ++ xlog(D_GENERAL, "Hostname contains invalid characters"); ++ return NULL; ++ } ++ ++ size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3; ++ if (size > PATH_MAX) { ++ xlog(D_GENERAL, "Hostname results in pathname that is too long"); ++ return NULL; ++ } ++ ++ path = malloc(size); ++ if (path == NULL) { ++ xlog(D_GENERAL, "Failed to allocate memory for pathname"); ++ return NULL; ++ } ++ ++ len = snprintf(path, size, "%s/%s/%s", ++ nsm_base_dirname, directory, hostname); ++ if (error_check(len, size)) { ++ xlog(D_GENERAL, "Pathname did not fit in specified buffer"); ++ free(path); ++ return NULL; ++ } ++ ++ return path; ++} ++ ++/* ++ * Returns a dynamically allocated, '\0'-terminated buffer ++ * containing an appropriate pathname, or NULL if an error ++ * occurs. Caller must free the returned result with free(3). ++ */ ++__attribute_malloc__ ++static char * ++nsm_make_pathname(const char *directory) ++{ ++ size_t size; ++ char *path; ++ int len; ++ ++ size = strlen(nsm_base_dirname) + strlen(directory) + 2; ++ if (size > PATH_MAX) ++ return NULL; ++ ++ path = malloc(size); ++ if (path == NULL) ++ return NULL; ++ ++ len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory); ++ if (error_check(len, size)) { ++ free(path); ++ return NULL; ++ } ++ ++ return path; ++} ++ ++/* ++ * Returns a dynamically allocated, '\0'-terminated buffer ++ * containing an appropriate pathname, or NULL if an error ++ * occurs. Caller must free the returned result with free(3). ++ */ ++__attribute_malloc__ ++static char * ++nsm_make_temp_pathname(const char *pathname) ++{ ++ size_t size; ++ char *path; ++ int len; ++ ++ size = strlen(pathname) + sizeof(".new") + 2; ++ if (size > PATH_MAX) ++ return NULL; ++ ++ path = malloc(size); ++ if (path == NULL) ++ return NULL; ++ ++ len = snprintf(path, size, "%s.new", pathname); ++ if (error_check(len, size)) { ++ free(path); ++ return NULL; ++ } ++ ++ return path; ++} ++ ++/* ++ * Use "mktemp, write, rename" to update the contents of a file atomically. ++ * ++ * Returns true if completely successful, or false if some error occurred. ++ */ ++static _Bool ++nsm_atomic_write(const char *path, const void *buf, const size_t buflen) ++{ ++ _Bool result = false; ++ ssize_t len; ++ char *temp; ++ int fd; ++ ++ temp = nsm_make_temp_pathname(path); ++ if (temp == NULL) { ++ xlog(L_ERROR, "Failed to create new path for %s", path); ++ goto out; ++ } ++ ++ fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644); ++ if (fd == -1) { ++ xlog(L_ERROR, "Failed to create %s: %m", temp); ++ goto out; ++ } ++ ++ len = write(fd, buf, buflen); ++ if (exact_error_check(len, buflen)) { ++ xlog(L_ERROR, "Failed to write %s: %m", temp); ++ (void)close(fd); ++ (void)unlink(temp); ++ goto out; ++ } ++ ++ if (close(fd) == -1) { ++ xlog(L_ERROR, "Failed to close %s: %m", temp); ++ (void)unlink(temp); ++ goto out; ++ } ++ ++ if (rename(temp, path) == -1) { ++ xlog(L_ERROR, "Failed to rename %s -> %s: %m", ++ temp, path); ++ (void)unlink(temp); ++ goto out; ++ } ++ ++ /* Ostensibly, a sync(2) is not needed here because ++ * open(O_CREAT), write(O_SYNC), and rename(2) are ++ * already synchronous with persistent storage, for ++ * any file system we care about. */ ++ ++ result = true; ++ ++out: ++ free(temp); ++ return result; ++} ++ ++/** ++ * nsm_setup_pathnames - set up pathname ++ * @progname: C string containing name of program, for error messages ++ * @parentdir: C string containing pathname to on-disk state, or NULL ++ * ++ * This runs before logging is set up, so error messages are directed ++ * to stderr. ++ * ++ * Returns true and sets up our pathnames, if @parentdir was valid ++ * and usable; otherwise false is returned. ++ */ ++_Bool ++nsm_setup_pathnames(const char *progname, const char *parentdir) ++{ ++ static char buf[PATH_MAX]; ++ struct stat st; ++ char *path; ++ ++ /* First: test length of name and whether it exists */ ++ if (lstat(parentdir, &st) == -1) { ++ (void)fprintf(stderr, "%s: Failed to stat %s: %s", ++ progname, parentdir, strerror(errno)); ++ return false; ++ } ++ ++ /* Ensure we have a clean directory pathname */ ++ strncpy(buf, parentdir, sizeof(buf)); ++ path = dirname(buf); ++ if (*path == '.') { ++ (void)fprintf(stderr, "%s: Unusable directory %s", ++ progname, parentdir); ++ return false; ++ } ++ ++ xlog(D_CALL, "Using %s as the state directory", parentdir); ++ strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname)); ++ return true; ++} ++ ++/** ++ * nsm_is_default_parentdir - check if parent directory is default ++ * ++ * Returns true if the active statd parent directory, set by ++ * nsm_change_pathname(), is the same as the built-in default ++ * parent directory; otherwise false is returned. ++ */ ++_Bool ++nsm_is_default_parentdir(void) ++{ ++ return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0; ++} ++ ++/* ++ * Clear all capabilities but CAP_NET_BIND_SERVICE. This permits ++ * callers to acquire privileged source ports, but all other root ++ * capabilities are disallowed. ++ * ++ * Returns true if successful, or false if some error occurred. ++ */ ++static _Bool ++nsm_clear_capabilities(void) ++{ ++ cap_t caps; ++ ++ caps = cap_from_text("cap_net_bind_service=ep"); ++ if (caps == NULL) { ++ xlog(L_ERROR, "Failed to allocate capability: %m"); ++ return false; ++ } ++ ++ if (cap_set_proc(caps) == -1) { ++ xlog(L_ERROR, "Failed to set capability flags: %m"); ++ (void)cap_free(caps); ++ return false; ++ } ++ ++ (void)cap_free(caps); ++ return true; ++} ++ ++/** ++ * nsm_drop_privileges - drop root privileges ++ * @pidfd: file descriptor of a pid file ++ * ++ * Returns true if successful, or false if some error occurred. ++ * ++ * Set our effective UID and GID to that of our on-disk database. ++ */ ++_Bool ++nsm_drop_privileges(const int pidfd) ++{ ++ struct stat st; ++ ++ (void)umask(S_IRWXO); ++ ++ /* ++ * XXX: If we can't stat dirname, or if dirname is owned by ++ * root, we should use "statduser" instead, which is set up ++ * by configure.ac. Nothing in nfs-utils seems to use ++ * "statduser," though. ++ */ ++ if (lstat(nsm_base_dirname, &st) == -1) { ++ xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname); ++ return false; ++ } ++ ++ if (st.st_uid == 0) { ++ xlog_warn("Running as root. " ++ "chown %s to choose different user", nsm_base_dirname); ++ return true; ++ } ++ ++ if (chdir(nsm_base_dirname) == -1) { ++ xlog(L_ERROR, "Failed to change working directory to %s: %m", ++ nsm_base_dirname); ++ return false; ++ } ++ ++ /* ++ * If the pidfile happens to reside on NFS, dropping privileges ++ * will probably cause us to lose access, even though we are ++ * holding it open. Chown it to prevent this. ++ */ ++ if (pidfd >= 0) ++ if (fchown(pidfd, st.st_uid, st.st_gid) == -1) ++ xlog_warn("Failed to change owner of pidfile: %m"); ++ ++ /* ++ * Don't clear capabilities when dropping root. ++ */ ++ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { ++ xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m"); ++ return 0; ++ } ++ ++ if (setgroups(0, NULL) == -1) { ++ xlog(L_ERROR, "Failed to drop supplementary groups: %m"); ++ return false; ++ } ++ ++ /* ++ * ORDER ++ * ++ * setgid(2) first, as setuid(2) may remove privileges needed ++ * to set the group id. ++ */ ++ if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) { ++ xlog(L_ERROR, "Failed to drop privileges: %m"); ++ return false; ++ } ++ ++ xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); ++ ++ return nsm_clear_capabilities(); ++} ++ ++/** ++ * nsm_get_state - retrieve on-disk NSM state number ++ * ++ * Returns an odd NSM state number read from disk, or an initial ++ * state number. Zero is returned if some error occurs. ++ */ ++int ++nsm_get_state(_Bool update) ++{ ++ int fd, state = 0; ++ ssize_t result; ++ char *path = NULL; ++ ++ path = nsm_make_pathname(NSM_STATE_FILE); ++ if (path == NULL) { ++ xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE); ++ goto out; ++ } ++ ++ fd = open(path, O_RDONLY); ++ if (fd == -1) { ++ if (errno != ENOENT) { ++ xlog(L_ERROR, "Failed to open %s: %m", path); ++ goto out; ++ } ++ ++ xlog(L_NOTICE, "Initializing NSM state"); ++ state = 1; ++ update = true; ++ goto update; ++ } ++ ++ result = read(fd, &state, sizeof(state)); ++ if (exact_error_check(result, sizeof(state))) { ++ xlog_warn("Failed to read %s: %m", path); ++ ++ xlog(L_NOTICE, "Initializing NSM state"); ++ state = 1; ++ update = true; ++ goto update; ++ } ++ ++ if ((state & 1) == 0) ++ state++; ++ ++update: ++ (void)close(fd); ++ ++ if (update) { ++ state += 2; ++ if (!nsm_atomic_write(path, &state, sizeof(state))) ++ state = 0; ++ } ++ ++out: ++ free(path); ++ return state; ++} ++ ++/** ++ * nsm_update_kernel_state - attempt to post new NSM state to kernel ++ * @state: NSM state number ++ * ++ */ ++void ++nsm_update_kernel_state(const int state) ++{ ++ ssize_t result; ++ char buf[20]; ++ int fd, len; ++ ++ fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY); ++ if (fd == -1) { ++ xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m"); ++ return; ++ } ++ ++ len = snprintf(buf, sizeof(buf), "%d", state); ++ if (error_check(len, sizeof(buf))) { ++ xlog_warn("Failed to form NSM state number string"); ++ return; ++ } ++ ++ result = write(fd, buf, strlen(buf)); ++ if (exact_error_check(result, strlen(buf))) ++ xlog_warn("Failed to write NSM state number: %m"); ++ ++ if (close(fd) == -1) ++ xlog(L_ERROR, "Failed to close NSM state file " ++ NSM_KERNEL_STATE_FILE ": %m"); ++} ++ ++/** ++ * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" ++ * ++ * Returns the count of host records that were moved. ++ * ++ * Note that if any error occurs during this process, some monitor ++ * records may be left in the "sm" directory. ++ */ ++unsigned int ++nsm_retire_monitored_hosts(void) ++{ ++ unsigned int count = 0; ++ struct dirent *de; ++ char *path; ++ DIR *dir; ++ ++ path = nsm_make_pathname(NSM_MONITOR_DIR); ++ if (path == NULL) { ++ xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); ++ return count; ++ } ++ ++ dir = opendir(path); ++ free(path); ++ if (dir == NULL) { ++ xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); ++ return count; ++ } ++ ++ while ((de = readdir(dir)) != NULL) { ++ char *src, *dst; ++ ++ if (de->d_type != (unsigned char)DT_REG) ++ continue; ++ if (de->d_name[0] == '.') ++ continue; ++ ++ src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); ++ if (src == NULL) { ++ xlog_warn("Bad monitor file name, skipping"); ++ continue; ++ } ++ ++ dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); ++ if (dst == NULL) { ++ free(src); ++ xlog_warn("Bad notify file name, skipping"); ++ continue; ++ } ++ ++ if (rename(src, dst) == -1) ++ xlog_warn("Failed to rename %s -> %s: %m", ++ src, dst); ++ else { ++ xlog(D_GENERAL, "Retired record for mon_name %s", ++ de->d_name); ++ count++; ++ } ++ ++ free(dst); ++ free(src); ++ } ++ ++ (void)closedir(dir); ++ return count; ++} ++ ++/* ++ * nsm_priv_to_hex - convert a NSM private cookie to a hex string. ++ * ++ * @priv: buffer holding the binary NSM private cookie ++ * @buf: output buffer for NULL terminated hex string ++ * @buflen: size of output buffer ++ * ++ * Returns the length of the resulting string or 0 on error ++ */ ++size_t ++nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen) ++{ ++ int i, len; ++ size_t remaining = buflen; ++ ++ for (i = 0; i < SM_PRIV_SIZE; i++) { ++ len = snprintf(buf, remaining, "%02x", ++ (unsigned int)(0xff & priv[i])); ++ if (error_check(len, remaining)) ++ return 0; ++ buf += len; ++ remaining -= (size_t)len; ++ } ++ ++ return buflen - remaining; ++} ++ ++/* ++ * Returns the length in bytes of the created record. ++ */ ++__attribute_noinline__ ++static size_t ++nsm_create_monitor_record(char *buf, const size_t buflen, ++ const struct sockaddr *sap, const struct mon *m) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ size_t hexlen, remaining = buflen; ++ int len; ++ ++ len = snprintf(buf, remaining, "%08x %08x %08x %08x ", ++ (unsigned int)sin->sin_addr.s_addr, ++ (unsigned int)m->mon_id.my_id.my_prog, ++ (unsigned int)m->mon_id.my_id.my_vers, ++ (unsigned int)m->mon_id.my_id.my_proc); ++ if (error_check(len, remaining)) ++ return 0; ++ buf += len; ++ remaining -= (size_t)len; ++ ++ hexlen = nsm_priv_to_hex(m->priv, buf, remaining); ++ if (hexlen == 0) ++ return 0; ++ buf += hexlen; ++ remaining -= hexlen; ++ ++ len = snprintf(buf, remaining, " %s %s\n", ++ m->mon_id.mon_name, m->mon_id.my_id.my_name); ++ if (error_check(len, remaining)) ++ return 0; ++ remaining -= (size_t)len; ++ ++ return buflen - remaining; ++} ++ ++static _Bool ++nsm_append_monitored_host(const char *path, const char *line) ++{ ++ _Bool result = false; ++ char *buf = NULL; ++ struct stat stb; ++ size_t buflen; ++ ssize_t len; ++ int fd; ++ ++ if (stat(path, &stb) == -1) { ++ xlog(L_ERROR, "Failed to insert: " ++ "could not stat original file %s: %m", path); ++ goto out; ++ } ++ buflen = (size_t)stb.st_size + strlen(line); ++ ++ buf = malloc(buflen + 1); ++ if (buf == NULL) { ++ xlog(L_ERROR, "Failed to insert: no memory"); ++ goto out; ++ } ++ memset(buf, 0, buflen + 1); ++ ++ fd = open(path, O_RDONLY); ++ if (fd == -1) { ++ xlog(L_ERROR, "Failed to insert: " ++ "could not open original file %s: %m", path); ++ goto out; ++ } ++ ++ len = read(fd, buf, (size_t)stb.st_size); ++ if (exact_error_check(len, (size_t)stb.st_size)) { ++ xlog(L_ERROR, "Failed to insert: " ++ "could not read original file %s: %m", path); ++ (void)close(fd); ++ goto out; ++ } ++ (void)close(fd); ++ ++ strcat(buf, line); ++ ++ if (nsm_atomic_write(path, buf, buflen)) ++ result = true; ++ ++out: ++ free(buf); ++ return result; ++} ++ ++/** ++ * nsm_insert_monitored_host - write callback data for one host to disk ++ * @hostname: C string containing a hostname ++ * @sap: sockaddr containing NLM callback address ++ * @mon: SM_MON arguments to save ++ * ++ * Returns true if successful, otherwise false if some error occurs. ++ */ ++_Bool ++nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, ++ const struct mon *m) ++{ ++ static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; ++ char *path; ++ _Bool result = false; ++ ssize_t len; ++ size_t size; ++ int fd; ++ ++ path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname); ++ if (path == NULL) { ++ xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'", ++ hostname); ++ return false; ++ } ++ ++ size = nsm_create_monitor_record(buf, sizeof(buf), sap, m); ++ if (size == 0) { ++ xlog(L_ERROR, "Failed to insert: record too long"); ++ goto out; ++ } ++ ++ /* ++ * If exclusive create fails, we're adding a new line to an ++ * existing file. ++ */ ++ fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR); ++ if (fd == -1) { ++ if (errno != EEXIST) { ++ xlog(L_ERROR, "Failed to insert: creating %s: %m", path); ++ goto out; ++ } ++ ++ result = nsm_append_monitored_host(path, buf); ++ goto out; ++ } ++ result = true; ++ ++ len = write(fd, buf, size); ++ if (exact_error_check(len, size)) { ++ xlog_warn("Failed to insert: writing %s: %m", path); ++ (void)unlink(path); ++ result = false; ++ } ++ ++ if (close(fd) == -1) { ++ xlog(L_ERROR, "Failed to insert: closing %s: %m", path); ++ (void)unlink(path); ++ result = false; ++ } ++ ++out: ++ free(path); ++ return result; ++} ++ ++__attribute_noinline__ ++static _Bool ++nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m) ++{ ++ unsigned int i, tmp; ++ int count; ++ char *c; ++ ++ c = strchr(line, '\n'); ++ if (c != NULL) ++ *c = '\0'; ++ ++ count = sscanf(line, "%8x %8x %8x %8x ", ++ (unsigned int *)&sin->sin_addr.s_addr, ++ (unsigned int *)&m->mon_id.my_id.my_prog, ++ (unsigned int *)&m->mon_id.my_id.my_vers, ++ (unsigned int *)&m->mon_id.my_id.my_proc); ++ if (count != 4) ++ return false; ++ ++ c = line + RPCARGSLEN; ++ for (i = 0; i < SM_PRIV_SIZE; i++) { ++ if (sscanf(c, "%2x", &tmp) != 1) ++ return false; ++ m->priv[i] = (char)tmp; ++ c += 2; ++ } ++ ++ c++; ++ m->mon_id.mon_name = c; ++ while (*c != '\0' && *c != ' ') ++ c++; ++ if (*c != '\0') ++ *c++ = '\0'; ++ while (*c == ' ') ++ c++; ++ m->mon_id.my_id.my_name = c; ++ ++ return true; ++} ++ ++/* ++ * Stuff a 'struct mon' with callback data, and call @func. ++ * ++ * Returns the count of in-core records created. ++ */ ++static unsigned int ++nsm_read_line(const char *hostname, const time_t timestamp, char *line, ++ nsm_populate_t func) ++{ ++ struct sockaddr_in sin = { ++ .sin_family = AF_INET, ++ }; ++ struct mon m; ++ ++ if (!nsm_parse_line(line, &sin, &m)) ++ return 0; ++ ++ return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp); ++} ++ ++/* ++ * Given a filename, reads data from a file under NSM_MONITOR_DIR ++ * and invokes @func so caller can populate their in-core ++ * database with this data. ++ */ ++static unsigned int ++nsm_load_host(const char *directory, const char *filename, nsm_populate_t func) ++{ ++ char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; ++ unsigned int result = 0; ++ struct stat stb; ++ char *path; ++ FILE *f; ++ ++ path = nsm_make_record_pathname(directory, filename); ++ if (path == NULL) ++ goto out_err; ++ ++ if (stat(path, &stb) == -1) { ++ xlog(L_ERROR, "Failed to stat %s: %m", path); ++ goto out_freepath; ++ } ++ ++ f = fopen(path, "r"); ++ if (f == NULL) { ++ xlog(L_ERROR, "Failed to open %s: %m", path); ++ goto out_freepath; ++ } ++ ++ while (fgets(buf, (int)sizeof(buf), f) != NULL) { ++ buf[sizeof(buf) - 1] = '\0'; ++ result += nsm_read_line(filename, stb.st_mtime, buf, func); ++ } ++ if (result == 0) ++ xlog(L_ERROR, "Failed to read monitor data from %s", path); ++ ++ (void)fclose(f); ++ ++out_freepath: ++ free(path); ++out_err: ++ return result; ++} ++ ++static unsigned int ++nsm_load_dir(const char *directory, nsm_populate_t func) ++{ ++ unsigned int count = 0; ++ struct dirent *de; ++ char *path; ++ DIR *dir; ++ ++ path = nsm_make_pathname(directory); ++ if (path == NULL) { ++ xlog(L_ERROR, "Failed to allocate path for directory %s", ++ directory); ++ return 0; ++ } ++ ++ dir = opendir(path); ++ free(path); ++ if (dir == NULL) { ++ xlog(L_ERROR, "Failed to open directory %s: %m", ++ directory); ++ return 0; ++ } ++ ++ while ((de = readdir(dir)) != NULL) { ++ if (de->d_type != (unsigned char)DT_REG) ++ continue; ++ if (de->d_name[0] == '.') ++ continue; ++ ++ count += nsm_load_host(directory, de->d_name, func); ++ } ++ ++ (void)closedir(dir); ++ return count; ++} ++ ++/** ++ * nsm_load_monitor_list - load list of hosts to monitor ++ * @func: callback function to create entry for one host ++ * ++ * Returns the count of hosts that were found in the directory. ++ */ ++unsigned int ++nsm_load_monitor_list(nsm_populate_t func) ++{ ++ return nsm_load_dir(NSM_MONITOR_DIR, func); ++} ++ ++/** ++ * nsm_load_notify_list - load list of hosts to notify ++ * @func: callback function to create entry for one host ++ * ++ * Returns the count of hosts that were found in the directory. ++ */ ++unsigned int ++nsm_load_notify_list(nsm_populate_t func) ++{ ++ return nsm_load_dir(NSM_NOTIFY_DIR, func); ++} ++ ++static void ++nsm_delete_host(const char *directory, const char *hostname, ++ const char *mon_name, const char *my_name) ++{ ++ char line[LINELEN + 1 + SM_MAXSTRLEN + 2]; ++ char *outbuf = NULL; ++ struct stat stb; ++ char *path, *next; ++ size_t remaining; ++ FILE *f; ++ ++ path = nsm_make_record_pathname(directory, hostname); ++ if (path == NULL) { ++ xlog(L_ERROR, "Bad filename, not deleting"); ++ return; ++ } ++ ++ if (stat(path, &stb) == -1) { ++ xlog(L_ERROR, "Failed to delete: " ++ "could not stat original file %s: %m", path); ++ goto out; ++ } ++ remaining = (size_t)stb.st_size + 1; ++ ++ outbuf = malloc(remaining); ++ if (outbuf == NULL) { ++ xlog(L_ERROR, "Failed to delete: no memory"); ++ goto out; ++ } ++ ++ f = fopen(path, "r"); ++ if (f == NULL) { ++ xlog(L_ERROR, "Failed to delete: " ++ "could not open original file %s: %m", path); ++ goto out; ++ } ++ ++ /* ++ * Walk the records in the file, and copy the non-matching ++ * ones to our output buffer. ++ */ ++ next = outbuf; ++ while (fgets(line, (int)sizeof(line), f) != NULL) { ++ struct sockaddr_in sin; ++ struct mon m; ++ size_t len; ++ ++ if (!nsm_parse_line(line, &sin, &m)) { ++ xlog(L_ERROR, "Failed to delete: " ++ "could not parse original file %s", path); ++ (void)fclose(f); ++ goto out; ++ } ++ ++ if (strcmp(mon_name, m.mon_id.mon_name) == 0 && ++ strcmp(my_name, m.mon_id.my_id.my_name) == 0) ++ continue; ++ ++ /* nsm_parse_line destroys the contents of line[], so ++ * reconstruct the copy in our output buffer. */ ++ len = nsm_create_monitor_record(next, remaining, ++ (struct sockaddr *)(char *)&sin, &m); ++ if (len == 0) { ++ xlog(L_ERROR, "Failed to delete: " ++ "could not construct output record"); ++ (void)fclose(f); ++ goto out; ++ } ++ next += len; ++ remaining -= len; ++ } ++ ++ (void)fclose(f); ++ ++ /* ++ * If nothing was copied when we're done, then unlink the file. ++ * Otherwise, atomically update the contents of the file. ++ */ ++ if (next != outbuf) { ++ if (!nsm_atomic_write(path, outbuf, strlen(outbuf))) ++ xlog(L_ERROR, "Failed to delete: " ++ "could not write new file %s: %m", path); ++ } else { ++ if (unlink(path) == -1) ++ xlog(L_ERROR, "Failed to delete: " ++ "could not unlink file %s: %m", path); ++ } ++ ++out: ++ free(outbuf); ++ free(path); ++} ++ ++/** ++ * nsm_delete_monitored_host - delete on-disk record for monitored host ++ * @hostname: '\0'-terminated C string containing hostname of record to delete ++ * @mon_name: '\0'-terminated C string containing monname of record to delete ++ * @my_name: '\0'-terminated C string containing myname of record to delete ++ * ++ */ ++void ++nsm_delete_monitored_host(const char *hostname, const char *mon_name, ++ const char *my_name) ++{ ++ nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name); ++} ++ ++/** ++ * nsm_delete_notified_host - delete on-disk host record after notification ++ * @hostname: '\0'-terminated C string containing hostname of record to delete ++ * @mon_name: '\0'-terminated C string containing monname of record to delete ++ * @my_name: '\0'-terminated C string containing myname of record to delete ++ * ++ */ ++void ++nsm_delete_notified_host(const char *hostname, const char *mon_name, ++ const char *my_name) ++{ ++ nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name); ++} +diff --git a/support/nsm/rpc.c b/support/nsm/rpc.c +new file mode 100644 +index 0000000..4e5f40e +--- /dev/null ++++ b/support/nsm/rpc.c +@@ -0,0 +1,534 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++/* ++ * NSM for Linux. ++ * ++ * Instead of using ONC or TI RPC library calls, statd constructs ++ * RPC calls directly in socket buffers. This allows a single ++ * socket to be concurrently shared among several different RPC ++ * programs and versions using a simple RPC request dispatcher. ++ * ++ * This file contains the details of RPC header and call ++ * construction and reply parsing, and a method for creating a ++ * socket for use with these functions. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif /* HAVE_CONFIG_H */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#ifdef HAVE_LIBTIRPC ++#include ++#include ++#endif /* HAVE_LIBTIRPC */ ++ ++#include "xlog.h" ++#include "nfsrpc.h" ++#include "nsm.h" ++#include "sm_inter.h" ++ ++/* ++ * Returns a fresh XID appropriate for RPC over UDP -- never zero. ++ */ ++static uint32_t ++nsm_next_xid(void) ++{ ++ static uint32_t nsm_xid = 0; ++ struct timeval now; ++ ++ if (nsm_xid == 0) { ++ (void)gettimeofday(&now, NULL); ++ nsm_xid = (uint32_t)getpid() ^ ++ (uint32_t)now.tv_sec ^ (uint32_t)now.tv_usec; ++ } ++ ++ return nsm_xid++; ++} ++ ++/* ++ * Select a fresh XID and construct an RPC header in @mesg. ++ * Always use AUTH_NULL credentials and verifiers. ++ * ++ * Returns the new XID. ++ */ ++static uint32_t ++nsm_init_rpc_header(const rpcprog_t program, const rpcvers_t version, ++ const rpcproc_t procedure, struct rpc_msg *mesg) ++{ ++ struct call_body *cb = &mesg->rm_call; ++ uint32_t xid = nsm_next_xid(); ++ ++ memset(mesg, 0, sizeof(*mesg)); ++ ++ mesg->rm_xid = (unsigned long)xid; ++ mesg->rm_direction = CALL; ++ ++ cb->cb_rpcvers = RPC_MSG_VERSION; ++ cb->cb_prog = program; ++ cb->cb_vers = version; ++ cb->cb_proc = procedure; ++ ++ cb->cb_cred.oa_flavor = AUTH_NULL; ++ cb->cb_cred.oa_base = (caddr_t) NULL; ++ cb->cb_cred.oa_length = 0; ++ cb->cb_verf.oa_flavor = AUTH_NULL; ++ cb->cb_verf.oa_base = (caddr_t) NULL; ++ cb->cb_verf.oa_length = 0; ++ ++ return xid; ++} ++ ++/* ++ * Initialize the network send buffer and XDR memory for encoding. ++ */ ++static void ++nsm_init_xdrmem(char *msgbuf, const unsigned int msgbuflen, ++ XDR *xdrp) ++{ ++ memset(msgbuf, 0, (size_t)msgbuflen); ++ memset(xdrp, 0, sizeof(*xdrp)); ++ xdrmem_create(xdrp, msgbuf, msgbuflen, XDR_ENCODE); ++} ++ ++/* ++ * Send a completed RPC call on a socket. ++ * ++ * Returns true if all the bytes were sent successfully; otherwise ++ * false if any error occurred. ++ */ ++static _Bool ++nsm_rpc_sendto(const int sock, const struct sockaddr *sap, ++ const socklen_t salen, XDR *xdrs, void *buf) ++{ ++ const size_t buflen = (size_t)xdr_getpos(xdrs); ++ ssize_t err; ++ ++ err = sendto(sock, buf, buflen, 0, sap, salen); ++ if ((err < 0) || ((size_t)err != buflen)) { ++ xlog(L_ERROR, "%s: sendto failed: %m", __func__); ++ return false; ++ } ++ return true; ++} ++ ++/** ++ * nsm_xmit_getport - post a PMAP_GETPORT call on a socket descriptor ++ * @sock: datagram socket descriptor ++ * @sin: pointer to AF_INET socket address of server ++ * @program: RPC program number to query ++ * @version: RPC version number to query ++ * ++ * Send a PMAP_GETPORT call to the portmap daemon at @sin using ++ * socket descriptor @sock. This request queries the RPC program ++ * [program, version, IPPROTO_UDP]. ++ * ++ * NB: PMAP_GETPORT works only for IPv4 hosts. This implementation ++ * works only over UDP, and queries only UDP registrations. ++ * ++ * Returns the XID of the call, or zero if an error occurred. ++ */ ++uint32_t ++nsm_xmit_getport(const int sock, const struct sockaddr_in *sin, ++ const unsigned long program, ++ const unsigned long version) ++{ ++ char msgbuf[NSM_MAXMSGSIZE]; ++ struct sockaddr_in addr; ++ struct rpc_msg mesg; ++ _Bool sent = false; ++ struct pmap parms = { ++ .pm_prog = program, ++ .pm_vers = version, ++ .pm_prot = (unsigned long)IPPROTO_UDP, ++ }; ++ uint32_t xid; ++ XDR xdr; ++ ++ xlog(D_CALL, "Sending PMAP_GETPORT for %u, %u, udp", program, version); ++ ++ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); ++ xid = nsm_init_rpc_header(PMAPPROG, PMAPVERS, ++ (rpcproc_t)PMAPPROC_GETPORT, &mesg); ++ ++ addr = *sin; ++ addr.sin_port = htons(PMAPPORT); ++ ++ if (xdr_callmsg(&xdr, &mesg) == TRUE && ++ xdr_pmap(&xdr, &parms) == TRUE) ++ sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr, ++ (socklen_t)sizeof(addr), &xdr, msgbuf); ++ else ++ xlog(L_ERROR, "%s: can't encode PMAP_GETPORT call", __func__); ++ ++ xdr_destroy(&xdr); ++ ++ if (sent == false) ++ return 0; ++ return xid; ++} ++ ++/** ++ * nsm_xmit_getaddr - post an RPCB_GETADDR call on a socket descriptor ++ * @sock: datagram socket descriptor ++ * @sin: pointer to AF_INET6 socket address of server ++ * @program: RPC program number to query ++ * @version: RPC version number to query ++ * ++ * Send an RPCB_GETADDR call to the rpcbind daemon at @sap using ++ * socket descriptor @sock. This request queries the RPC program ++ * [program, version, "udp6"]. ++ * ++ * NB: RPCB_GETADDR works for both IPv4 and IPv6 hosts. This ++ * implementation works only over UDP and AF_INET6, and queries ++ * only "udp6" registrations. ++ * ++ * Returns the XID of the call, or zero if an error occurred. ++ */ ++#ifdef HAVE_LIBTIRPC ++uint32_t ++nsm_xmit_getaddr(const int sock, const struct sockaddr_in6 *sin6, ++ const rpcprog_t program, const rpcvers_t version) ++{ ++ char msgbuf[NSM_MAXMSGSIZE]; ++ struct sockaddr_in6 addr; ++ struct rpc_msg mesg; ++ _Bool sent = false; ++ struct rpcb parms = { ++ .r_prog = program, ++ .r_vers = version, ++ .r_netid = "udp6", ++ .r_owner = "", ++ }; ++ uint32_t xid; ++ XDR xdr; ++ ++ xlog(D_CALL, "Sending RPCB_GETADDR for %u, %u, udp6", program, version); ++ ++ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); ++ xid = nsm_init_rpc_header(RPCBPROG, RPCBVERS, ++ (rpcproc_t)RPCBPROC_GETADDR, &mesg); ++ ++ addr = *sin6; ++ addr.sin6_port = htons(PMAPPORT); ++ parms.r_addr = nfs_sockaddr2universal((struct sockaddr *)(char *)&addr); ++ if (parms.r_addr == NULL) { ++ xlog(L_ERROR, "%s: can't encode socket address", __func__); ++ return 0; ++ } ++ ++ if (xdr_callmsg(&xdr, &mesg) == TRUE && ++ xdr_rpcb(&xdr, &parms) == TRUE) ++ sent = nsm_rpc_sendto(sock, (struct sockaddr *)(char *)&addr, ++ (socklen_t)sizeof(addr), &xdr, msgbuf); ++ else ++ xlog(L_ERROR, "%s: can't encode RPCB_GETADDR call", __func__); ++ ++ xdr_destroy(&xdr); ++ free(parms.r_addr); ++ ++ if (sent == false) ++ return 0; ++ return xid; ++} ++#else /* !HAVE_LIBTIRPC */ ++uint32_t ++nsm_xmit_getaddr(const int sock __attribute__((unused)), ++ const struct sockaddr_in6 *sin6 __attribute__((unused)), ++ const rpcprog_t program __attribute__((unused)), ++ const rpcvers_t version __attribute__((unused))) ++{ ++ return 0; ++} ++#endif /* !HAVE_LIBTIRPC */ ++ ++/** ++ * nsm_xmit_rpcbind - post an rpcbind request ++ * @sock: datagram socket descriptor ++ * @sap: pointer to socket address of server ++ * @program: RPC program number to query ++ * @version: RPC version number to query ++ * ++ * Send an rpcbind query to the rpcbind daemon at @sap using ++ * socket descriptor @sock. ++ * ++ * NB: This implementation works only over UDP, but can query IPv4 or IPv6 ++ * hosts. It queries only UDP registrations. ++ * ++ * Returns the XID of the call, or zero if an error occurred. ++ */ ++uint32_t ++nsm_xmit_rpcbind(const int sock, const struct sockaddr *sap, ++ const rpcprog_t program, const rpcvers_t version) ++{ ++ switch (sap->sa_family) { ++ case AF_INET: ++ return nsm_xmit_getport(sock, (const struct sockaddr_in *)sap, ++ program, version); ++ case AF_INET6: ++ return nsm_xmit_getaddr(sock, (const struct sockaddr_in6 *)sap, ++ program, version); ++ } ++ return 0; ++} ++ ++/** ++ * nsm_xmit_notify - post an NSMPROC_NOTIFY call on a socket descriptor ++ * @sock: datagram socket descriptor ++ * @sap: pointer to socket address of peer to notify (port already filled in) ++ * @salen: length of socket address ++ * @program: RPC program number to use ++ * @mon_name: mon_name of local peer (ie the rebooting system) ++ * @state: state of local peer ++ * ++ * Send an NSMPROC_NOTIFY call to the peer at @sap using socket descriptor @sock. ++ * This request notifies the peer that we have rebooted. ++ * ++ * NB: This implementation works only over UDP, but supports both AF_INET ++ * and AF_INET6. ++ * ++ * Returns the XID of the call, or zero if an error occurred. ++ */ ++uint32_t ++nsm_xmit_notify(const int sock, const struct sockaddr *sap, ++ const socklen_t salen, const rpcprog_t program, ++ const char *mon_name, const int state) ++{ ++ char msgbuf[NSM_MAXMSGSIZE]; ++ struct stat_chge state_change; ++ struct rpc_msg mesg; ++ _Bool sent = false; ++ uint32_t xid; ++ XDR xdr; ++ ++ state_change.mon_name = strdup(mon_name); ++ if (state_change.mon_name == NULL) { ++ xlog(L_ERROR, "%s: no memory", __func__); ++ return 0; ++ } ++ state_change.state = state; ++ ++ xlog(D_CALL, "Sending SM_NOTIFY for %s", mon_name); ++ ++ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); ++ xid = nsm_init_rpc_header(program, SM_VERS, SM_NOTIFY, &mesg); ++ ++ if (xdr_callmsg(&xdr, &mesg) == TRUE && ++ xdr_stat_chge(&xdr, &state_change) == TRUE) ++ sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf); ++ else ++ xlog(L_ERROR, "%s: can't encode NSMPROC_NOTIFY call", ++ __func__); ++ ++ xdr_destroy(&xdr); ++ free(state_change.mon_name); ++ ++ if (sent == false) ++ return 0; ++ return xid; ++} ++ ++/** ++ * nsm_xmit_nlmcall - post an unnamed call to local NLM on a socket descriptor ++ * @sock: datagram socket descriptor ++ * @sap: address/port of NLM service to contact ++ * @salen: size of @sap ++ * @m: callback data defining RPC call to make ++ * @state: state of rebooting host ++ * ++ * Send an unnamed call (previously requested via NSMPROC_MON) to the ++ * specified local UDP-based RPC service using socket descriptor @sock. ++ * ++ * NB: This implementation works only over UDP, but supports both AF_INET ++ * and AF_INET6. ++ * ++ * Returns the XID of the call, or zero if an error occurred. ++ */ ++uint32_t ++nsm_xmit_nlmcall(const int sock, const struct sockaddr *sap, ++ const socklen_t salen, const struct mon *m, ++ const int state) ++{ ++ const struct my_id *id = &m->mon_id.my_id; ++ char msgbuf[NSM_MAXMSGSIZE]; ++ struct status new_status; ++ struct rpc_msg mesg; ++ _Bool sent = false; ++ uint32_t xid; ++ XDR xdr; ++ ++ xlog(D_CALL, "Sending NLM downcall for %s", m->mon_id.mon_name); ++ ++ nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); ++ xid = nsm_init_rpc_header((rpcprog_t)id->my_prog, ++ (rpcvers_t)id->my_vers, ++ (rpcproc_t)id->my_proc, &mesg); ++ ++ new_status.mon_name = m->mon_id.mon_name; ++ new_status.state = state; ++ memcpy(&new_status.priv, &m->priv, sizeof(new_status.priv)); ++ ++ if (xdr_callmsg(&xdr, &mesg) == TRUE && ++ xdr_status(&xdr, &new_status) == TRUE) ++ sent = nsm_rpc_sendto(sock, sap, salen, &xdr, msgbuf); ++ else ++ xlog(L_ERROR, "%s: can't encode NLM downcall", __func__); ++ ++ xdr_destroy(&xdr); ++ ++ if (sent == false) ++ return 0; ++ return xid; ++} ++ ++/** ++ * nsm_parse_reply - parse and validate the header in an RPC reply ++ * @xdrs: pointer to XDR ++ * ++ * Returns the XID of the reply, or zero if an error occurred. ++ */ ++uint32_t ++nsm_parse_reply(XDR *xdrs) ++{ ++ struct rpc_msg mesg = { ++ .rm_reply.rp_acpt.ar_results.proc = (xdrproc_t)xdr_void, ++ }; ++ uint32_t xid; ++ ++ if (xdr_replymsg(xdrs, &mesg) == FALSE) { ++ xlog(L_ERROR, "%s: can't decode RPC reply", __func__); ++ return 0; ++ } ++ xid = (uint32_t)mesg.rm_xid; ++ ++ if (mesg.rm_reply.rp_stat != MSG_ACCEPTED) { ++ xlog(L_ERROR, "%s: [0x%x] RPC status %d", ++ __func__, xid, mesg.rm_reply.rp_stat); ++ return 0; ++ } ++ ++ if (mesg.rm_reply.rp_acpt.ar_stat != SUCCESS) { ++ xlog(L_ERROR, "%s: [0x%x] RPC accept status %d", ++ __func__, xid, mesg.rm_reply.rp_acpt.ar_stat); ++ return 0; ++ } ++ ++ return xid; ++} ++ ++/** ++ * nsm_recv_getport - parse PMAP_GETPORT reply ++ * @xdrs: pointer to XDR ++ * ++ * Returns the port number from the RPC reply, or zero ++ * if an error occurred. ++ */ ++unsigned long ++nsm_recv_getport(XDR *xdrs) ++{ ++ unsigned long port = 0; ++ ++ if (xdr_u_long(xdrs, &port) == FALSE) ++ xlog(L_ERROR, "%s: can't decode pmap reply", ++ __func__); ++ if (port > UINT16_MAX) { ++ xlog(L_ERROR, "%s: bad port number", ++ __func__); ++ port = 0; ++ } ++ ++ xlog(D_CALL, "Received PMAP_GETPORT result: %lu", port); ++ return port; ++} ++ ++/** ++ * nsm_recv_getaddr - parse RPCB_GETADDR reply ++ * @xdrs: pointer to XDR ++ * ++ * Returns the port number from the RPC reply, or zero ++ * if an error occurred. ++ */ ++uint16_t ++nsm_recv_getaddr(XDR *xdrs) ++{ ++ char *uaddr = NULL; ++ int port; ++ ++ if (xdr_wrapstring(xdrs, &uaddr) == FALSE) ++ xlog(L_ERROR, "%s: can't decode rpcb reply", ++ __func__); ++ ++ if ((uaddr == NULL) || (uaddr[0] == '\0')) { ++ xlog(D_CALL, "Received RPCB_GETADDR result: " ++ "program not registered"); ++ return 0; ++ } ++ ++ port = nfs_universal2port(uaddr); ++ ++ xdr_free((xdrproc_t)xdr_wrapstring, (char *)&uaddr); ++ ++ if (port < 0 || port > UINT16_MAX) { ++ xlog(L_ERROR, "%s: bad port number", ++ __func__); ++ return 0; ++ } ++ ++ xlog(D_CALL, "Received RPCB_GETADDR result: %d", port); ++ return (uint16_t)port; ++} ++ ++/** ++ * nsm_recv_rpcbind - parse rpcbind reply ++ * @af: address family of reply ++ * @xdrs: pointer to XDR ++ * ++ * Returns the port number from the RPC reply, or zero ++ * if an error occurred. ++ */ ++uint16_t ++nsm_recv_rpcbind(const sa_family_t family, XDR *xdrs) ++{ ++ switch (family) { ++ case AF_INET: ++ return (uint16_t)nsm_recv_getport(xdrs); ++ case AF_INET6: ++ return nsm_recv_getaddr(xdrs); ++ } ++ return 0; ++} +diff --git a/support/nsm/sm_inter.x b/support/nsm/sm_inter.x +new file mode 100644 +index 0000000..d8e0ad7 +--- /dev/null ++++ b/support/nsm/sm_inter.x +@@ -0,0 +1,131 @@ ++/* ++ * Copyright (C) 1986 Sun Microsystems, Inc. ++ * Modified by Jeffrey A. Uphoff, 1995, 1997-1999. ++ * Modified by Olaf Kirch, 1996. ++ * Modified by H.J. Lu, 1998. ++ * ++ * NSM for Linux. ++ */ ++ ++/* ++ * Copyright (c) 2009, Sun Microsystems, Inc. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * - Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * - Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * - Neither the name of Sun Microsystems, Inc. nor the names of its ++ * contributors may be used to endorse or promote products derived ++ * from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/* ++ * Status monitor protocol specification ++ */ ++ ++#ifdef RPC_CLNT ++%#include ++#endif ++ ++program SM_PROG { ++ version SM_VERS { ++ /* res_stat = stat_succ if status monitor agrees to monitor */ ++ /* res_stat = stat_fail if status monitor cannot monitor */ ++ /* if res_stat == stat_succ, state = state number of site sm_name */ ++ struct sm_stat_res SM_STAT(struct sm_name) = 1; ++ ++ /* res_stat = stat_succ if status monitor agrees to monitor */ ++ /* res_stat = stat_fail if status monitor cannot monitor */ ++ /* stat consists of state number of local site */ ++ struct sm_stat_res SM_MON(struct mon) = 2; ++ ++ /* stat consists of state number of local site */ ++ struct sm_stat SM_UNMON(struct mon_id) = 3; ++ ++ /* stat consists of state number of local site */ ++ struct sm_stat SM_UNMON_ALL(struct my_id) = 4; ++ ++ void SM_SIMU_CRASH(void) = 5; ++ ++ void SM_NOTIFY(struct stat_chge) = 6; ++ ++ } = 1; ++} = 100024; ++ ++const SM_MAXSTRLEN = 1024; ++const SM_PRIV_SIZE = 16; ++ ++struct sm_name { ++ string mon_name; ++}; ++ ++struct my_id { ++ string my_name; /* name of the site iniates the monitoring request*/ ++ int my_prog; /* rpc program # of the requesting process */ ++ int my_vers; /* rpc version # of the requesting process */ ++ int my_proc; /* rpc procedure # of the requesting process */ ++}; ++ ++struct mon_id { ++ string mon_name; /* name of the site to be monitored */ ++ struct my_id my_id; ++}; ++ ++ ++struct mon { ++ struct mon_id mon_id; ++ opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */ ++}; ++ ++struct stat_chge { ++ string mon_name; /* name of the site that had the state change */ ++ int state; ++}; ++ ++/* ++ * state # of status monitor monitonically increases each time ++ * status of the site changes: ++ * an even number (>= 0) indicates the site is down and ++ * an odd number (> 0) indicates the site is up; ++ */ ++struct sm_stat { ++ int state; /* state # of status monitor */ ++}; ++ ++enum res { ++ stat_succ = 0, /* status monitor agrees to monitor */ ++ stat_fail = 1 /* status monitor cannot monitor */ ++}; ++ ++struct sm_stat_res { ++ res res_stat; ++ int state; ++}; ++ ++/* ++ * structure of the status message sent back by the status monitor ++ * when monitor site status changes ++ */ ++struct status { ++ string mon_name; ++ int state; ++ opaque priv[SM_PRIV_SIZE]; /* stored private information */ ++}; ++ ++%#define SM_INTER_X +diff --git a/tests/Makefile.am b/tests/Makefile.am +new file mode 100644 +index 0000000..faa8197 +--- /dev/null ++++ b/tests/Makefile.am +@@ -0,0 +1,13 @@ ++## Process this file with automake to produce Makefile.in ++ ++check_PROGRAMS = statdb_dump ++statdb_dump_SOURCES = statdb_dump.c ++ ++statdb_dump_LDADD = ../support/nfs/libnfs.a \ ++ ../support/nsm/libnsm.a $(LIBCAP) ++ ++SUBDIRS = nsm_client ++ ++MAINTAINERCLEANFILES = Makefile.in ++ ++TESTS = t0001-statd-basic-mon-unmon.sh +diff --git a/tests/nsm_client/Makefile.am b/tests/nsm_client/Makefile.am +new file mode 100644 +index 0000000..4bf0a45 +--- /dev/null ++++ b/tests/nsm_client/Makefile.am +@@ -0,0 +1,45 @@ ++## Process this file with automake to produce Makefile.in ++ ++GENFILES_CLNT = nlm_sm_inter_clnt.c ++GENFILES_SVC = nlm_sm_inter_svc.c ++GENFILES_XDR = nlm_sm_inter_xdr.c ++GENFILES_H = nlm_sm_inter.h ++ ++GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) ++ ++ ++check_PROGRAMS = nsm_client ++nsm_client_SOURCES = $(GENFILES) nsm_client.c ++ ++BUILT_SOURCES = $(GENFILES) ++nsm_client_LDADD = ../../support/nfs/libnfs.a \ ++ ../../support/nsm/libnsm.a $(LIBCAP) ++ ++if CONFIG_RPCGEN ++RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen ++$(RPCGEN): ++ make -C ../../tools/rpcgen all ++else ++RPCGEN = @RPCGEN_PATH@ ++endif ++ ++$(GENFILES_CLNT): %_clnt.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -l -o $@ $< ++ ++$(GENFILES_SVC): %_svc.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -m -o $@ $< ++ ++$(GENFILES_XDR): %_xdr.c: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -c -o $@ $< ++ ++$(GENFILES_H): %.h: %.x $(RPCGEN) ++ test -f $@ && rm -rf $@ || true ++ $(RPCGEN) -h -o $@ $< ++ ++MAINTAINERCLEANFILES = Makefile.in ++ ++CLEANFILES = $(GENFILES) ++ +diff --git a/tests/nsm_client/README b/tests/nsm_client/README +new file mode 100644 +index 0000000..85379dd +--- /dev/null ++++ b/tests/nsm_client/README +@@ -0,0 +1,12 @@ ++The nsm_client program is intended for testing statd. It has the ability ++to act as a synthetic NSM client for sending artificial NSM calls to any ++host you choose. ++ ++It also has an NLM simulator that implements the call that statd uses to ++communicate with lockd. The daemon simulator will start itself up, ++register as an NLM service and listen for "downcalls" from statd. When ++it gets one, it will log a message. ++ ++Note that lockd will need to be down when using the daemon simulator. It ++also does not implement the entire NLM protocol and is only really ++useful for testing statd's downcall. +diff --git a/tests/nsm_client/nlm_sm_inter.x b/tests/nsm_client/nlm_sm_inter.x +new file mode 100644 +index 0000000..95fa326 +--- /dev/null ++++ b/tests/nsm_client/nlm_sm_inter.x +@@ -0,0 +1,43 @@ ++/* ++ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff ++ * Modified by Olaf Kirch, 1996. ++ * Modified by H.J. Lu, 1998. ++ * Modified by Jeff Layton, 2010. ++ * ++ * NLM similator for Linux ++ */ ++ ++#ifdef RPC_CLNT ++%#include ++#endif ++ ++/* ++ * statd rejects monitor registrations for any non-lockd services, so pretend ++ * to be lockd when testing. Furthermore, the only call we care about from ++ * statd is #16, which is the downcall to notify the kernel of a host's status ++ * change. ++ */ ++program NLM_SM_PROG { ++ /* version 3 of the NLM protocol */ ++ version NLM_SM_VERS3 { ++ void NLM_SM_NOTIFY(struct nlm_sm_notify) = 16; ++ } = 3; ++ ++ /* version 2 of NLM protocol */ ++ version NLM_SM_VERS4 { ++ void NLM_SM_NOTIFY(struct nlm_sm_notify) = 16; ++ } = 4; ++} = 100021; ++ ++const SM_MAXSTRLEN = 1024; ++const SM_PRIV_SIZE = 16; ++ ++/* ++ * structure of the status message sent back by the status monitor ++ * when monitor site status changes ++ */ ++struct nlm_sm_notify { ++ string mon_name; ++ int state; ++ opaque priv[SM_PRIV_SIZE]; /* stored private information */ ++}; +diff --git a/tests/nsm_client/nsm_client.c b/tests/nsm_client/nsm_client.c +new file mode 100644 +index 0000000..0d1159a +--- /dev/null ++++ b/tests/nsm_client/nsm_client.c +@@ -0,0 +1,465 @@ ++/* ++ * nsm_client.c -- synthetic client and lockd simulator for testing statd ++ * ++ * Copyright (C) 2010 Red Hat, Jeff Layton ++ * ++ * This program 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. ++ * ++ * This program 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 this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ * ++ * Very loosely based on "simulator.c" in the statd directory. Original ++ * copyright for that program follows: ++ * ++ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nfslib.h" ++#include "nfsrpc.h" ++#include "nsm.h" ++#include "sm_inter.h" ++#include "nlm_sm_inter.h" ++#include "sockaddr.h" ++#include "xcommon.h" ++ ++static void daemon_simulator(void); ++static void sim_killer(int sig); ++static int nsm_client_crash(char *); ++static int nsm_client_mon(char *, char *, char *, char *, int, int); ++static int nsm_client_stat(char *, char *); ++static int nsm_client_notify(char *, char *, char *); ++static int nsm_client_unmon(char *, char *, char *, int, int); ++static int nsm_client_unmon_all(char *, char *, int, int); ++ ++extern void nlm_sm_prog_4(struct svc_req *rqstp, register SVCXPRT *transp); ++extern void svc_exit(void); ++ ++/* ++ * default to 15 retransmit interval, which seems to be the default for ++ * UDP clients w/ legacy glibc RPC ++ */ ++static struct timeval retrans_interval = ++{ ++ .tv_sec = 15, ++}; ++ ++static struct option longopts[] = ++{ ++ { "help", 0, 0, 'h' }, ++ { "host", 0, 0, 'H' }, ++ { "name", 1, 0, 'n' }, ++ { "program", 1, 0, 'P' }, ++ { "version", 1, 0, 'v' }, ++ { NULL, 0, 0, 0 }, ++}; ++ ++static int ++usage(char *program) ++{ ++ printf("Usage:\n"); ++ printf("%s [options] [arg]...\n", program); ++ printf("where command is one of these with the specified args:\n"); ++ printf("crash\t\t\t\ttell host to simulate crash\n"); ++ printf("daemon\t\t\t\t\tstart up lockd daemon simulator\n"); ++ printf("notify \tsend a reboot notification to host\n"); ++ printf("stat \t\t\tget status of on host\n"); ++ printf("unmon_all\t\t\ttell host to unmon everything\n"); ++ printf("unmon \t\t\ttell host to unmon \n"); ++ printf("mon \t\ttell host to monitor with private \n"); ++ return 1; ++} ++ ++static int ++hex2bin(char *dst, size_t dstlen, char *src) ++{ ++ int i; ++ unsigned int tmp; ++ ++ for (i = 0; *src && i < dstlen; i++) { ++ if (sscanf(src, "%2x", &tmp) != 1) ++ return 0; ++ dst[i] = tmp; ++ src++; ++ if (!*src) ++ break; ++ src++; ++ } ++ ++ return 1; ++} ++ ++static void ++bin2hex(char *dst, char *src, size_t srclen) ++{ ++ int i; ++ ++ for (i = 0; i < srclen; i++) ++ dst += sprintf(dst, "%02x", 0xff & src[i]); ++} ++ ++int ++main(int argc, char **argv) ++{ ++ int arg, err = 0; ++ int remaining_args; ++ char my_name[NI_MAXHOST], host[NI_MAXHOST]; ++ char cookie[SM_PRIV_SIZE]; ++ int my_prog = NLM_SM_PROG; ++ int my_vers = NLM_SM_VERS4; ++ ++ my_name[0] = '\0'; ++ host[0] = '\0'; ++ ++ while ((arg = getopt_long(argc, argv, "hHn:P:v:", longopts, ++ NULL)) != EOF) { ++ switch (arg) { ++ case 'H': ++ strncpy(host, optarg, sizeof(host)); ++ case 'n': ++ strncpy(my_name, optarg, sizeof(my_name)); ++ case 'P': ++ my_prog = atoi(optarg); ++ case 'v': ++ my_vers = atoi(optarg); ++ } ++ } ++ ++ remaining_args = argc - optind; ++ if (remaining_args <= 0) ++ usage(argv[0]); ++ ++ if (!my_name[0]) ++ gethostname(my_name, sizeof(my_name)); ++ if (!host[0]) ++ strncpy(host, "127.0.0.1", sizeof(host)); ++ ++ if (!strcasecmp(argv[optind], "daemon")) { ++ daemon_simulator(); ++ } else if (!strcasecmp(argv[optind], "crash")) { ++ err = nsm_client_crash(host); ++ } else if (!strcasecmp(argv[optind], "stat")) { ++ if (remaining_args < 2) ++ usage(argv[0]); ++ err = nsm_client_stat(host, argv[optind + 2]); ++ } else if (!strcasecmp(argv[optind], "unmon_all")) { ++ err = nsm_client_unmon_all(host, my_name, my_prog, my_vers); ++ } else if (!strcasecmp(argv[optind], "unmon")) { ++ if (remaining_args < 2) ++ usage(argv[0]); ++ err = nsm_client_unmon(host, argv[optind + 1], my_name, my_prog, ++ my_vers); ++ } else if (!strcasecmp(argv[optind], "notify")) { ++ if (remaining_args < 2) ++ usage(argv[0]); ++ err = nsm_client_notify(host, argv[optind + 1], ++ argv[optind + 2]); ++ } else if (!strcasecmp(argv[optind], "mon")) { ++ if (remaining_args < 2) ++ usage(argv[0]); ++ ++ memset(cookie, '\0', SM_PRIV_SIZE); ++ if (!hex2bin(cookie, sizeof(cookie), argv[optind + 2])) { ++ fprintf(stderr, "SYS:%d\n", EINVAL); ++ printf("Unable to convert hex cookie %s to binary.\n", ++ argv[optind + 2]); ++ return 1; ++ } ++ ++ err = nsm_client_mon(host, argv[optind + 1], cookie, my_name, ++ my_prog, my_vers); ++ } else { ++ err = usage(argv[0]); ++ } ++ ++ return err; ++} ++ ++static CLIENT * ++nsm_client_get_rpcclient(const char *node) ++{ ++ unsigned short port; ++ struct addrinfo *ai; ++ struct addrinfo hints = { .ai_flags = AI_ADDRCONFIG }; ++ int err; ++ CLIENT *client = NULL; ++ ++#ifndef IPV6_ENABLED ++ hints.ai_family = AF_INET; ++#endif /* IPV6_ENABLED */ ++ ++ /* FIXME: allow support for providing port? */ ++ err = getaddrinfo(node, NULL, &hints, &ai); ++ if (err) { ++ fprintf(stderr, "EAI:%d\n", err); ++ if (err == EAI_SYSTEM) ++ fprintf(stderr, "SYS:%d\n", errno); ++ printf("Unable to translate host to address: %s\n", ++ err == EAI_SYSTEM ? strerror(errno) : ++ gai_strerror(err)); ++ return client; ++ } ++ ++ /* FIXME: allow for TCP too? */ ++ port = nfs_getport(ai->ai_addr, ai->ai_addrlen, SM_PROG, ++ SM_VERS, IPPROTO_UDP); ++ if (!port) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("Unable to determine port for service\n"); ++ goto out; ++ } ++ ++ nfs_set_port(ai->ai_addr, port); ++ ++ client = nfs_get_rpcclient(ai->ai_addr, ai->ai_addrlen, IPPROTO_UDP, ++ SM_PROG, SM_VERS, &retrans_interval); ++ if (!client) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("RPC client creation failed\n"); ++ } ++out: ++ freeaddrinfo(ai); ++ return client; ++} ++ ++static int ++nsm_client_mon(char *calling, char *monitoring, char *cookie, char *my_name, ++ int my_prog, int my_vers) ++{ ++ CLIENT *client; ++ sm_stat_res *result; ++ mon mon; ++ int err = 0; ++ ++ printf("Calling %s (as %s) to monitor %s\n", calling, my_name, ++ monitoring); ++ ++ if ((client = nsm_client_get_rpcclient(calling)) == NULL) ++ return 1; ++ ++ memcpy(mon.priv, cookie, SM_PRIV_SIZE); ++ mon.mon_id.my_id.my_name = my_name; ++ mon.mon_id.my_id.my_prog = my_prog; ++ mon.mon_id.my_id.my_vers = my_vers; ++ mon.mon_id.my_id.my_proc = NLM_SM_NOTIFY; ++ mon.mon_id.mon_name = monitoring; ++ ++ if (!(result = sm_mon_1(&mon, client))) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_mon_1")); ++ err = 1; ++ goto mon_out; ++ } ++ ++ printf("SM_MON request %s, state: %d\n", ++ result->res_stat == stat_succ ? "successful" : "failed", ++ result->state); ++ ++ if (result->res_stat != stat_succ) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ err = 1; ++ } ++ ++mon_out: ++ clnt_destroy(client); ++ return err; ++} ++ ++static int ++nsm_client_unmon(char *calling, char *unmonitoring, char *my_name, int my_prog, ++ int my_vers) ++{ ++ CLIENT *client; ++ sm_stat *result; ++ mon_id mon_id; ++ int err = 0; ++ ++ printf("Calling %s (as %s) to unmonitor %s\n", calling, my_name, ++ unmonitoring); ++ ++ if ((client = nsm_client_get_rpcclient(calling)) == NULL) ++ return 1; ++ ++ mon_id.my_id.my_name = my_name; ++ mon_id.my_id.my_prog = my_prog; ++ mon_id.my_id.my_vers = my_vers; ++ mon_id.my_id.my_proc = NLM_SM_NOTIFY; ++ mon_id.mon_name = unmonitoring; ++ ++ if (!(result = sm_unmon_1(&mon_id, client))) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_unmon_1")); ++ err = 1; ++ goto unmon_out; ++ } ++ ++ printf("SM_UNMON state: %d\n", result->state); ++ ++unmon_out: ++ clnt_destroy(client); ++ return err; ++} ++ ++static int ++nsm_client_unmon_all(char *calling, char *my_name, int my_prog, int my_vers) ++{ ++ CLIENT *client; ++ sm_stat *result; ++ my_id my_id; ++ int err = 0; ++ ++ printf("Calling %s (as %s) to unmonitor all hosts\n", calling, my_name); ++ ++ if ((client = nsm_client_get_rpcclient(calling)) == NULL) { ++ printf("RPC client creation failed\n"); ++ return 1; ++ } ++ ++ my_id.my_name = my_name; ++ my_id.my_prog = my_prog; ++ my_id.my_vers = my_vers; ++ my_id.my_proc = NLM_SM_NOTIFY; ++ ++ if (!(result = sm_unmon_all_1(&my_id, client))) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_unmon_all_1")); ++ err = 1; ++ goto unmon_all_out; ++ } ++ ++ printf("SM_UNMON_ALL state: %d\n", result->state); ++ ++unmon_all_out: ++ return err; ++} ++ ++static int ++nsm_client_crash(char *host) ++{ ++ CLIENT *client; ++ ++ if ((client = nsm_client_get_rpcclient(host)) == NULL) ++ return 1; ++ ++ if (!sm_simu_crash_1(NULL, client)) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_simu_crash_1")); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int ++nsm_client_stat(char *calling, char *monitoring) ++{ ++ CLIENT *client; ++ sm_name checking; ++ sm_stat_res *result; ++ ++ if ((client = nsm_client_get_rpcclient(calling)) == NULL) ++ return 1; ++ ++ checking.mon_name = monitoring; ++ ++ if (!(result = sm_stat_1(&checking, client))) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_stat_1")); ++ return 1; ++ } ++ ++ if (result->res_stat != stat_succ) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("stat_fail from %s for %s, state: %d\n", calling, ++ monitoring, result->state); ++ return 1; ++ } ++ ++ printf("stat_succ from %s for %s, state: %d\n", calling, ++ monitoring, result->state); ++ ++ return 0; ++} ++ ++static int ++nsm_client_notify(char *calling, char *mon_name, char *statestr) ++{ ++ CLIENT *client; ++ ++ stat_chge stat_chge = { .mon_name = mon_name }; ++ ++ stat_chge.state = atoi(statestr); ++ ++ if ((client = nsm_client_get_rpcclient(calling)) == NULL) ++ return 1; ++ ++ if (!sm_notify_1(&stat_chge, client)) { ++ fprintf(stderr, "RPC:%d\n", rpc_createerr.cf_stat); ++ printf("%s\n", clnt_sperror(client, "sm_notify_1")); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static void sim_killer(int sig) ++{ ++#ifdef HAVE_LIBTIRPC ++ (void) rpcb_unset(NLM_SM_PROG, NLM_SM_VERS4, NULL); ++#else ++ (void) pmap_unset(NLM_SM_PROG, NLM_SM_VERS4); ++#endif ++ exit(0); ++} ++ ++static void daemon_simulator(void) ++{ ++ signal(SIGHUP, sim_killer); ++ signal(SIGINT, sim_killer); ++ signal(SIGTERM, sim_killer); ++ /* FIXME: allow for different versions? */ ++ nfs_svc_create("nlmsim", NLM_SM_PROG, NLM_SM_VERS4, nlm_sm_prog_4, 0); ++ svc_run(); ++} ++ ++void *nlm_sm_notify_4_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp) ++{ ++ static char *result; ++ char priv[SM_PRIV_SIZE * 2 + 1]; ++ ++ bin2hex(priv, argp->priv, SM_PRIV_SIZE); ++ ++ printf("state=%d:mon_name=%s:private=%s\n", argp->state, ++ argp->mon_name, priv); ++ return (void *) &result; ++} ++ ++void *nlm_sm_notify_3_svc(struct nlm_sm_notify *argp, struct svc_req *rqstp) ++{ ++ return nlm_sm_notify_4_svc(argp, rqstp); ++} +diff --git a/tests/statdb_dump.c b/tests/statdb_dump.c +new file mode 100644 +index 0000000..92d63f2 +--- /dev/null ++++ b/tests/statdb_dump.c +@@ -0,0 +1,99 @@ ++/* ++ * statdb_dump.c -- dump contents of statd's monitor DB ++ * ++ * Copyright (C) 2010 Red Hat, Jeff Layton ++ * ++ * This program 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. ++ * ++ * This program 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 this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include ++#include ++#include ++ ++#include "nsm.h" ++#include "xlog.h" ++ ++static char cookiebuf[(SM_PRIV_SIZE * 2) + 1]; ++static char addrbuf[INET6_ADDRSTRLEN + 1]; ++ ++static unsigned int ++dump_host(const char *hostname, const struct sockaddr *sa, const struct mon *m, ++ const time_t timestamp) ++{ ++ int ret; ++ const char *addr; ++ const struct sockaddr_in *sin; ++ const struct sockaddr_in6 *sin6; ++ ++ ret = nsm_priv_to_hex(m->priv, cookiebuf, sizeof(cookiebuf)); ++ if (!ret) { ++ xlog(L_ERROR, "Unable to convert cookie to hex string.\n"); ++ return ret; ++ } ++ ++ switch (sa->sa_family) { ++ case AF_INET: ++ sin = (struct sockaddr_in *)(char *)sa; ++ addr = inet_ntop(sa->sa_family, &sin->sin_addr.s_addr, addrbuf, ++ (socklen_t)sizeof(addrbuf)); ++ break; ++ case AF_INET6: ++ sin6 = (struct sockaddr_in6 *)(char *)sa; ++ addr = inet_ntop(sa->sa_family, &sin6->sin6_addr, addrbuf, ++ (socklen_t)sizeof(addrbuf)); ++ break; ++ default: ++ xlog(L_ERROR, "Unrecognized address family: %hu\n", ++ sa->sa_family); ++ return 0; ++ } ++ ++ if (addr == NULL) { ++ xlog(L_ERROR, "Unable to convert sockaddr to string: %s\n", ++ strerror(errno)); ++ return 0; ++ } ++ ++ /* ++ * Callers of this program should assume that in the future, extra ++ * fields may be added to the output. Anyone adding extra fields to ++ * the output should add them to the end of the line. ++ */ ++ printf("%s %s %s %s %s %d %d %d\n", ++ hostname, addr, cookiebuf, ++ m->mon_id.mon_name, ++ m->mon_id.my_id.my_name, ++ m->mon_id.my_id.my_prog, ++ m->mon_id.my_id.my_vers, ++ m->mon_id.my_id.my_proc); ++ ++ return 1; ++} ++ ++int ++main(int argc, char **argv) ++{ ++ xlog_syslog(0); ++ xlog_stderr(1); ++ xlog_open(argv[0]); ++ ++ nsm_load_monitor_list(dump_host); ++ return 0; ++} +diff --git a/tests/t0001-statd-basic-mon-unmon.sh b/tests/t0001-statd-basic-mon-unmon.sh +new file mode 100644 +index 0000000..00127fb +--- /dev/null ++++ b/tests/t0001-statd-basic-mon-unmon.sh +@@ -0,0 +1,58 @@ ++#!/bin/bash ++# ++# statd_basic_mon_unmon -- test basic mon/unmon functionality with statd ++# ++# Copyright (C) 2010 Red Hat, Jeff Layton ++# ++# This program 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. ++# ++# This program 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 this program; if not, write to the Free Software Foundation, Inc., ++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++# ++ ++. ./test-lib.sh ++ ++# This test needs root privileges ++check_root ++ ++start_statd ++if [ $? -ne 0 ]; then ++ echo "FAIL: problem starting statd" ++ exit 1 ++fi ++ ++COOKIE=`echo $$ | md5sum | cut -d' ' -f1` ++MON_NAME=`hostname` ++ ++nsm_client mon $MON_NAME $COOKIE ++if [ $? -ne 0 ]; then ++ echo "FAIL: mon failed" ++ kill_statd ++ exit 1 ++fi ++ ++statdb_dump | grep $MON_NAME | grep -q $COOKIE ++if [ $? -ne 0 ]; then ++ echo "FAIL: monitor DB doesn't seem to contain entry" ++ kill_statd ++ exit 1 ++fi ++ ++nsm_client unmon $MON_NAME ++if [ $? -ne 0 ]; then ++ echo "FAIL: unmon failed" ++ kill_statd ++ exit 1 ++fi ++ ++kill_statd ++ +diff --git a/tests/test-lib.sh b/tests/test-lib.sh +new file mode 100644 +index 0000000..3d47264 +--- /dev/null ++++ b/tests/test-lib.sh +@@ -0,0 +1,60 @@ ++#!/bin/bash ++# ++# test-lib.sh -- library of functions for nfs-utils tests ++# ++# Copyright (C) 2010 Red Hat, Jeff Layton ++# ++# This program 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. ++# ++# This program 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 this program; if not, write to the Free Software Foundation, Inc., ++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++# ++ ++# make sure $srcdir is set and sanity check it ++srcdir=${srcdir-.} ++if [ ! -d ${srcdir} ]; then ++ echo "***ERROR***: bad installation -- \$srcdir=${srcdir}" ++ exit 1 ++fi ++ ++export PATH=$PATH:${srcdir}:${srcdir}/nsm_client ++ ++# Some tests require root privileges. Check for them and skip the test (exit 77) ++# if the caller doesn't have them. ++check_root() { ++ if [ $EUID -ne 0 ]; then ++ echo "*** Skipping this test as it requires root privs ***" ++ exit 77 ++ fi ++} ++ ++# is lockd registered as a service? ++lockd_registered() { ++ rpcinfo -p | grep -q nlockmgr ++ return $? ++} ++ ++# start up statd ++start_statd() { ++ rpcinfo -u 127.0.0.1 status 1 &> /dev/null ++ if [ $? -eq 0 ]; then ++ echo "***ERROR***: statd is already running and should " ++ echo " be down when starting this test" ++ return 1 ++ fi ++ $srcdir/../utils/statd/statd --no-notify ++} ++ ++# shut down statd ++kill_statd() { ++ kill `cat /var/run/rpc.statd.pid` ++} +diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c +index 593a8eb..331e57e 100644 +--- a/utils/exportfs/exportfs.c ++++ b/utils/exportfs/exportfs.c +@@ -13,6 +13,7 @@ + #endif + + #include ++#include + #include + #include + #include +diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c +index 40a2b4d..bd37a5f 100644 +--- a/utils/gssd/gssd.c ++++ b/utils/gssd/gssd.c +@@ -56,7 +56,6 @@ + #include "krb5_util.h" + + char pipefs_dir[PATH_MAX] = GSSD_PIPEFS_DIR; +-char pipefs_nfsdir[PATH_MAX] = GSSD_PIPEFS_DIR; + char keytabfile[PATH_MAX] = GSSD_DEFAULT_KEYTAB_FILE; + char ccachedir[PATH_MAX] = GSSD_DEFAULT_CRED_DIR; + char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1]; +@@ -159,11 +158,6 @@ main(int argc, char *argv[]) + if (preferred_realm == NULL) + gssd_k5_get_default_realm(&preferred_realm); + +- snprintf(pipefs_nfsdir, sizeof(pipefs_nfsdir), "%s/%s", +- pipefs_dir, GSSD_SERVICE_NAME); +- if (pipefs_nfsdir[sizeof(pipefs_nfsdir)-1] != '\0') +- errx(1, "pipefs_nfsdir path name too long"); +- + if ((progname = strrchr(argv[0], '/'))) + progname++; + else +diff --git a/utils/gssd/gssd.h b/utils/gssd/gssd.h +index 3c52f46..465c305 100644 +--- a/utils/gssd/gssd.h ++++ b/utils/gssd/gssd.h +@@ -60,7 +60,6 @@ enum {AUTHTYPE_KRB5, AUTHTYPE_SPKM3, AUTHTYPE_LIPKEY}; + + + extern char pipefs_dir[PATH_MAX]; +-extern char pipefs_nfsdir[PATH_MAX]; + extern char keytabfile[PATH_MAX]; + extern char *ccachesearch[]; + extern int use_memcache; +@@ -83,13 +82,24 @@ struct clnt_info { + int krb5_poll_index; + int spkm3_fd; + int spkm3_poll_index; ++ int gssd_fd; ++ int gssd_poll_index; + struct sockaddr_storage addr; + }; + ++TAILQ_HEAD(topdirs_list_head, topdirs_info) topdirs_list; ++ ++struct topdirs_info { ++ TAILQ_ENTRY(topdirs_info) list; ++ char *dirname; ++ int fd; ++}; ++ + void init_client_list(void); + int update_client_list(void); + void handle_krb5_upcall(struct clnt_info *clp); + void handle_spkm3_upcall(struct clnt_info *clp); ++void handle_gssd_upcall(struct clnt_info *clp); + int gssd_acquire_cred(char *server_name); + void gssd_run(void); + +diff --git a/utils/gssd/gssd_main_loop.c b/utils/gssd/gssd_main_loop.c +index 917b662..f1a68d3 100644 +--- a/utils/gssd/gssd_main_loop.c ++++ b/utils/gssd/gssd_main_loop.c +@@ -49,6 +49,7 @@ + #include + #include + #include ++#include + + #include "gssd.h" + #include "err_util.h" +@@ -73,6 +74,17 @@ scan_poll_results(int ret) + + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) + { ++ i = clp->gssd_poll_index; ++ if (i >= 0 && pollarray[i].revents) { ++ if (pollarray[i].revents & POLLHUP) ++ dir_changed = 1; ++ if (pollarray[i].revents & POLLIN) ++ handle_gssd_upcall(clp); ++ pollarray[clp->gssd_poll_index].revents = 0; ++ ret--; ++ if (!ret) ++ break; ++ } + i = clp->krb5_poll_index; + if (i >= 0 && pollarray[i].revents) { + if (pollarray[i].revents & POLLHUP) +@@ -98,12 +110,85 @@ scan_poll_results(int ret) + } + }; + ++static int ++topdirs_add_entry(struct dirent *dent) ++{ ++ struct topdirs_info *tdi; ++ ++ tdi = calloc(sizeof(struct topdirs_info), 1); ++ if (tdi == NULL) { ++ printerr(0, "ERROR: Couldn't allocate struct topdirs_info\n"); ++ return -1; ++ } ++ tdi->dirname = malloc(PATH_MAX); ++ if (tdi->dirname == NULL) { ++ printerr(0, "ERROR: Couldn't allocate directory name\n"); ++ free(tdi); ++ return -1; ++ } ++ snprintf(tdi->dirname, PATH_MAX, "%s/%s", pipefs_dir, dent->d_name); ++ tdi->fd = open(tdi->dirname, O_RDONLY); ++ if (tdi->fd != -1) { ++ fcntl(tdi->fd, F_SETSIG, DNOTIFY_SIGNAL); ++ fcntl(tdi->fd, F_NOTIFY, ++ DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT); ++ } ++ ++ TAILQ_INSERT_HEAD(&topdirs_list, tdi, list); ++ return 0; ++} ++ ++static void ++topdirs_free_list(void) ++{ ++ struct topdirs_info *tdi; ++ ++ TAILQ_FOREACH(tdi, &topdirs_list, list) { ++ free(tdi->dirname); ++ if (tdi->fd != -1) ++ close(tdi->fd); ++ TAILQ_REMOVE(&topdirs_list, tdi, list); ++ free(tdi); ++ } ++} ++ ++static int ++topdirs_init_list(void) ++{ ++ DIR *pipedir; ++ struct dirent *dent; ++ int ret; ++ ++ TAILQ_INIT(&topdirs_list); ++ ++ pipedir = opendir(pipefs_dir); ++ if (pipedir == NULL) { ++ printerr(0, "ERROR: could not open rpc_pipefs directory '%s': " ++ "%s\n", pipefs_dir, strerror(errno)); ++ return -1; ++ } ++ for (dent = readdir(pipedir); dent != NULL; dent = readdir(pipedir)) { ++ if (dent->d_type != DT_DIR || ++ strcmp(dent->d_name, ".") == 0 || ++ strcmp(dent->d_name, "..") == 0) { ++ continue; ++ } ++ ret = topdirs_add_entry(dent); ++ if (ret) ++ goto out_err; ++ } ++ closedir(pipedir); ++ return 0; ++out_err: ++ topdirs_free_list(); ++ return -1; ++} ++ + void + gssd_run() + { + int ret; + struct sigaction dn_act; +- int fd; + sigset_t set; + + /* Taken from linux/Documentation/dnotify.txt: */ +@@ -117,13 +202,8 @@ gssd_run() + sigaddset(&set, DNOTIFY_SIGNAL); + sigprocmask(SIG_UNBLOCK, &set, NULL); + +- if ((fd = open(pipefs_nfsdir, O_RDONLY)) == -1) { +- printerr(0, "ERROR: failed to open %s: %s\n", +- pipefs_nfsdir, strerror(errno)); +- exit(1); +- } +- fcntl(fd, F_SETSIG, DNOTIFY_SIGNAL); +- fcntl(fd, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MODIFY|DN_MULTISHOT); ++ if (topdirs_init_list() != 0) ++ return; + + init_client_list(); + +@@ -132,8 +212,7 @@ gssd_run() + while (dir_changed) { + dir_changed = 0; + if (update_client_list()) { +- printerr(0, "ERROR: couldn't update " +- "client list\n"); ++ /* Error msg is already printed */ + exit(1); + } + } +@@ -151,6 +230,7 @@ gssd_run() + scan_poll_results(ret); + } + } +- close(fd); ++ topdirs_free_list(); ++ + return; + } +diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c +index 37e2aa5..be4fb11 100644 +--- a/utils/gssd/gssd_proc.c ++++ b/utils/gssd/gssd_proc.c +@@ -73,6 +73,7 @@ + #include "krb5_util.h" + #include "context.h" + #include "nfsrpc.h" ++#include "nfslib.h" + + /* + * pollarray: +@@ -83,20 +84,22 @@ + * linked list of struct clnt_info which associates a clntXXX directory + * with an index into pollarray[], and other basic data about that client. + * +- * Directory structure: created by the kernel nfs client +- * {pipefs_nfsdir}/clntXX : one per rpc_clnt struct in the kernel +- * {pipefs_nfsdir}/clntXX/krb5 : read uid for which kernel wants ++ * Directory structure: created by the kernel ++ * {rpc_pipefs}/{dir}/clntXX : one per rpc_clnt struct in the kernel ++ * {rpc_pipefs}/{dir}/clntXX/krb5 : read uid for which kernel wants + * a context, write the resulting context +- * {pipefs_nfsdir}/clntXX/info : stores info such as server name ++ * {rpc_pipefs}/{dir}/clntXX/info : stores info such as server name ++ * {rpc_pipefs}/{dir}/clntXX/gssd : pipe for all gss mechanisms using ++ * a text-based string of parameters + * + * Algorithm: +- * Poll all {pipefs_nfsdir}/clntXX/krb5 files. When ready, data read +- * is a uid; performs rpcsec_gss context initialization protocol to ++ * Poll all {rpc_pipefs}/{dir}/clntXX/YYYY files. When data is ready, ++ * read and process; performs rpcsec_gss context initialization protocol to + * get a cred for that user. Writes result to corresponding krb5 file + * in a form the kernel code will understand. + * In addition, we make sure we are notified whenever anything is +- * created or destroyed in {pipefs_nfsdir} or in an of the clntXX directories, +- * and rescan the whole {pipefs_nfsdir} when this happens. ++ * created or destroyed in {rpc_pipefs} or in any of the clntXX directories, ++ * and rescan the whole {rpc_pipefs} when this happens. + */ + + struct pollfd * pollarray; +@@ -105,7 +108,7 @@ int pollsize; /* the size of pollaray (in pollfd's) */ + + /* + * convert a presentation address string to a sockaddr_storage struct. Returns +- * true on success and false on failure. ++ * true on success or false on failure. + * + * Note that we do not populate the sin6_scope_id field here for IPv6 addrs. + * gssd nececessarily relies on hostname resolution and DNS AAAA records +@@ -117,26 +120,43 @@ int pollsize; /* the size of pollaray (in pollfd's) */ + * not really feasible at present. + */ + static int +-addrstr_to_sockaddr(struct sockaddr *sa, const char *addr, const int port) ++addrstr_to_sockaddr(struct sockaddr *sa, const char *node, const char *port) + { +- struct sockaddr_in *s4 = (struct sockaddr_in *) sa; +-#ifdef IPV6_SUPPORTED +- struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) sa; +-#endif /* IPV6_SUPPORTED */ ++ int rc; ++ struct addrinfo *res; ++ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV }; + +- if (inet_pton(AF_INET, addr, &s4->sin_addr)) { +- s4->sin_family = AF_INET; +- s4->sin_port = htons(port); +-#ifdef IPV6_SUPPORTED +- } else if (inet_pton(AF_INET6, addr, &s6->sin6_addr)) { +- s6->sin6_family = AF_INET6; +- s6->sin6_port = htons(port); ++#ifndef IPV6_SUPPORTED ++ hints.ai_family = AF_INET; + #endif /* IPV6_SUPPORTED */ +- } else { +- printerr(0, "ERROR: unable to convert %s to address\n", addr); ++ ++ rc = getaddrinfo(node, port, &hints, &res); ++ if (rc) { ++ printerr(0, "ERROR: unable to convert %s|%s to sockaddr: %s\n", ++ node, port, rc == EAI_SYSTEM ? strerror(errno) : ++ gai_strerror(rc)); + return 0; + } + ++#ifdef IPV6_SUPPORTED ++ /* ++ * getnameinfo ignores the scopeid. If the address turns out to have ++ * a non-zero scopeid, we can't use it -- the resolved host might be ++ * completely different from the one intended. ++ */ ++ if (res->ai_addr->sa_family == AF_INET6) { ++ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr; ++ if (sin6->sin6_scope_id) { ++ printerr(0, "ERROR: address %s has non-zero " ++ "sin6_scope_id!\n", node); ++ freeaddrinfo(res); ++ return 0; ++ } ++ } ++#endif /* IPV6_SUPPORTED */ ++ ++ memcpy(sa, res->ai_addr, res->ai_addrlen); ++ freeaddrinfo(res); + return 1; + } + +@@ -194,11 +214,10 @@ read_service_info(char *info_file_name, char **servicename, char **servername, + char program[16]; + char version[16]; + char protoname[16]; +- char cb_port[128]; ++ char port[128]; + char *p; + int fd = -1; + int numfields; +- int port = 0; + + *servicename = *servername = *protocol = NULL; + +@@ -227,20 +246,22 @@ read_service_info(char *info_file_name, char **servicename, char **servername, + goto fail; + } + +- cb_port[0] = '\0'; ++ port[0] = '\0'; + if ((p = strstr(buf, "port")) != NULL) +- sscanf(p, "port: %127s\n", cb_port); ++ sscanf(p, "port: %127s\n", port); + + /* check service, program, and version */ +- if(memcmp(service, "nfs", 3)) return -1; ++ if (memcmp(service, "nfs", 3) != 0) ++ return -1; + *prog = atoi(program + 1); /* skip open paren */ + *vers = atoi(version); +- if((*prog != 100003) || ((*vers != 2) && (*vers != 3) && (*vers != 4))) +- goto fail; + +- if (cb_port[0] != '\0') { +- port = atoi(cb_port); +- if (port < 0 || port > 65535) ++ if (strlen(service) == 3 ) { ++ if ((*prog != 100003) || ((*vers != 2) && (*vers != 3) && ++ (*vers != 4))) ++ goto fail; ++ } else if (memcmp(service, "nfs4_cb", 7) == 0) { ++ if (*vers != 1) + goto fail; + } + +@@ -281,9 +302,13 @@ destroy_client(struct clnt_info *clp) + if (clp->spkm3_poll_index != -1) + memset(&pollarray[clp->spkm3_poll_index], 0, + sizeof(struct pollfd)); ++ if (clp->gssd_poll_index != -1) ++ memset(&pollarray[clp->gssd_poll_index], 0, ++ sizeof(struct pollfd)); + if (clp->dir_fd != -1) close(clp->dir_fd); + if (clp->krb5_fd != -1) close(clp->krb5_fd); + if (clp->spkm3_fd != -1) close(clp->spkm3_fd); ++ if (clp->gssd_fd != -1) close(clp->gssd_fd); + free(clp->dirname); + free(clp->servicename); + free(clp->servername); +@@ -303,8 +328,10 @@ insert_new_clnt(void) + } + clp->krb5_poll_index = -1; + clp->spkm3_poll_index = -1; ++ clp->gssd_poll_index = -1; + clp->krb5_fd = -1; + clp->spkm3_fd = -1; ++ clp->gssd_fd = -1; + clp->dir_fd = -1; + + TAILQ_INSERT_HEAD(&clnt_list, clp, list); +@@ -315,19 +342,43 @@ out: + static int + process_clnt_dir_files(struct clnt_info * clp) + { +- char kname[32]; +- char sname[32]; +- char info_file_name[32]; ++ char name[PATH_MAX]; ++ char gname[PATH_MAX]; ++ char info_file_name[PATH_MAX]; + +- if (clp->krb5_fd == -1) { +- snprintf(kname, sizeof(kname), "%s/krb5", clp->dirname); +- clp->krb5_fd = open(kname, O_RDWR); ++ if (clp->gssd_fd == -1) { ++ snprintf(gname, sizeof(gname), "%s/gssd", clp->dirname); ++ clp->gssd_fd = open(gname, O_RDWR); + } +- if (clp->spkm3_fd == -1) { +- snprintf(sname, sizeof(sname), "%s/spkm3", clp->dirname); +- clp->spkm3_fd = open(sname, O_RDWR); ++ if (clp->gssd_fd == -1) { ++ if (clp->krb5_fd == -1) { ++ snprintf(name, sizeof(name), "%s/krb5", clp->dirname); ++ clp->krb5_fd = open(name, O_RDWR); ++ } ++ if (clp->spkm3_fd == -1) { ++ snprintf(name, sizeof(name), "%s/spkm3", clp->dirname); ++ clp->spkm3_fd = open(name, O_RDWR); ++ } ++ ++ /* If we opened a gss-specific pipe, let's try opening ++ * the new upcall pipe again. If we succeed, close ++ * gss-specific pipe(s). ++ */ ++ if (clp->krb5_fd != -1 || clp->spkm3_fd != -1) { ++ clp->gssd_fd = open(gname, O_RDWR); ++ if (clp->gssd_fd != -1) { ++ if (clp->krb5_fd != -1) ++ close(clp->krb5_fd); ++ clp->krb5_fd = -1; ++ if (clp->spkm3_fd != -1) ++ close(clp->spkm3_fd); ++ clp->spkm3_fd = -1; ++ } ++ } + } +- if((clp->krb5_fd == -1) && (clp->spkm3_fd == -1)) ++ ++ if ((clp->krb5_fd == -1) && (clp->spkm3_fd == -1) && ++ (clp->gssd_fd == -1)) + return -1; + snprintf(info_file_name, sizeof(info_file_name), "%s/info", + clp->dirname); +@@ -362,6 +413,15 @@ get_poll_index(int *ind) + static int + insert_clnt_poll(struct clnt_info *clp) + { ++ if ((clp->gssd_fd != -1) && (clp->gssd_poll_index == -1)) { ++ if (get_poll_index(&clp->gssd_poll_index)) { ++ printerr(0, "ERROR: Too many gssd clients\n"); ++ return -1; ++ } ++ pollarray[clp->gssd_poll_index].fd = clp->gssd_fd; ++ pollarray[clp->gssd_poll_index].events |= POLLIN; ++ } ++ + if ((clp->krb5_fd != -1) && (clp->krb5_poll_index == -1)) { + if (get_poll_index(&clp->krb5_poll_index)) { + printerr(0, "ERROR: Too many krb5 clients\n"); +@@ -384,17 +444,18 @@ insert_clnt_poll(struct clnt_info *clp) + } + + static void +-process_clnt_dir(char *dir) ++process_clnt_dir(char *dir, char *pdir) + { + struct clnt_info * clp; + + if (!(clp = insert_new_clnt())) + goto fail_destroy_client; + +- if (!(clp->dirname = calloc(strlen(dir) + 1, 1))) { ++ /* An extra for the '/', and an extra for the null */ ++ if (!(clp->dirname = calloc(strlen(dir) + strlen(pdir) + 2, 1))) { + goto fail_destroy_client; + } +- memcpy(clp->dirname, dir, strlen(dir)); ++ sprintf(clp->dirname, "%s/%s", pdir, dir); + if ((clp->dir_fd = open(clp->dirname, O_RDONLY)) == -1) { + printerr(0, "ERROR: can't open %s: %s\n", + clp->dirname, strerror(errno)); +@@ -438,16 +499,24 @@ init_client_list(void) + * directories, since the DNOTIFY could have been in there. + */ + static void +-update_old_clients(struct dirent **namelist, int size) ++update_old_clients(struct dirent **namelist, int size, char *pdir) + { + struct clnt_info *clp; + void *saveprev; + int i, stillhere; ++ char fname[PATH_MAX]; + + for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { ++ /* only compare entries in the global list that are from the ++ * same pipefs parent directory as "pdir" ++ */ ++ if (strncmp(clp->dirname, pdir, strlen(pdir)) != 0) continue; ++ + stillhere = 0; + for (i=0; i < size; i++) { +- if (!strcmp(clp->dirname, namelist[i]->d_name)) { ++ snprintf(fname, sizeof(fname), "%s/%s", ++ pdir, namelist[i]->d_name); ++ if (strcmp(clp->dirname, fname) == 0) { + stillhere = 1; + break; + } +@@ -468,48 +537,69 @@ update_old_clients(struct dirent **namelist, int size) + + /* Search for a client by directory name, return 1 if found, 0 otherwise */ + static int +-find_client(char *dirname) ++find_client(char *dirname, char *pdir) + { + struct clnt_info *clp; ++ char fname[PATH_MAX]; + +- for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) +- if (!strcmp(clp->dirname, dirname)) ++ for (clp = clnt_list.tqh_first; clp != NULL; clp = clp->list.tqe_next) { ++ snprintf(fname, sizeof(fname), "%s/%s", pdir, dirname); ++ if (strcmp(clp->dirname, fname) == 0) + return 1; ++ } + return 0; + } + +-/* Used to read (and re-read) list of clients, set up poll array. */ +-int +-update_client_list(void) ++static int ++process_pipedir(char *pipe_name) + { + struct dirent **namelist; + int i, j; + +- if (chdir(pipefs_nfsdir) < 0) { ++ if (chdir(pipe_name) < 0) { + printerr(0, "ERROR: can't chdir to %s: %s\n", +- pipefs_nfsdir, strerror(errno)); ++ pipe_name, strerror(errno)); + return -1; + } + +- j = scandir(pipefs_nfsdir, &namelist, NULL, alphasort); ++ j = scandir(pipe_name, &namelist, NULL, alphasort); + if (j < 0) { + printerr(0, "ERROR: can't scandir %s: %s\n", +- pipefs_nfsdir, strerror(errno)); ++ pipe_name, strerror(errno)); + return -1; + } +- update_old_clients(namelist, j); ++ ++ update_old_clients(namelist, j, pipe_name); + for (i=0; i < j; i++) { + if (i < FD_ALLOC_BLOCK + && !strncmp(namelist[i]->d_name, "clnt", 4) +- && !find_client(namelist[i]->d_name)) +- process_clnt_dir(namelist[i]->d_name); ++ && !find_client(namelist[i]->d_name, pipe_name)) ++ process_clnt_dir(namelist[i]->d_name, pipe_name); + free(namelist[i]); + } + + free(namelist); ++ + return 0; + } + ++/* Used to read (and re-read) list of clients, set up poll array. */ ++int ++update_client_list(void) ++{ ++ int retval = -1; ++ struct topdirs_info *tdi; ++ ++ TAILQ_FOREACH(tdi, &topdirs_list, list) { ++ retval = process_pipedir(tdi->dirname); ++ if (retval) ++ printerr(1, "WARNING: error processing %s\n", ++ tdi->dirname); ++ ++ } ++ return retval; ++} ++ + static int + do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd, + gss_buffer_desc *context_token) +@@ -798,15 +888,14 @@ int create_auth_rpc_client(struct clnt_info *clp, + goto out; + } + +- + /* + * this code uses the userland rpcsec gss library to create a krb5 + * context on behalf of the kernel + */ +-void +-handle_krb5_upcall(struct clnt_info *clp) ++static void ++process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname, ++ char *service) + { +- uid_t uid; + CLIENT *rpc_clnt = NULL; + AUTH *auth = NULL; + struct authgss_private_data pd; +@@ -815,23 +904,51 @@ handle_krb5_upcall(struct clnt_info *clp) + char **ccname; + char **dirname; + int create_resp = -1; ++ int err, downcall_err = -EACCES; + +- printerr(1, "handling krb5 upcall\n"); ++ printerr(1, "handling krb5 upcall (%s)\n", clp->dirname); + ++ if (tgtname) { ++ if (clp->servicename) { ++ free(clp->servicename); ++ clp->servicename = strdup(tgtname); ++ } ++ } + token.length = 0; + token.value = NULL; + memset(&pd, 0, sizeof(struct authgss_private_data)); + +- if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) { +- printerr(0, "WARNING: failed reading uid from krb5 " +- "upcall pipe: %s\n", strerror(errno)); +- goto out; +- } +- +- if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) { ++ /* ++ * If "service" is specified, then the kernel is indicating that ++ * we must use machine credentials for this request. (Regardless ++ * of the uid value or the setting of root_uses_machine_creds.) ++ * If the service value is "*", then any service name can be used. ++ * Otherwise, it specifies the service name that should be used. ++ * (For now, the values of service will only be "*" or "nfs".) ++ * ++ * Restricting gssd to use "nfs" service name is needed for when ++ * the NFS server is doing a callback to the NFS client. In this ++ * case, the NFS server has to authenticate itself as "nfs" -- ++ * even if there are other service keys such as "host" or "root" ++ * in the keytab. ++ * ++ * Another case when the kernel may specify the service attribute ++ * is when gssd is being asked to create the context for a ++ * SETCLIENT_ID operation. In this case, machine credentials ++ * must be used for the authentication. However, the service name ++ * used for this case is not important. ++ * ++ */ ++ printerr(2, "%s: service is '%s'\n", __func__, ++ service ? service : ""); ++ if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 && ++ service == NULL)) { + /* Tell krb5 gss which credentials cache to use */ + for (dirname = ccachesearch; *dirname != NULL; dirname++) { +- if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0) ++ err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname); ++ if (err == -EKEYEXPIRED) ++ downcall_err = -EKEYEXPIRED; ++ else if (!err) + create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, + AUTHTYPE_KRB5); + if (create_resp == 0) +@@ -839,12 +956,13 @@ handle_krb5_upcall(struct clnt_info *clp) + } + } + if (create_resp != 0) { +- if (uid == 0 && root_uses_machine_creds == 1) { ++ if (uid == 0 && (root_uses_machine_creds == 1 || ++ service != NULL)) { + int nocache = 0; + int success = 0; + do { + gssd_refresh_krb5_machine_credential(clp->servername, +- NULL, nocache); ++ NULL, service); + /* + * Get a list of credential cache names and try each + * of them until one works or we've tried them all +@@ -904,7 +1022,7 @@ handle_krb5_upcall(struct clnt_info *clp) + goto out_return_error; + } + +- do_downcall(clp->krb5_fd, uid, &pd, &token); ++ do_downcall(fd, uid, &pd, &token); + + out: + if (token.value) +@@ -920,7 +1038,7 @@ out: + return; + + out_return_error: +- do_error_downcall(clp->krb5_fd, uid, -1); ++ do_error_downcall(fd, uid, downcall_err); + goto out; + } + +@@ -928,26 +1046,19 @@ out_return_error: + * this code uses the userland rpcsec gss library to create an spkm3 + * context on behalf of the kernel + */ +-void +-handle_spkm3_upcall(struct clnt_info *clp) ++static void ++process_spkm3_upcall(struct clnt_info *clp, uid_t uid, int fd) + { +- uid_t uid; + CLIENT *rpc_clnt = NULL; + AUTH *auth = NULL; + struct authgss_private_data pd; + gss_buffer_desc token; + +- printerr(2, "handling spkm3 upcall\n"); ++ printerr(2, "handling spkm3 upcall (%s)\n", clp->dirname); + + token.length = 0; + token.value = NULL; + +- if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) { +- printerr(0, "WARNING: failed reading uid from spkm3 " +- "upcall pipe: %s\n", strerror(errno)); +- goto out; +- } +- + if (create_auth_rpc_client(clp, &rpc_clnt, &auth, uid, AUTHTYPE_SPKM3)) { + printerr(0, "WARNING: Failed to create spkm3 context for " + "user with uid %d\n", uid); +@@ -968,7 +1079,7 @@ handle_spkm3_upcall(struct clnt_info *clp) + goto out_return_error; + } + +- do_downcall(clp->spkm3_fd, uid, &pd, &token); ++ do_downcall(fd, uid, &pd, &token); + + out: + if (token.value) +@@ -980,6 +1091,139 @@ out: + return; + + out_return_error: +- do_error_downcall(clp->spkm3_fd, uid, -1); ++ do_error_downcall(fd, uid, -1); + goto out; + } ++ ++void ++handle_krb5_upcall(struct clnt_info *clp) ++{ ++ uid_t uid; ++ ++ if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) { ++ printerr(0, "WARNING: failed reading uid from krb5 " ++ "upcall pipe: %s\n", strerror(errno)); ++ return; ++ } ++ ++ return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL); ++} ++ ++void ++handle_spkm3_upcall(struct clnt_info *clp) ++{ ++ uid_t uid; ++ ++ if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) { ++ printerr(0, "WARNING: failed reading uid from spkm3 " ++ "upcall pipe: %s\n", strerror(errno)); ++ return; ++ } ++ ++ return process_spkm3_upcall(clp, uid, clp->spkm3_fd); ++} ++ ++void ++handle_gssd_upcall(struct clnt_info *clp) ++{ ++ uid_t uid; ++ char *lbuf = NULL; ++ int lbuflen = 0; ++ char *p; ++ char *mech = NULL; ++ char *target = NULL; ++ char *service = NULL; ++ ++ printerr(1, "handling gssd upcall (%s)\n", clp->dirname); ++ ++ if (readline(clp->gssd_fd, &lbuf, &lbuflen) != 1) { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed reading request\n"); ++ return; ++ } ++ printerr(2, "%s: '%s'\n", __func__, lbuf); ++ ++ /* find the mechanism name */ ++ if ((p = strstr(lbuf, "mech=")) != NULL) { ++ mech = malloc(lbuflen); ++ if (!mech) ++ goto out; ++ if (sscanf(p, "mech=%s", mech) != 1) { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to parse gss mechanism name " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ } else { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to find gss mechanism name " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ ++ /* read uid */ ++ if ((p = strstr(lbuf, "uid=")) != NULL) { ++ if (sscanf(p, "uid=%d", &uid) != 1) { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to parse uid " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ } else { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to find uid " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ ++ /* read target name */ ++ if ((p = strstr(lbuf, "target=")) != NULL) { ++ target = malloc(lbuflen); ++ if (!target) ++ goto out; ++ if (sscanf(p, "target=%s", target) != 1) { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to parse target name " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ } ++ ++ /* ++ * read the service name ++ * ++ * The presence of attribute "service=" indicates that machine ++ * credentials should be used for this request. If the value ++ * is "*", then any machine credentials available can be used. ++ * If the value is anything else, then machine credentials for ++ * the specified service name (always "nfs" for now) should be ++ * used. ++ */ ++ if ((p = strstr(lbuf, "service=")) != NULL) { ++ service = malloc(lbuflen); ++ if (!service) ++ goto out; ++ if (sscanf(p, "service=%s", service) != 1) { ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "failed to parse service type " ++ "in upcall string '%s'\n", lbuf); ++ goto out; ++ } ++ } ++ ++ if (strcmp(mech, "krb5") == 0) ++ process_krb5_upcall(clp, uid, clp->gssd_fd, target, service); ++ else if (strcmp(mech, "spkm3") == 0) ++ process_spkm3_upcall(clp, uid, clp->gssd_fd); ++ else ++ printerr(0, "WARNING: handle_gssd_upcall: " ++ "received unknown gss mech '%s'\n", mech); ++ ++out: ++ free(lbuf); ++ free(mech); ++ free(target); ++ free(service); ++ return; ++} ++ +diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c +index 78e9775..1295f57 100644 +--- a/utils/gssd/krb5_util.c ++++ b/utils/gssd/krb5_util.c +@@ -170,9 +170,8 @@ select_krb5_ccache(const struct dirent *d) + * what we want. Otherwise, return zero and no dirent pointer. + * The caller is responsible for freeing the dirent if one is returned. + * +- * Returns: +- * 0 => could not find an existing entry +- * 1 => found an existing entry ++ * Returns 0 if a valid-looking entry was found and a non-zero error ++ * code otherwise. + */ + static int + gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) +@@ -186,7 +185,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) + char buf[1030]; + char *princname = NULL; + char *realm = NULL; +- int score, best_match_score = 0; ++ int score, best_match_score = 0, err = -EACCES; + + memset(&best_match_stat, 0, sizeof(best_match_stat)); + *d = NULL; +@@ -229,6 +228,7 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) + printerr(3, "CC file '%s' is expired or corrupt\n", + statname); + free(namelist[i]); ++ err = -EKEYEXPIRED; + continue; + } + +@@ -284,11 +284,12 @@ gssd_find_existing_krb5_ccache(uid_t uid, char *dirname, struct dirent **d) + } + free(namelist); + } +- if (found) +- { ++ if (found) { + *d = best_match_dir; ++ return 0; + } +- return found; ++ ++ return err; + } + + +@@ -797,10 +798,9 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, + */ + static int + find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, +- krb5_keytab_entry *kte) ++ krb5_keytab_entry *kte, const char **svcnames) + { + krb5_error_code code; +- const char *svcnames[] = { "root", "nfs", "host", NULL }; + char **realmnames = NULL; + char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST]; + int i, j, retval; +@@ -1025,29 +1025,29 @@ err_cache: + * given only a UID. We really need more information, but we + * do the best we can. + * +- * Returns: +- * 0 => a ccache was found +- * 1 => no ccache was found ++ * Returns 0 if a ccache was found, and a non-zero error code otherwise. + */ + int + gssd_setup_krb5_user_gss_ccache(uid_t uid, char *servername, char *dirname) + { + char buf[MAX_NETOBJ_SZ]; + struct dirent *d; ++ int err; + + printerr(2, "getting credentials for client with uid %u for " + "server %s\n", uid, servername); + memset(buf, 0, sizeof(buf)); +- if (gssd_find_existing_krb5_ccache(uid, dirname, &d)) { +- snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name); +- free(d); +- } +- else +- return 1; ++ err = gssd_find_existing_krb5_ccache(uid, dirname, &d); ++ if (err) ++ return err; ++ ++ snprintf(buf, sizeof(buf), "FILE:%s/%s", dirname, d->d_name); ++ free(d); ++ + printerr(2, "using %s as credentials cache for client with " + "uid %u for server %s\n", buf, uid, servername); + gssd_set_krb5_ccache_name(buf); +- return 0; ++ return err; + } + + /* +@@ -1096,7 +1096,8 @@ gssd_get_krb5_machine_cred_list(char ***list) + for (ple = gssd_k5_kt_princ_list; ple; ple = ple->next) { + if (ple->ccname) { + /* Make sure cred is up-to-date before returning it */ +- retval = gssd_refresh_krb5_machine_credential(NULL, ple, 0); ++ retval = gssd_refresh_krb5_machine_credential(NULL, ple, ++ NULL); + if (retval) + continue; + if (i + 1 > listsize) { +@@ -1186,14 +1187,24 @@ gssd_destroy_krb5_machine_creds(void) + */ + int + gssd_refresh_krb5_machine_credential(char *hostname, +- struct gssd_k5_kt_princ *ple, int nocache) ++ struct gssd_k5_kt_princ *ple, ++ char *service) + { + krb5_error_code code = 0; + krb5_context context; + krb5_keytab kt = NULL;; + int retval = 0; + char *k5err = NULL; ++ const char *svcnames[4] = { "root", "nfs", "host", NULL }; + ++ /* ++ * If a specific service name was specified, use it. ++ * Otherwise, use the default list. ++ */ ++ if (service != NULL && strcmp(service, "*") != 0) { ++ svcnames[0] = service; ++ svcnames[1] = NULL; ++ } + if (hostname == NULL && ple == NULL) + return EINVAL; + +@@ -1216,7 +1227,7 @@ gssd_refresh_krb5_machine_credential(char *hostname, + if (ple == NULL) { + krb5_keytab_entry kte; + +- code = find_keytab_entry(context, kt, hostname, &kte); ++ code = find_keytab_entry(context, kt, hostname, &kte, svcnames); + if (code) { + printerr(0, "ERROR: %s: no usable keytab entry found " + "in keytab %s for connection with host %s\n", +@@ -1241,7 +1252,7 @@ gssd_refresh_krb5_machine_credential(char *hostname, + goto out; + } + } +- retval = gssd_get_single_krb5_cred(context, kt, ple, nocache); ++ retval = gssd_get_single_krb5_cred(context, kt, ple, 0); + out: + if (kt) + krb5_kt_close(context, kt); +diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h +index 4b6b281..4602cc3 100644 +--- a/utils/gssd/krb5_util.h ++++ b/utils/gssd/krb5_util.h +@@ -30,7 +30,8 @@ void gssd_free_krb5_machine_cred_list(char **list); + void gssd_setup_krb5_machine_gss_ccache(char *servername); + void gssd_destroy_krb5_machine_creds(void); + int gssd_refresh_krb5_machine_credential(char *hostname, +- struct gssd_k5_kt_princ *ple, int nocache); ++ struct gssd_k5_kt_princ *ple, ++ char *service); + char *gssd_k5_err_msg(krb5_context context, krb5_error_code code); + void gssd_k5_get_default_realm(char **def_realm); + +diff --git a/utils/gssd/svcgssd_proc.c b/utils/gssd/svcgssd_proc.c +index 6f2ba61..f1bfbef 100644 +--- a/utils/gssd/svcgssd_proc.c ++++ b/utils/gssd/svcgssd_proc.c +@@ -56,6 +56,7 @@ + #include "gss_util.h" + #include "err_util.h" + #include "context.h" ++#include "gss_oids.h" + + extern char * mech2file(gss_OID mech); + #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel" +@@ -73,7 +74,7 @@ struct svc_cred { + static int + do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, + gss_OID mech, gss_buffer_desc *context_token, +- int32_t endtime) ++ int32_t endtime, char *client_name) + { + FILE *f; + int i; +@@ -98,9 +99,10 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, + qword_printint(f, cred->cr_gid); + qword_printint(f, cred->cr_ngroups); + printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), " +- "uid: %d, gid: %d, num aux grps: %d:\n", ++ "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n", + fname, out_handle->length, context_token->length, + endtime, endtime - time(0), ++ client_name ? client_name : "", + cred->cr_uid, cred->cr_gid, cred->cr_ngroups); + for (i=0; i < cred->cr_ngroups; i++) { + qword_printint(f, cred->cr_groups[i]); +@@ -108,6 +110,8 @@ do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, + } + qword_print(f, fname); + qword_printhex(f, context_token->value, context_token->length); ++ if (client_name) ++ qword_print(f, client_name); + err = qword_eol(f); + if (err) { + printerr(1, "WARNING: error writing to downcall channel " +@@ -307,6 +311,75 @@ print_hexl(const char *description, unsigned char *cp, int length) + } + #endif + ++static int ++get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name) ++{ ++ char *p, *sname = NULL; ++ if (strchr(name->value, '@') && strchr(name->value, '/')) { ++ if ((sname = calloc(name->length, 1)) == NULL) { ++ printerr(0, "ERROR: get_krb5_hostbased_name failed " ++ "to allocate %d bytes\n", name->length); ++ return -1; ++ } ++ /* read in name and instance and replace '/' with '@' */ ++ sscanf(name->value, "%[^@]", sname); ++ p = strrchr(sname, '/'); ++ if (p == NULL) { /* The '@' preceeded the '/' */ ++ free(sname); ++ return -1; ++ } ++ *p = '@'; ++ } ++ *hostbased_name = sname; ++ return 0; ++} ++ ++static int ++get_hostbased_client_name(gss_name_t client_name, gss_OID mech, ++ char **hostbased_name) ++{ ++ u_int32_t maj_stat, min_stat; ++ gss_buffer_desc name; ++ gss_OID name_type = GSS_C_NO_OID; ++ char *cname; ++ int res = -1; ++ ++ *hostbased_name = NULL; /* preset in case we fail */ ++ ++ /* Get the client's gss authenticated name */ ++ maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); ++ if (maj_stat != GSS_S_COMPLETE) { ++ pgsserr("get_hostbased_client_name: gss_display_name", ++ maj_stat, min_stat, mech); ++ goto out_err; ++ } ++ if (name.length >= 0xffff) { /* don't overflow */ ++ printerr(0, "ERROR: get_hostbased_client_name: " ++ "received gss_name is too long (%d bytes)\n", ++ name.length); ++ goto out_rel_buf; ++ } ++ ++ /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to ++ * an NT_HOSTBASED_SERVICE name */ ++ if (g_OID_equal(&krb5oid, mech)) { ++ if (get_krb5_hostbased_name(&name, &cname) == 0) ++ *hostbased_name = cname; ++ } ++ ++ /* No support for SPKM3, just print a warning (for now) */ ++ if (g_OID_equal(&spkm3oid, mech)) { ++ printerr(1, "WARNING: get_hostbased_client_name: " ++ "no hostbased_name support for SPKM3\n"); ++ } ++ ++ res = 0; ++out_rel_buf: ++ gss_release_buffer(&min_stat, &name); ++out_err: ++ return res; ++} ++ + void + handle_nullreq(FILE *f) { + /* XXX initialize to a random integer to reduce chances of unnecessary +@@ -325,7 +398,7 @@ handle_nullreq(FILE *f) { + null_token = {.value = NULL}; + u_int32_t ret_flags; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; +- gss_name_t client_name; ++ gss_name_t client_name = NULL; + gss_OID mech = GSS_C_NO_OID; + u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0; + u_int32_t ignore_min_stat; +@@ -334,6 +407,7 @@ handle_nullreq(FILE *f) { + static int lbuflen = 0; + static char *cp; + int32_t ctx_endtime; ++ char *hostbased_name = NULL; + + printerr(1, "handling null request\n"); + +@@ -396,11 +470,13 @@ handle_nullreq(FILE *f) { + if (get_ids(client_name, mech, &cred)) { + /* get_ids() prints error msg */ + maj_stat = GSS_S_BAD_NAME; /* XXX ? */ +- gss_release_name(&ignore_min_stat, &client_name); + goto out_err; + } +- gss_release_name(&ignore_min_stat, &client_name); +- ++ if (get_hostbased_client_name(client_name, mech, &hostbased_name)) { ++ /* get_hostbased_client_name() prints error msg */ ++ maj_stat = GSS_S_BAD_NAME; /* XXX ? */ ++ goto out_err; ++ } + + /* Context complete. Pass handle_seq in out_handle to use + * for context lookup in the kernel. */ +@@ -419,7 +495,8 @@ handle_nullreq(FILE *f) { + /* We no longer need the gss context */ + gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); + +- do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime); ++ do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime, ++ hostbased_name); + continue_needed: + send_response(f, &in_handle, &in_tok, maj_stat, min_stat, + &out_handle, &out_tok); +@@ -428,6 +505,9 @@ out: + free(ctx_token.value); + if (out_tok.value != NULL) + gss_release_buffer(&ignore_min_stat, &out_tok); ++ if (client_name) ++ gss_release_name(&ignore_min_stat, &client_name); ++ free(hostbased_name); + printerr(1, "finished handling null request\n"); + return; + +diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am +index 4dabb3d..4218048 100644 +--- a/utils/idmapd/Makefile.am ++++ b/utils/idmapd/Makefile.am +@@ -1,6 +1,5 @@ + ## Process this file with automake to produce Makefile.in + +-man5_MANS = idmapd.conf.man + man8_MANS = idmapd.man + + RPCPREFIX = rpc. +@@ -8,7 +7,6 @@ KPREFIX = @kprefix@ + sbin_PROGRAMS = idmapd + + EXTRA_DIST = \ +- $(man5_MANS) \ + $(man8_MANS) \ + idmapd.conf + +@@ -48,8 +46,8 @@ uninstall-hook: + + # XXX This makes some assumptions about what automake does. + # XXX But there is no install-man-hook or install-man-local. +-install-man: install-man5 install-man8 install-man-links +-uninstall-man: uninstall-man5 uninstall-man8 uninstall-man-links ++install-man: install-man8 install-man-links ++uninstall-man: uninstall-man8 uninstall-man-links + + install-man-links: + (cd $(DESTDIR)$(man8dir) && \ +diff --git a/utils/idmapd/idmapd.conf.man b/utils/idmapd/idmapd.conf.man +deleted file mode 100644 +index 02722b1..0000000 +--- a/utils/idmapd/idmapd.conf.man ++++ /dev/null +@@ -1,74 +0,0 @@ +-.\" $OpenBSD: mdoc.template,v 1.6 2001/02/03 08:22:44 niklas Exp $ +-.\" +-.\" The following requests are required for all man pages. +-.Dd July 16, 2003 +-.Dt idmapd.conf 5 +-.Os +-.Sh NAME +-.Nm idmapd.conf +-.Nd configuration file for idmapd, the NFSv4 ID <-> Name Mapper +-.Sh SYNOPSIS +-Configuration file for idmapd, the NFSv4 ID <-> Name Mapper +-.Sh DESCRIPTION +-The +-.Nm +-configuration file has two sections, initiated by the strings +-[General] and [Mapping]. Each section may contain lines of the form +-.Dl "" +-.Dl variable = value +-.Dl "" +-The variables allowed in the General section are +-.Va Verbosity, +-.Va Pipefs-Directory, +-and +-.Va Domain, +-whose values have the same effect as the arguments to the +-.Fl v, +-.Fl p, +-and +-.Fl d +-commandline options, respectively. The variables allowed in the +-Mapping section are +-.Va Nobody-User +-and +-.Va Nobody-Group, +-which have the same effect as the +-.Fl U +-and +-.Fl G +-commandline options. +-' +-.Sh EXAMPLES +-' +-An example +-.Pa /etc/idmapd.conf +-file: +-' +-.Bd -literal +-[General] +- +-Verbosity = 0 +-Pipefs-Directory = /var/lib/nfs/rpc_pipefs +-Domain = localdomain +- +-[Mapping] +- +-Nobody-User = nobody +-Nobody-Group = nobody +-.Ed +-' +-.Sh SEE ALSO +-.Xr idmapd 8 +-.\".Sh SEE ALSO +-.\".Xr nylon.conf 4 +-.\" .Sh COMPATIBILITY +-.\".Sh STANDARDS +-.\".Sh ACKNOWLEDGEMENTS +-.Sh AUTHORS +-The idmapd software has been developed by Marius Aamodt Eriksen +-.Aq marius@citi.umich.edu . +-.\" .Sh HISTORY +-.\".Sh BUGS +-.\"Please report any bugs to Marius Aamodt Eriksen +-.\".Aq marius@monkey.org . +-.\" .Sh CAVEATS +diff --git a/utils/mount/configfile.c b/utils/mount/configfile.c +index 28b722c..1dd4159 100644 +--- a/utils/mount/configfile.c ++++ b/utils/mount/configfile.c +@@ -194,13 +194,29 @@ void free_all(void) + static char *versions[] = {"v2", "v3", "v4", "vers", "nfsvers", NULL}; + int inline check_vers(char *mopt, char *field) + { +- int i; ++ int i, found=0; + +- if (strncmp("mountvers", field, strlen("mountvers")) != 0) { +- for (i=0; versions[i]; i++) +- if (strcasestr(mopt, versions[i]) != NULL) +- return 1; ++ /* ++ * First check to see if the config setting is one ++ * of the many version settings ++ */ ++ for (i=0; versions[i]; i++) { ++ if (strcasestr(field, versions[i]) != NULL) { ++ found++; ++ break; ++ } + } ++ if (!found) ++ return 0; ++ /* ++ * It appears the version is being set, now see ++ * if the version appears on the command ++ */ ++ for (i=0; versions[i]; i++) { ++ if (strcasestr(mopt, versions[i]) != NULL) ++ return 1; ++ } ++ + return 0; + } + +diff --git a/utils/mount/mount.c b/utils/mount/mount.c +index 355df79..82b9169 100644 +--- a/utils/mount/mount.c ++++ b/utils/mount/mount.c +@@ -24,6 +24,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -593,6 +594,9 @@ int main(int argc, char *argv[]) + if (mnt_err == EX_BG) { + printf(_("%s: backgrounding \"%s\"\n"), + progname, spec); ++ printf(_("%s: mount options: \"%s\"\n"), ++ progname, extra_opts); ++ + fflush(stdout); + + /* +diff --git a/utils/mount/network.c b/utils/mount/network.c +index 7b1152a..06cab6a 100644 +--- a/utils/mount/network.c ++++ b/utils/mount/network.c +@@ -37,11 +37,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + ++#include "sockaddr.h" + #include "xcommon.h" + #include "mount.h" + #include "nls.h" +@@ -56,10 +58,6 @@ + #define CONNECT_TIMEOUT (20) + #define MOUNT_TIMEOUT (30) + +-#if SIZEOF_SOCKLEN_T - 0 == 0 +-#define socklen_t unsigned int +-#endif +- + extern int nfs_mount_data_version; + extern char *progname; + extern int verbose; +@@ -193,8 +191,18 @@ static const unsigned int *nfs_default_proto() + } + #endif /* MOUNT_CONFIG */ + +-static int nfs_lookup(const char *hostname, const sa_family_t family, +- struct sockaddr *sap, socklen_t *salen) ++/** ++ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address ++ * @hostname: pointer to C string containing DNS hostname to resolve ++ * @family: address family hint ++ * @sap: pointer to buffer to fill with socket address ++ * @len: IN: size of buffer to fill; OUT: size of socket address ++ * ++ * Returns 1 and places a socket address at @sap if successful; ++ * otherwise zero. ++ */ ++int nfs_lookup(const char *hostname, const sa_family_t family, ++ struct sockaddr *sap, socklen_t *salen) + { + struct addrinfo *gai_results; + struct addrinfo gai_hint = { +@@ -243,25 +251,6 @@ static int nfs_lookup(const char *hostname, const sa_family_t family, + } + + /** +- * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address +- * @hostname: pointer to C string containing DNS hostname to resolve +- * @sap: pointer to buffer to fill with socket address +- * @len: IN: size of buffer to fill; OUT: size of socket address +- * +- * Returns 1 and places a socket address at @sap if successful; +- * otherwise zero. +- */ +-int nfs_name_to_address(const char *hostname, +- struct sockaddr *sap, socklen_t *salen) +-{ +-#ifdef IPV6_SUPPORTED +- return nfs_lookup(hostname, AF_UNSPEC, sap, salen); +-#else /* !IPV6_SUPPORTED */ +- return nfs_lookup(hostname, AF_INET, sap, salen); +-#endif /* !IPV6_SUPPORTED */ +-} +- +-/** + * nfs_gethostbyname - resolve a hostname to an IPv4 address + * @hostname: pointer to a C string containing a DNS hostname + * @sin: returns an IPv4 address +@@ -283,8 +272,8 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) + * OUT: length of converted socket address + * + * Convert a presentation format address string to a socket address. +- * Similar to nfs_name_to_address(), but the DNS query is squelched, +- * and won't make any noise if the getaddrinfo() call fails. ++ * Similar to nfs_lookup(), but the DNS query is squelched, and it ++ * won't make any noise if the getaddrinfo() call fails. + * + * Returns 1 and fills in @sap and @salen if successful; otherwise zero. + * +@@ -549,8 +538,8 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap, const unsigned long *versions, + const unsigned int *protos) + { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + const unsigned long prog = pmap->pm_prog, *p_vers; + const unsigned int prot = (u_int)pmap->pm_prot, *p_prot; + const u_short port = (u_short) pmap->pm_port; +@@ -840,8 +829,8 @@ int start_statd(void) + int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen, + const struct pmap *pmap, const dirpath *argp) + { +- struct sockaddr_storage address; +- struct sockaddr *saddr = (struct sockaddr *)&address; ++ union nfs_sockaddr address; ++ struct sockaddr *saddr = &address.sa; + struct pmap mnt_pmap = *pmap; + struct timeval timeout = { + .tv_sec = MOUNT_TIMEOUT >> 3, +@@ -1289,6 +1278,7 @@ nfs_nfs_version(struct mount_options *options, unsigned long *version) + int + nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) + { ++ sa_family_t family; + char *option; + + switch (po_rightmost(options, nfs_transport_opttbl)) { +@@ -1300,17 +1290,8 @@ nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) + return 1; + case 2: /* proto */ + option = po_get(options, "proto"); +- if (option) { +- if (strcmp(option, "tcp") == 0) { +- *protocol = IPPROTO_TCP; +- return 1; +- } +- if (strcmp(option, "udp") == 0) { +- *protocol = IPPROTO_UDP; +- return 1; +- } +- return 0; +- } ++ if (option != NULL) ++ return nfs_get_proto(option, &family, protocol); + } + + /* +@@ -1352,6 +1333,40 @@ nfs_nfs_port(struct mount_options *options, unsigned long *port) + } + + /* ++ * Returns TRUE and fills in @family if a valid NFS protocol option ++ * is found, or FALSE if the option was specified with an invalid value. ++ */ ++int nfs_nfs_proto_family(struct mount_options *options, ++ sa_family_t *family) ++{ ++ unsigned long protocol; ++ char *option; ++ ++#ifdef IPV6_SUPPORTED ++ *family = AF_UNSPEC; ++#else ++ *family = AF_INET; ++#endif ++ ++ switch (po_rightmost(options, nfs_transport_opttbl)) { ++ case 0: /* udp */ ++ return 1; ++ case 1: /* tcp */ ++ return 1; ++ case 2: /* proto */ ++ option = po_get(options, "proto"); ++ if (option != NULL) ++ return nfs_get_proto(option, family, &protocol); ++ } ++ ++ /* ++ * NFS transport protocol wasn't specified. Return the ++ * default address family. ++ */ ++ return 1; ++} ++ ++/* + * "mountprog" is supported only by the legacy mount command. The + * kernel mount client does not support this option. + * +@@ -1419,20 +1434,12 @@ nfs_mount_version(struct mount_options *options, unsigned long *version) + static int + nfs_mount_protocol(struct mount_options *options, unsigned long *protocol) + { ++ sa_family_t family; + char *option; + + option = po_get(options, "mountproto"); +- if (option) { +- if (strcmp(option, "tcp") == 0) { +- *protocol = IPPROTO_TCP; +- return 1; +- } +- if (strcmp(option, "udp") == 0) { +- *protocol = IPPROTO_UDP; +- return 1; +- } +- return 0; +- } ++ if (option != NULL) ++ return nfs_get_proto(option, &family, protocol); + + /* + * MNT transport protocol wasn't specified. If the NFS +@@ -1472,6 +1479,35 @@ nfs_mount_port(struct mount_options *options, unsigned long *port) + return 1; + } + ++/* ++ * Returns TRUE and fills in @family if a valid MNT protocol option ++ * is found, or FALSE if the option was specified with an invalid value. ++ */ ++int nfs_mount_proto_family(struct mount_options *options, ++ sa_family_t *family) ++{ ++ unsigned long protocol; ++ char *option; ++ ++#ifdef HAVE_LIBTIRPC ++ *family = AF_UNSPEC; ++#else ++ *family = AF_INET; ++#endif ++ ++ option = po_get(options, "mountproto"); ++ if (option != NULL) ++ return nfs_get_proto(option, family, &protocol); ++ ++ /* ++ * MNT transport protocol wasn't specified. If the NFS ++ * transport protocol was specified, derive the family ++ * from that; otherwise, return the default family for ++ * NFS. ++ */ ++ return nfs_nfs_proto_family(options, family); ++} ++ + /** + * nfs_options2pmap - set up pmap structs based on mount options + * @options: pointer to mount options +diff --git a/utils/mount/network.h b/utils/mount/network.h +index 7eb89b0..2a3a110 100644 +--- a/utils/mount/network.h ++++ b/utils/mount/network.h +@@ -44,7 +44,8 @@ int nfs_probe_bothports(const struct sockaddr *, const socklen_t, + struct pmap *, const struct sockaddr *, + const socklen_t, struct pmap *); + int nfs_gethostbyname(const char *, struct sockaddr_in *); +-int nfs_name_to_address(const char *, struct sockaddr *, socklen_t *); ++int nfs_lookup(const char *hostname, const sa_family_t family, ++ struct sockaddr *sap, socklen_t *salen); + int nfs_string_to_sockaddr(const char *, struct sockaddr *, socklen_t *); + int nfs_present_sockaddr(const struct sockaddr *, + const socklen_t, char *, const size_t); +@@ -56,6 +57,8 @@ int clnt_ping(struct sockaddr_in *, const unsigned long, + + struct mount_options; + ++int nfs_nfs_proto_family(struct mount_options *options, sa_family_t *family); ++int nfs_mount_proto_family(struct mount_options *options, sa_family_t *family); + int nfs_nfs_version(struct mount_options *options, unsigned long *version); + int nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol); + +diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man +index 2299637..93bd642 100644 +--- a/utils/mount/nfs.man ++++ b/utils/mount/nfs.man +@@ -58,9 +58,17 @@ The server's hostname and export pathname + are separated by a colon, while + the mount options are separated by commas. The remaining fields + are separated by blanks or tabs. ++.P + The server's hostname can be an unqualified hostname, + a fully qualified domain name, +-or a dotted quad IPv4 address. ++a dotted quad IPv4 address, or ++an IPv6 address enclosed in square brackets. ++Link-local and site-local IPv6 addresses must be accompanied by an ++interface identifier. ++See ++.BR ipv6 (7) ++for details on specifying raw IPv6 addresses. ++.P + The + .I fstype + field contains either "nfs" (for version 2 or version 3 NFS mounts) +@@ -470,32 +478,38 @@ for mounting the + .B nfs + file system type. + .TP 1.5i +-.BI proto= transport +-The transport the NFS client uses ++.BI proto= netid ++The transport protocol name and protocol family the NFS client uses + to transmit requests to the NFS server for this mount point. +-.I transport +-can be either +-.B udp +-or +-.BR tcp . +-Each transport uses different default ++If an NFS server has both an IPv4 and an IPv6 address, using a specific ++netid will force the use of IPv4 or IPv6 networking to communicate ++with that server. ++.IP ++If support for TI-RPC is built into the ++.B mount.nfs ++command, ++.I netid ++is a valid netid listed in ++.IR /etc/netconfig . ++Otherwise, ++.I netid ++is one of "tcp," "udp," or "rdma," and only IPv4 may be used. ++.IP ++Each transport protocol uses different default + .B retrans + and + .B timeo +-settings; refer to the description of these two mount options for details. ++settings. ++Refer to the description of these two mount options for details. + .IP + In addition to controlling how the NFS client transmits requests to + the server, this mount option also controls how the + .BR mount (8) + command communicates with the server's rpcbind and mountd services. +-Specifying +-.B proto=tcp +-forces all traffic from the ++Specifying a netid that uses TCP forces all traffic from the + .BR mount (8) + command and the NFS client to use TCP. +-Specifying +-.B proto=udp +-forces all traffic types to use UDP. ++Specifying a netid that uses UDP forces all traffic types to use UDP. + .IP + If the + .B proto +@@ -548,15 +562,20 @@ or the server's mountd service is not available on the advertised port. + This option can be used when mounting an NFS server + through a firewall that blocks the rpcbind protocol. + .TP 1.5i +-.BI mountproto= transport +-The transport the NFS client uses ++.BI mountproto= netid ++The transport protocol name and protocol family the NFS client uses + to transmit requests to the NFS server's mountd service when performing + this mount request, and when later unmounting this mount point. +-.I transport +-can be either +-.B udp +-or +-.BR tcp . ++.IP ++If support for TI-RPC is built into the ++.B mount.nfs ++command, ++.I netid ++is a valid netid listed in ++.IR /etc/netconfig . ++Otherwise, ++.I netid ++is one of "tcp" or "udp," and only IPv4 may be used. + .IP + This option can be used when mounting an NFS server + through a firewall that blocks a particular transport. +@@ -566,6 +585,7 @@ option, different transports for mountd requests and NFS requests + can be specified. + If the server's mountd service is not available via the specified + transport, the mount request fails. ++.IP + Refer to the TRANSPORT METHODS section for more on how the + .B mountproto + mount option interacts with the +@@ -709,17 +729,26 @@ for mounting the + .B nfs4 + file system type. + .TP 1.5i +-.BI proto= transport +-The transport the NFS client uses ++.BI proto= netid ++The transport protocol name and protocol family the NFS client uses + to transmit requests to the NFS server for this mount point. +-.I transport +-can be either +-.B udp +-or +-.BR tcp . ++If an NFS server has both an IPv4 and an IPv6 address, using a specific ++netid will force the use of IPv4 or IPv6 networking to communicate ++with that server. ++.IP ++If support for TI-RPC is built into the ++.B mount.nfs ++command, ++.I netid ++is a valid netid listed in ++.IR /etc/netconfig . ++Otherwise, ++.I netid ++is one of "tcp" or "udp," and only IPv4 may be used. ++.IP + All NFS version 4 servers are required to support TCP, + so if this mount option is not specified, the NFS version 4 client +-uses the TCP transport protocol. ++uses the TCP protocol. + Refer to the TRANSPORT METHODS section for more details. + .TP 1.5i + .BI port= n +@@ -779,7 +808,8 @@ The DATA AND METADATA COHERENCE section discusses + the behavior of this option in more detail. + .TP 1.5i + .BI clientaddr= n.n.n.n +-Specifies a single IPv4 address (in dotted-quad form) ++Specifies a single IPv4 address (in dotted-quad form), ++or a non-link-local IPv6 address, + that the NFS client advertises to allow servers + to perform NFS version 4 callback requests against + files on this mount point. If the server is unable to +@@ -855,6 +885,14 @@ This example can be used to mount /usr over NFS. + .TA 2.5i +0.7i +0.7i +.7i + server:/export /usr nfs ro,nolock,nocto,actimeo=3600 0 0 + .FI ++.P ++This example shows how to mount an NFS server ++using a raw IPv6 link-local address. ++.P ++.NF ++.TA 2.5i +0.7i +0.7i +.7i ++ [fe80::215:c5ff:fb3e:e2b1%eth0]:/export /mnt nfs defaults 0 0 ++.FI + .SH "TRANSPORT METHODS" + NFS clients send requests to NFS servers via + Remote Procedure Calls, or +@@ -1498,6 +1536,8 @@ such as security negotiation, server referrals, and named attributes. + .BR mount.nfs (5), + .BR umount.nfs (5), + .BR exports (5), ++.BR netconfig (5), ++.BR ipv6 (7), + .BR nfsd (8), + .BR sm-notify (8), + .BR rpc.statd (8), +diff --git a/utils/mount/nfs4mount.c b/utils/mount/nfs4mount.c +index a2f318f..4a2fab7 100644 +--- a/utils/mount/nfs4mount.c ++++ b/utils/mount/nfs4mount.c +@@ -217,8 +217,11 @@ int nfs4mount(const char *spec, const char *node, int flags, + progname); + goto fail; + } +- snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s", +- old_opts, *old_opts ? "," : "", s); ++ if (running_bg) ++ strncpy(new_opts, old_opts, sizeof(new_opts)); ++ else ++ snprintf(new_opts, sizeof(new_opts), "%s%saddr=%s", ++ old_opts, *old_opts ? "," : "", s); + *extra_opts = xstrdup(new_opts); + + /* Set default options. +@@ -434,15 +437,17 @@ int nfs4mount(const char *spec, const char *node, int flags, + break; + } + +- switch(rpc_createerr.cf_stat){ +- case RPC_TIMEDOUT: +- break; +- case RPC_SYSTEMERROR: +- if (errno == ETIMEDOUT) ++ if (!bg) { ++ switch(rpc_createerr.cf_stat) { ++ case RPC_TIMEDOUT: + break; +- default: +- rpc_mount_errors(hostname, 0, bg); +- goto fail; ++ case RPC_SYSTEMERROR: ++ if (errno == ETIMEDOUT) ++ break; ++ default: ++ rpc_mount_errors(hostname, 0, bg); ++ goto fail; ++ } + } + + if (bg && !running_bg) { +diff --git a/utils/mount/nfsmount.c b/utils/mount/nfsmount.c +index 6355681..6b3356c 100644 +--- a/utils/mount/nfsmount.c ++++ b/utils/mount/nfsmount.c +@@ -170,7 +170,7 @@ parse_options(char *old_opts, struct nfs_mount_data *data, + struct pmap *mnt_pmap = &mnt_server->pmap; + struct pmap *nfs_pmap = &nfs_server->pmap; + int len; +- char *opt, *opteq, *p, *opt_b; ++ char *opt, *opteq, *p, *opt_b, *tmp_opts; + char *mounthost = NULL; + char cbuf[128]; + int open_quote = 0; +@@ -179,7 +179,8 @@ parse_options(char *old_opts, struct nfs_mount_data *data, + *bg = 0; + + len = strlen(new_opts); +- for (p=old_opts, opt_b=NULL; p && *p; p++) { ++ tmp_opts = xstrdup(old_opts); ++ for (p=tmp_opts, opt_b=NULL; p && *p; p++) { + if (!opt_b) + opt_b = p; /* begin of the option item */ + if (*p == '"') +@@ -457,10 +458,12 @@ parse_options(char *old_opts, struct nfs_mount_data *data, + goto out_bad; + *mnt_server->hostname = mounthost; + } ++ free(tmp_opts); + return 1; + bad_parameter: + nfs_error(_("%s: Bad nfs mount parameter: %s\n"), progname, opt); + out_bad: ++ free(tmp_opts); + return 0; + } + +diff --git a/utils/mount/nfsumount.c b/utils/mount/nfsumount.c +index c5505b1..9d798a2 100644 +--- a/utils/mount/nfsumount.c ++++ b/utils/mount/nfsumount.c +@@ -169,10 +169,15 @@ out: + static int nfs_umount_do_umnt(struct mount_options *options, + char **hostname, char **dirname) + { +- struct sockaddr_storage address; +- struct sockaddr *sap = (struct sockaddr *)&address; ++ union { ++ struct sockaddr sa; ++ struct sockaddr_in s4; ++ struct sockaddr_in6 s6; ++ } address; ++ struct sockaddr *sap = &address.sa; + socklen_t salen = sizeof(address); + 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); +@@ -189,8 +194,10 @@ static int nfs_umount_do_umnt(struct mount_options *options, + return EX_FAIL; + } + +- if (nfs_name_to_address(*hostname, sap, &salen) == 0) +- /* nfs_name_to_address reports any errors */ ++ if (!nfs_mount_proto_family(options, &family)) ++ return 0; ++ if (!nfs_lookup(*hostname, family, sap, &salen)) ++ /* nfs_lookup reports any errors */ + return EX_FAIL; + + if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0) +diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c +index b595649..57a4b32 100644 +--- a/utils/mount/stropts.c ++++ b/utils/mount/stropts.c +@@ -35,9 +35,11 @@ + #include + #include + ++#include "sockaddr.h" + #include "xcommon.h" + #include "mount.h" + #include "nls.h" ++#include "nfsrpc.h" + #include "mount_constants.h" + #include "stropts.h" + #include "error.h" +@@ -81,7 +83,7 @@ struct nfsmount_info { + *node, /* mounted-on dir */ + *type; /* "nfs" or "nfs4" */ + char *hostname; /* server's hostname */ +- struct sockaddr_storage address; /* server's address */ ++ union nfs_sockaddr address; + socklen_t salen; /* size of server's address */ + + struct mount_options *options; /* parsed mount options */ +@@ -204,9 +206,9 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap, + socklen_t salen, + struct mount_options *options) + { +- struct sockaddr_storage dummy; +- struct sockaddr *my_addr = (struct sockaddr *)&dummy; +- socklen_t my_len = sizeof(dummy); ++ union nfs_sockaddr address; ++ struct sockaddr *my_addr = &address.sa; ++ socklen_t my_len = sizeof(address); + + if (po_contains(options, "clientaddr") == PO_FOUND) + return 1; +@@ -218,21 +220,33 @@ static int nfs_append_clientaddr_option(const struct sockaddr *sap, + } + + /* +- * Resolve the 'mounthost=' hostname and append a new option using +- * the resulting address. ++ * Determine whether to append a 'mountaddr=' option. The option is needed if: ++ * ++ * 1. "mounthost=" was specified, or ++ * 2. The address families for proto= and mountproto= are different. + */ +-static int nfs_fix_mounthost_option(struct mount_options *options) ++static int nfs_fix_mounthost_option(struct mount_options *options, ++ const char *nfs_hostname) + { +- struct sockaddr_storage dummy; +- struct sockaddr *sap = (struct sockaddr *)&dummy; +- socklen_t salen = sizeof(dummy); ++ union nfs_sockaddr address; ++ struct sockaddr *sap = &address.sa; ++ socklen_t salen = sizeof(address); ++ sa_family_t nfs_family, mnt_family; + char *mounthost; + ++ if (!nfs_nfs_proto_family(options, &nfs_family)) ++ return 0; ++ if (!nfs_mount_proto_family(options, &mnt_family)) ++ return 0; ++ + mounthost = po_get(options, "mounthost"); +- if (!mounthost) +- return 1; ++ if (mounthost == NULL) { ++ if (nfs_family == mnt_family) ++ return 1; ++ mounthost = (char *)nfs_hostname; ++ } + +- if (!nfs_name_to_address(mounthost, sap, &salen)) { ++ if (!nfs_lookup(mounthost, mnt_family, sap, &salen)) { + nfs_error(_("%s: unable to determine mount server's address"), + progname); + return 0; +@@ -319,13 +333,16 @@ static int nfs_set_version(struct nfsmount_info *mi) + */ + static int nfs_validate_options(struct nfsmount_info *mi) + { +- struct sockaddr *sap = (struct sockaddr *)&mi->address; ++ struct sockaddr *sap = &mi->address.sa; ++ sa_family_t family; + + if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL)) + return 0; + ++ if (!nfs_nfs_proto_family(mi->options, &family)) ++ return 0; + mi->salen = sizeof(mi->address); +- if (!nfs_name_to_address(mi->hostname, sap, &mi->salen)) ++ if (!nfs_lookup(mi->hostname, family, sap, &mi->salen)) + return 0; + + if (!nfs_set_version(mi)) +@@ -371,10 +388,13 @@ static int nfs_extract_server_addresses(struct mount_options *options, + } + + static int nfs_construct_new_options(struct mount_options *options, ++ struct sockaddr *nfs_saddr, + struct pmap *nfs_pmap, ++ struct sockaddr *mnt_saddr, + struct pmap *mnt_pmap) + { + char new_option[64]; ++ char *netid; + + po_remove_all(options, "nfsprog"); + po_remove_all(options, "mountprog"); +@@ -391,20 +411,14 @@ static int nfs_construct_new_options(struct mount_options *options, + po_remove_all(options, "proto"); + po_remove_all(options, "udp"); + po_remove_all(options, "tcp"); +- switch (nfs_pmap->pm_prot) { +- case IPPROTO_TCP: +- snprintf(new_option, sizeof(new_option) - 1, +- "proto=tcp"); +- if (po_append(options, new_option) == PO_FAILED) +- return 0; +- break; +- case IPPROTO_UDP: +- snprintf(new_option, sizeof(new_option) - 1, +- "proto=udp"); +- if (po_append(options, new_option) == PO_FAILED) +- return 0; +- break; +- } ++ netid = nfs_get_netid(nfs_saddr->sa_family, nfs_pmap->pm_prot); ++ if (netid == NULL) ++ return 0; ++ snprintf(new_option, sizeof(new_option) - 1, ++ "proto=%s", netid); ++ free(netid); ++ if (po_append(options, new_option) == PO_FAILED) ++ return 0; + + po_remove_all(options, "port"); + if (nfs_pmap->pm_port != NFS_PORT) { +@@ -421,20 +435,14 @@ static int nfs_construct_new_options(struct mount_options *options, + return 0; + + po_remove_all(options, "mountproto"); +- switch (mnt_pmap->pm_prot) { +- case IPPROTO_TCP: +- snprintf(new_option, sizeof(new_option) - 1, +- "mountproto=tcp"); +- if (po_append(options, new_option) == PO_FAILED) +- return 0; +- break; +- case IPPROTO_UDP: +- snprintf(new_option, sizeof(new_option) - 1, +- "mountproto=udp"); +- if (po_append(options, new_option) == PO_FAILED) +- return 0; +- break; +- } ++ netid = nfs_get_netid(mnt_saddr->sa_family, mnt_pmap->pm_prot); ++ if (netid == NULL) ++ return 0; ++ snprintf(new_option, sizeof(new_option) - 1, ++ "mountproto=%s", netid); ++ free(netid); ++ if (po_append(options, new_option) == PO_FAILED) ++ return 0; + + po_remove_all(options, "mountport"); + snprintf(new_option, sizeof(new_option) - 1, +@@ -461,12 +469,12 @@ static int nfs_construct_new_options(struct mount_options *options, + static int + nfs_rewrite_pmap_mount_options(struct mount_options *options) + { +- struct sockaddr_storage nfs_address; +- struct sockaddr *nfs_saddr = (struct sockaddr *)&nfs_address; ++ union nfs_sockaddr nfs_address; ++ struct sockaddr *nfs_saddr = &nfs_address.sa; + socklen_t nfs_salen = sizeof(nfs_address); + struct pmap nfs_pmap; +- struct sockaddr_storage mnt_address; +- struct sockaddr *mnt_saddr = (struct sockaddr *)&mnt_address; ++ union nfs_sockaddr mnt_address; ++ struct sockaddr *mnt_saddr = &mnt_address.sa; + socklen_t mnt_salen = sizeof(mnt_address); + struct pmap mnt_pmap; + char *option; +@@ -510,7 +518,8 @@ nfs_rewrite_pmap_mount_options(struct mount_options *options) + return 0; + } + +- if (!nfs_construct_new_options(options, &nfs_pmap, &mnt_pmap)) { ++ if (!nfs_construct_new_options(options, nfs_saddr, &nfs_pmap, ++ mnt_saddr, &mnt_pmap)) { + errno = EINVAL; + return 0; + } +@@ -566,7 +575,7 @@ static int nfs_try_mount_v3v2(struct nfsmount_info *mi) + return result; + } + +- if (!nfs_fix_mounthost_option(options)) { ++ if (!nfs_fix_mounthost_option(options, mi->hostname)) { + errno = EINVAL; + goto out_fail; + } +@@ -601,7 +610,7 @@ out_fail: + */ + static int nfs_try_mount_v4(struct nfsmount_info *mi) + { +- struct sockaddr *sap = (struct sockaddr *)&mi->address; ++ struct sockaddr *sap = &mi->address.sa; + struct mount_options *options = po_dup(mi->options); + int result = 0; + +@@ -611,6 +620,18 @@ static int nfs_try_mount_v4(struct nfsmount_info *mi) + } + + if (mi->version == 0) { ++ if (po_contains(options, "mounthost") || ++ po_contains(options, "mountaddr") || ++ po_contains(options, "mountvers") || ++ po_contains(options, "mountproto")) { ++ /* ++ * Since these mountd options are set assume version 3 ++ * is wanted so error out with EPROTONOSUPPORT so the ++ * protocol negation starts with v3. ++ */ ++ errno = EPROTONOSUPPORT; ++ goto out_fail; ++ } + if (po_append(options, "vers=4") == PO_FAILED) { + errno = EINVAL; + goto out_fail; +@@ -656,9 +677,10 @@ static int nfs_try_mount(struct nfsmount_info *mi) + /* + * To deal with legacy Linux servers that don't + * automatically export a pseudo root, retry +- * ENOENT errors using version 3 ++ * ENOENT errors using version 3. And for ++ * Linux servers prior to 2.6.25, retry EPERM + */ +- if (errno != ENOENT) ++ if (errno != ENOENT && errno != EPERM) + break; + } + } +diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am +index 1e76cf8..eba81fc 100644 +--- a/utils/mountd/Makefile.am ++++ b/utils/mountd/Makefile.am +@@ -8,7 +8,7 @@ KPREFIX = @kprefix@ + sbin_PROGRAMS = mountd + + mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \ +- svc_run.c fsloc.c mountd.h ++ svc_run.c fsloc.c v4root.c mountd.h + mountd_LDADD = ../../support/export/libexport.a \ + ../../support/nfs/libnfs.a \ + ../../support/misc/libmisc.a \ +diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c +index 575f207..13eba70 100644 +--- a/utils/mountd/auth.c ++++ b/utils/mountd/auth.c +@@ -20,6 +20,7 @@ + #include "exportfs.h" + #include "mountd.h" + #include "xmalloc.h" ++#include "v4root.h" + + enum auth_error + { +@@ -102,75 +103,91 @@ auth_reload() + memset(&my_client, 0, sizeof(my_client)); + xtab_export_read(); + check_useipaddr(); ++ v4root_set(); ++ + ++counter; + + return counter; + } + ++static char *get_client_hostname(struct sockaddr_in *caller, struct hostent *hp, enum auth_error *error) ++{ ++ char *n; ++ ++ if (use_ipaddr) ++ return strdup(inet_ntoa(caller->sin_addr)); ++ n = client_compose(hp); ++ *error = unknown_host; ++ if (!n) ++ return NULL; ++ if (*n) ++ return n; ++ free(n); ++ return strdup("DEFAULT"); ++} ++ ++/* return static nfs_export with details filled in */ + static nfs_export * +-auth_authenticate_internal(char *what, struct sockaddr_in *caller, ++auth_authenticate_newcache(char *what, struct sockaddr_in *caller, + char *path, struct hostent *hp, + enum auth_error *error) + { +- nfs_export *exp; ++ nfs_export *exp; ++ int i; + +- if (new_cache) { +- int i; +- /* return static nfs_export with details filled in */ +- char *n; +- free(my_client.m_hostname); +- if (use_ipaddr) { +- my_client.m_hostname = +- strdup(inet_ntoa(caller->sin_addr)); +- } else { +- n = client_compose(hp); +- *error = unknown_host; +- if (!n) +- my_client.m_hostname = NULL; +- else if (*n) +- my_client.m_hostname = n; +- else { +- free(n); +- my_client.m_hostname = strdup("DEFAULT"); +- } ++ free(my_client.m_hostname); ++ ++ my_client.m_hostname = get_client_hostname(caller, hp, error); ++ if (my_client.m_hostname == NULL) ++ return NULL; ++ ++ my_client.m_naddr = 1; ++ my_client.m_addrlist[0] = caller->sin_addr; ++ my_exp.m_client = &my_client; ++ ++ exp = NULL; ++ for (i = 0; !exp && i < MCL_MAXTYPES; i++) ++ for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { ++ if (strcmp(path, exp->m_export.e_path)) ++ 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)) ++ continue; ++ break; + } +- if (my_client.m_hostname == NULL) +- return NULL; +- my_client.m_naddr = 1; +- my_client.m_addrlist[0] = caller->sin_addr; +- my_exp.m_client = &my_client; +- +- exp = NULL; +- for (i = 0; !exp && i < MCL_MAXTYPES; i++) +- for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { +- if (strcmp(path, exp->m_export.e_path)) +- 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)) +- continue; +- break; +- } +- *error = not_exported; +- if (!exp) +- return exp; ++ *error = not_exported; ++ if (!exp) ++ return NULL; + +- my_exp.m_export = exp->m_export; +- exp = &my_exp; ++ my_exp.m_export = exp->m_export; ++ exp = &my_exp; ++ return exp; ++} ++ ++static nfs_export * ++auth_authenticate_internal(char *what, struct sockaddr_in *caller, ++ char *path, struct hostent *hp, ++ enum auth_error *error) ++{ ++ nfs_export *exp; + ++ if (new_cache) { ++ exp = auth_authenticate_newcache(what, caller, path, hp, error); ++ if (!exp) ++ return NULL; + } else { + if (!(exp = export_find(hp, path))) { + *error = no_entry; + return NULL; + } +- if (!exp->m_mayexport) { +- *error = not_exported; +- return NULL; +- } ++ } ++ if (exp->m_export.e_flags & NFSEXP_V4ROOT) { ++ *error = no_entry; ++ return NULL; + } + if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) && +- (ntohs(caller->sin_port) < IPPORT_RESERVED/2 || +- ntohs(caller->sin_port) >= IPPORT_RESERVED)) { ++ ntohs(caller->sin_port) >= IPPORT_RESERVED) { + *error = illegal_port; + return NULL; + } +diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c +index e4e2f22..d63e10a 100644 +--- a/utils/mountd/cache.c ++++ b/utils/mountd/cache.c +@@ -614,73 +614,54 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex + return qword_eol(f); + } + +-void nfsd_export(FILE *f) ++static int is_subdirectory(char *subpath, char *path) + { +- /* requests are: +- * domain path +- * determine export options and return: +- * domain path expiry flags anonuid anongid fsid +- */ +- +- char *cp; +- int i; +- char *dom, *path; +- nfs_export *exp, *found = NULL; +- int found_type = 0; +- struct in_addr addr; +- struct hostent *he = NULL; +- +- +- if (readline(fileno(f), &lbuf, &lbuflen) != 1) +- return; ++ int l = strlen(path); + +- xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf); ++ return strcmp(subpath, path) == 0 ++ || (strncmp(subpath, path, l) == 0 && path[l] == '/'); ++} + +- cp = lbuf; +- dom = malloc(strlen(cp)); +- path = malloc(strlen(cp)); ++static int path_matches(nfs_export *exp, char *path) ++{ ++ if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) ++ return is_subdirectory(path, exp->m_export.e_path); ++ return strcmp(path, exp->m_export.e_path) == 0; ++} + +- if (!dom || !path) +- goto out; ++static int client_matches(nfs_export *exp, char *dom, struct hostent *he) ++{ ++ if (use_ipaddr) ++ return client_check(exp->m_client, he); ++ return client_member(dom, exp->m_client->m_hostname); ++} + +- if (qword_get(&cp, dom, strlen(lbuf)) <= 0) +- goto out; +- if (qword_get(&cp, path, strlen(lbuf)) <= 0) +- goto out; ++static int export_matches(nfs_export *exp, char *dom, char *path, struct hostent *he) ++{ ++ return path_matches(exp, path) && client_matches(exp, dom, he); ++} + +- auth_reload(); ++static nfs_export *lookup_export(char *dom, char *path, struct hostent *he) ++{ ++ nfs_export *exp; ++ nfs_export *found = NULL; ++ int found_type = 0; ++ int i; + +- /* now find flags for this export point in this domain */ + for (i=0 ; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { +- if (!use_ipaddr && !client_member(dom, exp->m_client->m_hostname)) +- continue; +- if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) { +- /* if path is a mountpoint below e_path, then OK */ +- int l = strlen(exp->m_export.e_path); +- if (strcmp(path, exp->m_export.e_path) == 0 || +- (strncmp(path, exp->m_export.e_path, l) == 0 && +- path[l] == '/' && +- is_mountpoint(path))) +- /* ok */; +- else +- continue; +- } else if (strcmp(path, exp->m_export.e_path) != 0) ++ if (!export_matches(exp, dom, path, he)) + continue; +- if (use_ipaddr) { +- if (he == NULL) { +- if (!inet_aton(dom, &addr)) +- goto out; +- he = client_resolve(addr); +- } +- if (!client_check(exp->m_client, he)) +- continue; +- } + if (!found) { + found = exp; + found_type = i; + continue; + } ++ ++ /* Always prefer non-V4ROOT mounts */ ++ if (found->m_export.e_flags & NFSEXP_V4ROOT) ++ continue; ++ + /* If one is a CROSSMOUNT, then prefer the longest path */ + if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) || + (exp->m_export.e_flags & NFSEXP_CROSSMOUNT)) && +@@ -703,6 +684,50 @@ void nfsd_export(FILE *f) + } + } + } ++ return found; ++} ++ ++void nfsd_export(FILE *f) ++{ ++ /* requests are: ++ * domain path ++ * determine export options and return: ++ * domain path expiry flags anonuid anongid fsid ++ */ ++ ++ char *cp; ++ char *dom, *path; ++ nfs_export *found = NULL; ++ struct in_addr addr; ++ struct hostent *he = NULL; ++ ++ ++ if (readline(fileno(f), &lbuf, &lbuflen) != 1) ++ return; ++ ++ xlog(D_CALL, "nfsd_export: inbuf '%s'", lbuf); ++ ++ cp = lbuf; ++ dom = malloc(strlen(cp)); ++ path = malloc(strlen(cp)); ++ ++ if (!dom || !path) ++ goto out; ++ ++ if (qword_get(&cp, dom, strlen(lbuf)) <= 0) ++ goto out; ++ if (qword_get(&cp, path, strlen(lbuf)) <= 0) ++ goto out; ++ ++ auth_reload(); ++ ++ if (use_ipaddr) { ++ if (!inet_aton(dom, &addr)) ++ goto out; ++ he = client_resolve(addr); ++ } ++ ++ found = lookup_export(dom, path, he); + + if (found) { + if (dump_to_cache(f, dom, path, &found->m_export) < 0) { +diff --git a/utils/mountd/mount_dispatch.c b/utils/mountd/mount_dispatch.c +index 199fcec..ba6981d 100644 +--- a/utils/mountd/mount_dispatch.c ++++ b/utils/mountd/mount_dispatch.c +@@ -70,12 +70,10 @@ mount_dispatch(struct svc_req *rqstp, SVCXPRT *transp) + { + union mountd_arguments argument; + union mountd_results result; +-#ifdef HAVE_TCP_WRAPPER +- struct sockaddr_in *sin = nfs_getrpccaller_in(transp); + ++#ifdef HAVE_TCP_WRAPPER + /* remote host authorization check */ +- if (sin->sin_family == AF_INET && +- !check_default("mountd", sin, rqstp->rq_proc, MOUNTPROG)) { ++ if (!check_default("mountd", nfs_getrpccaller(transp), MOUNTPROG)) { + svcerr_auth (transp, AUTH_FAILED); + return; + } +diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c +index 888fd8c..a0a1f2d 100644 +--- a/utils/mountd/mountd.c ++++ b/utils/mountd/mountd.c +@@ -509,12 +509,89 @@ get_rootfh(struct svc_req *rqstp, dirpath *path, nfs_export **expret, + return fh; + } + ++static void remove_all_clients(exportnode *e) ++{ ++ struct groupnode *g, *ng; ++ ++ for (g = e->ex_groups; g; g = ng) { ++ ng = g->gr_next; ++ xfree(g->gr_name); ++ xfree(g); ++ } ++ e->ex_groups = NULL; ++} ++ ++static void free_exportlist(exports *elist) ++{ ++ struct exportnode *e, *ne; ++ ++ for (e = *elist; e != NULL; e = ne) { ++ ne = e->ex_next; ++ remove_all_clients(e); ++ xfree(e->ex_dir); ++ xfree(e); ++ } ++ *elist = NULL; ++} ++ ++static void prune_clients(nfs_export *exp, struct exportnode *e) ++{ ++ struct hostent *hp; ++ 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)) { ++ *cp = c->gr_next; ++ xfree(c->gr_name); ++ xfree(c); ++ xfree (hp); ++ continue; ++ } ++ xfree (hp); ++ } ++ cp = &(c->gr_next); ++ } ++} ++ ++static exportnode *lookup_or_create_elist_entry(exports *elist, nfs_export *exp) ++{ ++ exportnode *e; ++ ++ for (e = *elist; e != NULL; e = e->ex_next) { ++ if (!strcmp(exp->m_export.e_path, e->ex_dir)) ++ return e; ++ } ++ e = xmalloc(sizeof(*e)); ++ e->ex_next = *elist; ++ e->ex_groups = NULL; ++ e->ex_dir = xstrdup(exp->m_export.e_path); ++ *elist = e; ++ return e; ++} ++ ++static void insert_group(struct exportnode *e, char *newname) ++{ ++ struct groupnode *g; ++ ++ for (g = e->ex_groups; g; g = g->gr_next) ++ if (strcmp(g->gr_name, newname)) ++ return; ++ ++ g = xmalloc(sizeof(*g)); ++ g->gr_name = xstrdup(newname); ++ g->gr_next = e->ex_groups; ++ e->ex_groups = g; ++} ++ + static exports + get_exportlist(void) + { + static exports elist = NULL; +- struct exportnode *e, *ne; +- struct groupnode *g, *ng, *c, **cp; ++ struct exportnode *e; + nfs_export *exp; + int i; + static unsigned int ecounter; +@@ -526,77 +603,26 @@ get_exportlist(void) + + ecounter = acounter; + +- for (e = elist; e != NULL; e = ne) { +- ne = e->ex_next; +- for (g = e->ex_groups; g != NULL; g = ng) { +- ng = g->gr_next; +- xfree(g->gr_name); +- xfree(g); +- } +- xfree(e->ex_dir); +- xfree(e); +- } +- elist = NULL; ++ free_exportlist(&elist); + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { +- for (e = elist; e != NULL; e = e->ex_next) { +- if (!strcmp(exp->m_export.e_path, e->ex_dir)) +- break; +- } +- if (!e) { +- e = (struct exportnode *) xmalloc(sizeof(*e)); +- e->ex_next = elist; +- e->ex_groups = NULL; +- e->ex_dir = xstrdup(exp->m_export.e_path); +- elist = e; +- } +- +- /* We need to check if we should remove +- previous ones. */ +- if (i == MCL_ANONYMOUS && e->ex_groups) { +- for (g = e->ex_groups; g; g = ng) { +- ng = g->gr_next; +- xfree(g->gr_name); +- xfree(g); +- } +- e->ex_groups = NULL; ++ /* Don't show pseudo exports */ ++ if (exp->m_export.e_flags & NFSEXP_V4ROOT) + continue; +- } +- +- if (i != MCL_FQDN && e->ex_groups) { +- struct hostent *hp; ++ e = lookup_or_create_elist_entry(&elist, exp); + +- 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)) { +- *cp = c->gr_next; +- xfree(c->gr_name); +- xfree(c); +- xfree (hp); ++ /* exports to "*" absorb any others */ ++ if (i == MCL_ANONYMOUS && e->ex_groups) { ++ remove_all_clients(e); + continue; +- } +- xfree (hp); +- } +- cp = &(c->gr_next); +- } + } ++ /* non-FQDN's absorb FQDN's they contain: */ ++ if (i != MCL_FQDN && e->ex_groups) ++ prune_clients(exp, e); + +- if (exp->m_export.e_hostname [0] != '\0') { +- for (g = e->ex_groups; g; g = g->gr_next) +- if (strcmp (exp->m_export.e_hostname, +- g->gr_name) == 0) +- break; +- if (g) +- continue; +- g = (struct groupnode *) xmalloc(sizeof(*g)); +- g->gr_name = xstrdup(exp->m_export.e_hostname); +- g->gr_next = e->ex_groups; +- e->ex_groups = g; +- } ++ if (exp->m_export.e_hostname[0] != '\0') ++ insert_group(e, exp->m_export.e_hostname); + } + } + +diff --git a/utils/mountd/rmtab.c b/utils/mountd/rmtab.c +index c371f8d..b028529 100644 +--- a/utils/mountd/rmtab.c ++++ b/utils/mountd/rmtab.c +@@ -143,23 +143,16 @@ mountlist_del_all(struct sockaddr_in *sin) + return; + if (!(hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) { + xlog(L_ERROR, "can't get hostname of %s", inet_ntoa(addr)); +- xfunlock(lockid); +- return; ++ goto out_unlock; + } +- else +- hp = hostent_dup (hp); ++ hp = hostent_dup (hp); ++ ++ if (!setrmtabent("r")) ++ goto out_free; ++ ++ if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) ++ goto out_close; + +- if (!setrmtabent("r")) { +- xfunlock(lockid); +- free (hp); +- return; +- } +- if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) { +- endrmtabent(); +- xfunlock(lockid); +- free (hp); +- return; +- } + while ((rep = getrmtabent(1, NULL)) != NULL) { + if (strcmp(rep->r_client, hp->h_name) == 0 && + (exp = auth_authenticate("umountall", sin, rep->r_path))) +@@ -170,10 +163,13 @@ mountlist_del_all(struct sockaddr_in *sin) + xlog(L_ERROR, "couldn't rename %s to %s", + _PATH_RMTABTMP, _PATH_RMTAB); + } +- endrmtabent(); /* close & unlink */ + fendrmtabent(fp); +- xfunlock(lockid); ++out_close: ++ endrmtabent(); /* close & unlink */ ++out_free: + free (hp); ++out_unlock: ++ xfunlock(lockid); + } + + mountlist +diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c +new file mode 100644 +index 0000000..7fd6af3 +--- /dev/null ++++ b/utils/mountd/v4root.c +@@ -0,0 +1,196 @@ ++/* ++ * Copyright (C) 2009 Red Hat ++ * ++ * support/export/v4root.c ++ * ++ * Routines used to support NFSv4 pseudo roots ++ * ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "xlog.h" ++#include "exportfs.h" ++#include "nfslib.h" ++#include "misc.h" ++#include "v4root.h" ++ ++int v4root_needed; ++ ++static nfs_export pseudo_root = { ++ .m_next = NULL, ++ .m_client = NULL, ++ .m_export = { ++ .e_hostname = "*", ++ .e_path = "/", ++ .e_flags = NFSEXP_READONLY | NFSEXP_ROOTSQUASH ++ | NFSEXP_NOSUBTREECHECK | NFSEXP_FSID ++ | NFSEXP_V4ROOT, ++ .e_anonuid = 65534, ++ .e_anongid = 65534, ++ .e_squids = NULL, ++ .e_nsquids = 0, ++ .e_sqgids = NULL, ++ .e_nsqgids = 0, ++ .e_fsid = 0, ++ .e_mountpoint = NULL, ++ }, ++ .m_exported = 0, ++ .m_xtabent = 1, ++ .m_mayexport = 1, ++ .m_changed = 0, ++ .m_warned = 0, ++}; ++ ++void set_pseudofs_security(struct exportent *pseudo, struct exportent *source) ++{ ++ struct sec_entry *se; ++ int i; ++ ++ if (source->e_flags & NFSEXP_INSECURE_PORT) ++ pseudo->e_flags |= NFSEXP_INSECURE_PORT; ++ for (se = source->e_secinfo; se->flav; se++) { ++ struct sec_entry *new; ++ ++ i = secinfo_addflavor(se->flav, pseudo); ++ new = &pseudo->e_secinfo[i]; ++ ++ if (se->flags & NFSEXP_INSECURE_PORT) ++ new->flags |= NFSEXP_INSECURE_PORT; ++ } ++} ++ ++/* ++ * Create a pseudo export ++ */ ++static struct exportent * ++v4root_create(char *path, nfs_export *export) ++{ ++ nfs_export *exp; ++ struct exportent eep; ++ struct exportent *curexp = &export->m_export; ++ ++ dupexportent(&eep, &pseudo_root.m_export); ++ eep.e_hostname = strdup(curexp->e_hostname); ++ strncpy(eep.e_path, path, sizeof(eep.e_path)); ++ if (strcmp(path, "/") != 0) ++ eep.e_flags &= ~NFSEXP_FSID; ++ set_pseudofs_security(&eep, curexp); ++ exp = export_create(&eep, 0); ++ if (exp == NULL) ++ return NULL; ++ xlog(D_CALL, "v4root_create: path '%s'", exp->m_export.e_path); ++ return &exp->m_export; ++} ++ ++/* ++ * Make sure the kernel has pseudo root support. ++ */ ++static int ++v4root_support(void) ++{ ++ struct export_features *ef; ++ static int warned = 0; ++ ++ ef = get_export_features(); ++ ++ if (ef->flags & NFSEXP_V4ROOT) ++ return 1; ++ if (!warned) { ++ xlog(L_WARNING, "Kernel does not have pseudo root support."); ++ xlog(L_WARNING, "NFS v4 mounts will be disabled unless fsid=0"); ++ xlog(L_WARNING, "is specfied in /etc/exports file."); ++ warned++; ++ } ++ return 0; ++} ++ ++int pseudofs_update(char *hostname, char *path, nfs_export *source) ++{ ++ nfs_export *exp; ++ ++ exp = export_lookup(hostname, path, 0); ++ if (exp && !(exp->m_export.e_flags & NFSEXP_V4ROOT)) ++ return 0; ++ if (!exp) { ++ if (v4root_create(path, source) == NULL) { ++ xlog(L_WARNING, "v4root_set: Unable to create " ++ "pseudo export for '%s'", path); ++ return -ENOMEM; ++ } ++ return 0; ++ } ++ /* Update an existing V4ROOT export: */ ++ set_pseudofs_security(&exp->m_export, &source->m_export); ++ return 0; ++} ++ ++static int v4root_add_parents(nfs_export *exp) ++{ ++ char *hostname = exp->m_export.e_hostname; ++ char *path; ++ char *ptr; ++ ++ path = strdup(exp->m_export.e_path); ++ if (!path) ++ return -ENOMEM; ++ for (ptr = path + 1; ptr; ptr = strchr(ptr, '/')) { ++ int ret; ++ char saved; ++ ++ saved = *ptr; ++ *ptr = '\0'; ++ ret = pseudofs_update(hostname, path, exp); ++ if (ret) ++ return ret; ++ *ptr = saved; ++ ptr++; ++ } ++ free(path); ++ return 0; ++} ++ ++/* ++ * Create pseudo exports by running through the real export ++ * looking at the components of the path that make up the export. ++ * Those path components, if not exported, will become pseudo ++ * exports allowing them to be found when the kernel does an upcall ++ * looking for components of the v4 mount. ++ */ ++void ++v4root_set() ++{ ++ nfs_export *exp; ++ int i, ret; ++ ++ if (!v4root_needed) ++ return; ++ if (!v4root_support()) ++ return; ++ ++ for (i = 0; i < MCL_MAXTYPES; i++) { ++ for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { ++ if (exp->m_export.e_flags & NFSEXP_V4ROOT) ++ /* ++ * We just added this one, so its ++ * parents are already dealt with! ++ */ ++ continue; ++ ++ ret = v4root_add_parents(exp); ++ /* XXX: error handling! */ ++ } ++ } ++} +diff --git a/utils/nfsd/nfssvc.c b/utils/nfsd/nfssvc.c +index 12d3253..b8028bb 100644 +--- a/utils/nfsd/nfssvc.c ++++ b/utils/nfsd/nfssvc.c +@@ -212,7 +212,7 @@ int + nfssvc_set_sockets(const int family, const unsigned int protobits, + const char *host, const char *port) + { +- struct addrinfo hints = { .ai_flags = AI_PASSIVE | AI_ADDRCONFIG }; ++ struct addrinfo hints = { .ai_flags = AI_PASSIVE }; + + hints.ai_family = family; + +diff --git a/utils/showmount/showmount.c b/utils/showmount/showmount.c +index 418e8b9..f567093 100644 +--- a/utils/showmount/showmount.c ++++ b/utils/showmount/showmount.c +@@ -78,29 +78,36 @@ static void usage(FILE *fp, int n) + exit(n); + } + +-static const char *nfs_sm_pgmtbl[] = { ++static const char *mount_pgm_tbl[] = { + "showmount", + "mount", + "mountd", + NULL, + }; + ++static const rpcvers_t mount_vers_tbl[] = { ++ MOUNTVERS_NFSV3, ++ MOUNTVERS_POSIX, ++ MOUNTVERS, ++}; ++static const unsigned int max_vers_tblsz = ++ (sizeof(mount_vers_tbl)/sizeof(mount_vers_tbl[0])); ++ + /* + * Generate an RPC client handle connected to the mountd service + * at @hostname, or die trying. + * + * Supports both AF_INET and AF_INET6 server addresses. + */ +-static CLIENT *nfs_get_mount_client(const char *hostname) ++static CLIENT *nfs_get_mount_client(const char *hostname, rpcvers_t vers) + { +- rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, nfs_sm_pgmtbl); ++ rpcprog_t program = nfs_getrpcbyname(MOUNTPROG, mount_pgm_tbl); + CLIENT *client; + +- client = clnt_create(hostname, program, MOUNTVERS, "tcp"); ++ client = clnt_create(hostname, program, vers, "tcp"); + if (client) + return client; +- +- client = clnt_create(hostname, program, MOUNTVERS, "udp"); ++ client = clnt_create(hostname, program, vers, "udp"); + if (client) + return client; + +@@ -123,6 +130,7 @@ int main(int argc, char **argv) + int i; + int n; + int maxlen; ++ int unsigned vers=0; + char **dumpv; + + program_name = argv[0]; +@@ -185,11 +193,12 @@ int main(int argc, char **argv) + break; + } + +- mclient = nfs_get_mount_client(hostname); ++ mclient = nfs_get_mount_client(hostname, mount_vers_tbl[vers]); + mclient->cl_auth = authunix_create_default(); + total_timeout.tv_sec = TOTAL_TIMEOUT; + total_timeout.tv_usec = 0; + ++again: + if (eflag) { + memset(&exportlist, '\0', sizeof(exportlist)); + +@@ -197,6 +206,13 @@ int main(int argc, char **argv) + (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_exports, (caddr_t) &exportlist, + total_timeout); ++ if (clnt_stat == RPC_PROGVERSMISMATCH) { ++ if (++vers < max_vers_tblsz) { ++ (void)CLNT_CONTROL(mclient, CLSET_VERS, ++ (void *)&mount_vers_tbl[vers]); ++ goto again; ++ } ++ } + if (clnt_stat != RPC_SUCCESS) { + clnt_perror(mclient, "rpc mount export"); + clnt_destroy(mclient); +@@ -232,6 +248,13 @@ int main(int argc, char **argv) + (xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_mountlist, (caddr_t) &dumplist, + total_timeout); ++ if (clnt_stat == RPC_PROGVERSMISMATCH) { ++ if (++vers < max_vers_tblsz) { ++ (void)CLNT_CONTROL(mclient, CLSET_VERS, ++ (void *)&mount_vers_tbl[vers]); ++ goto again; ++ } ++ } + if (clnt_stat != RPC_SUCCESS) { + clnt_perror(mclient, "rpc mount dump"); + clnt_destroy(mclient); +diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am +index 8a3ba4e..1744791 100644 +--- a/utils/statd/Makefile.am ++++ b/utils/statd/Makefile.am +@@ -2,31 +2,25 @@ + + man8_MANS = statd.man sm-notify.man + +-GENFILES_CLNT = sm_inter_clnt.c +-GENFILES_SVC = sm_inter_svc.c +-GENFILES_XDR = sm_inter_xdr.c +-GENFILES_H = sm_inter.h +- +-GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) +- + RPCPREFIX = rpc. + KPREFIX = @kprefix@ + sbin_PROGRAMS = statd sm-notify + dist_sbin_SCRIPTS = start-statd +-statd_SOURCES = callback.c notlist.c log.c misc.c monitor.c \ ++statd_SOURCES = callback.c notlist.c misc.c monitor.c hostname.c \ + simu.c stat.c statd.c svc_run.c rmtcall.c \ +- sm_inter_clnt.c sm_inter_svc.c sm_inter_xdr.c log.h \ +- notlist.h statd.h system.h version.h sm_inter.h ++ notlist.h statd.h system.h version.h + sm_notify_SOURCES = sm-notify.c + + BUILT_SOURCES = $(GENFILES) +-statd_LDADD = ../../support/export/libexport.a \ ++statd_LDADD = ../../support/nsm/libnsm.a \ + ../../support/nfs/libnfs.a \ + ../../support/misc/libmisc.a \ +- $(LIBWRAP) $(LIBNSL) +-sm_notify_LDADD = $(LIBNSL) ++ $(LIBWRAP) $(LIBNSL) $(LIBCAP) ++sm_notify_LDADD = ../../support/nsm/libnsm.a \ ++ ../../support/nfs/libnfs.a \ ++ $(LIBNSL) $(LIBCAP) + +-EXTRA_DIST = sim_sm_inter.x sm_inter.x $(man8_MANS) COPYRIGHT simulate.c ++EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c + + if CONFIG_RPCGEN + RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen +diff --git a/utils/statd/callback.c b/utils/statd/callback.c +index 8885238..d1cc139 100644 +--- a/utils/statd/callback.c ++++ b/utils/statd/callback.c +@@ -10,10 +10,9 @@ + #include + #endif + +-#include ++#include + + #include "rpcmisc.h" +-#include "misc.h" + #include "statd.h" + #include "notlist.h" + +@@ -21,30 +20,85 @@ + /* notify_list *cbnl = NULL; ... never used */ + + +-/* ++/* + * Services SM_NOTIFY requests. +- * Any clients that have asked us to monitor that host are put on +- * the global callback list, which is processed as soon as statd +- * returns to svc_run. ++ * ++ * When NLM uses an SM_MON request to tell statd to monitor a remote, ++ * the request contains a "mon_name" argument. This is usually the ++ * "caller_name" argument of an NLMPROC_LOCK request. On Linux, the ++ * NLM can send statd the remote's IP address instead of its ++ * caller_name. The NSM protocol does not allow both the remote's ++ * caller_name and it's IP address to be sent in the same SM_MON ++ * request. ++ * ++ * The remote's caller_name is useful because it makes it simple ++ * to identify rebooting remotes by matching the "mon_name" argument ++ * they sent via an SM_NOTIFY request. ++ * ++ * The caller_name string may not be a fully qualified domain name, ++ * or even registered in the DNS database, however. Having the ++ * remote's IP address is useful because then there is no ambiguity ++ * about where to send an SM_NOTIFY after the local system reboots. ++ * ++ * Without the actual caller_name, however, statd must use an ++ * heuristic to match an incoming SM_NOTIFY request to one of the ++ * hosts it is currently monitoring. The incoming mon_name in an ++ * SM_NOTIFY address is converted to a list of IP addresses using ++ * DNS. Each mon_name on statd's monitor list is also converted to ++ * an address list, and the two lists are checked to see if there is ++ * a matching address. ++ * ++ * There are some risks to this strategy: ++ * ++ * 1. The external DNS database is not reliable. It can change ++ * over time, or the forward and reverse mappings could be ++ * inconsistent. ++ * ++ * 2. If statd's monitor list becomes substantial, finding a match ++ * can generate a not inconsequential amount of DNS traffic. ++ * ++ * 3. statd is a single-threaded service. When DNS becomes slow or ++ * unresponsive, statd also becomes slow or unresponsive. ++ * ++ * 4. If the remote does not have a DNS entry at all (or if the ++ * remote can resolve itself, but the local host can't resolve ++ * the remote's hostname), the remote cannot be monitored, and ++ * therefore NLM locking cannot be provided for that host. ++ * ++ * 5. Local DNS resolution can produce different results for the ++ * mon_name than the results the remote might see for the same ++ * query, especially if the remote did not send a caller_name ++ * or mon_name that is a fully qualified domain name. ++ * ++ * Note that a caller_name is passed from NFS client to server, ++ * but the client never knows what mon_name the server might use ++ * to notify it of a reboot. On Linux, the client extracts the ++ * server's name from the devname it was passed by the mount ++ * command. This is often not a fully-qualified domain name. + */ + void * + sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp) + { + notify_list *lp, *call; + static char *result = NULL; +- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); +- char *ip_addr = xstrdup(inet_ntoa(sin->sin_addr)); ++ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); ++ char ip_addr[INET6_ADDRSTRLEN]; + +- dprintf(N_DEBUG, "Received SM_NOTIFY from %s, state: %d", ++ xlog(D_CALL, "Received SM_NOTIFY from %s, state: %d", + argp->mon_name, argp->state); + + /* quick check - don't bother if we're not monitoring anyone */ + if (rtnl == NULL) { +- note(N_WARNING, "SM_NOTIFY from %s while not monitoring any hosts.", ++ xlog_warn("SM_NOTIFY from %s while not monitoring any hosts", + argp->mon_name); + return ((void *) &result); + } + ++ if (!statd_present_address(sap, ip_addr, sizeof(ip_addr))) { ++ xlog_warn("Unrecognized sender address"); ++ return ((void *) &result); ++ } ++ + /* okir change: statd doesn't remove the remote host from its + * internal monitor list when receiving an SM_NOTIFY call from + * it. Lockd will want to continue monitoring the remote host +@@ -52,8 +106,8 @@ sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp) + */ + for (lp = rtnl ; lp ; lp = lp->next) + if (NL_STATE(lp) != argp->state && +- (matchhostname(argp->mon_name, lp->dns_name) || +- matchhostname(ip_addr, lp->dns_name))) { ++ (statd_matchhostname(argp->mon_name, lp->dns_name) || ++ statd_matchhostname(ip_addr, lp->dns_name))) { + NL_STATE(lp) = argp->state; + call = nlist_clone(lp); + nlist_insert(¬ify, call); +diff --git a/utils/statd/hostname.c b/utils/statd/hostname.c +new file mode 100644 +index 0000000..7d704cc +--- /dev/null ++++ b/utils/statd/hostname.c +@@ -0,0 +1,284 @@ ++/* ++ * Copyright 2009 Oracle. All rights reserved. ++ * ++ * 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 . ++ */ ++ ++/* ++ * NSM for Linux. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "sockaddr.h" ++#include "statd.h" ++#include "xlog.h" ++ ++#ifndef HAVE_DECL_AI_ADDRCONFIG ++#define AI_ADDRCONFIG 0 ++#endif ++ ++/** ++ * statd_present_address - convert sockaddr to presentation address ++ * @sap: pointer to socket address to convert ++ * @buf: pointer to buffer to fill in ++ * @buflen: length of buffer ++ * ++ * Convert the passed-in sockaddr-style address to presentation format. ++ * The presentation format address is placed in @buf and is ++ * '\0'-terminated. ++ * ++ * Returns true if successful; otherwise false. ++ * ++ * getnameinfo(3) is preferred, since it can parse IPv6 scope IDs. ++ * An alternate version of statd_present_address() is available to ++ * handle older glibcs that do not have getnameinfo(3). ++ */ ++#ifdef HAVE_GETNAMEINFO ++_Bool ++statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen) ++{ ++ socklen_t salen; ++ int error; ++ ++ salen = nfs_sockaddr_length(sap); ++ if (salen == 0) { ++ xlog(D_GENERAL, "%s: unsupported address family", ++ __func__); ++ return false; ++ } ++ ++ error = getnameinfo(sap, salen, buf, (socklen_t)buflen, ++ NULL, 0, NI_NUMERICHOST); ++ if (error != 0) { ++ xlog(D_GENERAL, "%s: getnameinfo(3): %s", ++ __func__, gai_strerror(error)); ++ return false; ++ } ++ return true; ++} ++#else /* !HAVE_GETNAMEINFO */ ++_Bool ++statd_present_address(const struct sockaddr *sap, char *buf, const size_t buflen) ++{ ++ const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; ++ ++ if (sin->sin_family != AF_INET) { ++ xlog(D_GENERAL, "%s: unsupported address family", __func__); ++ return false; ++ } ++ ++ /* ensure '\0' termination */ ++ memset(buf, 0, buflen); ++ ++ if (inet_ntop(AF_INET, (char *)&sin->sin_addr, ++ buf, (socklen_t)buflen) == NULL) { ++ xlog(D_GENERAL, "%s: inet_ntop(3): %m", __func__); ++ return false; ++ } ++ return true; ++} ++#endif /* !HAVE_GETNAMEINFO */ ++ ++/* ++ * Look up the hostname; report exceptional errors. Caller must ++ * call freeaddrinfo(3) if a valid addrinfo is returned. ++ */ ++__attribute_malloc__ ++static struct addrinfo * ++get_addrinfo(const char *hostname, const struct addrinfo *hint) ++{ ++ struct addrinfo *ai = NULL; ++ int error; ++ ++ error = getaddrinfo(hostname, NULL, hint, &ai); ++ switch (error) { ++ case 0: ++ return ai; ++ case EAI_NONAME: ++ break; ++ default: ++ xlog(D_GENERAL, "%s: failed to resolve host %s: %s", ++ __func__, hostname, gai_strerror(error)); ++ } ++ ++ return NULL; ++} ++ ++#ifdef HAVE_GETNAMEINFO ++static _Bool ++get_nameinfo(const struct sockaddr *sap, const socklen_t salen, ++ /*@out@*/ char *buf, const socklen_t buflen) ++{ ++ int error; ++ ++ error = getnameinfo(sap, salen, buf, buflen, NULL, 0, NI_NAMEREQD); ++ if (error != 0) { ++ xlog(D_GENERAL, "%s: failed to resolve address: %s", ++ __func__, gai_strerror(error)); ++ return false; ++ } ++ ++ return true; ++} ++#else /* !HAVE_GETNAMEINFO */ ++static _Bool ++get_nameinfo(const struct sockaddr *sap, ++ __attribute__ ((unused)) const socklen_t salen, ++ /*@out@*/ char *buf, socklen_t buflen) ++{ ++ struct sockaddr_in *sin = (struct sockaddr_in *)(char *)sap; ++ struct hostent *hp; ++ ++ if (sin->sin_family != AF_INET) { ++ xlog(D_GENERAL, "%s: unknown address family: %d", ++ sin->sin_family); ++ return false; ++ } ++ ++ hp = gethostbyaddr((const char *)&(sin->sin_addr.s_addr), ++ sizeof(struct in_addr), AF_INET); ++ if (hp == NULL) { ++ xlog(D_GENERAL, "%s: failed to resolve address: %m", __func__); ++ return false; ++ } ++ ++ strncpy(buf, hp->h_name, (size_t)buflen); ++ return true; ++} ++#endif /* !HAVE_GETNAMEINFO */ ++ ++/** ++ * statd_canonical_name - choose file name for monitor record files ++ * @hostname: C string containing hostname or presentation address ++ * ++ * Returns a '\0'-terminated ASCII string containing a fully qualified ++ * canonical hostname, or NULL if @hostname does not have a reverse ++ * mapping. Caller must free the result with free(3). ++ * ++ * Incoming hostnames are looked up to determine the canonical hostname, ++ * and incoming presentation addresses are converted to canonical ++ * hostnames. ++ * ++ * We won't monitor peers that don't have a reverse map. The canonical ++ * name gives us a key for our monitor list. ++ */ ++__attribute_malloc__ ++char * ++statd_canonical_name(const char *hostname) ++{ ++ struct addrinfo hint = { ++#ifdef IPV6_SUPPORTED ++ .ai_family = AF_UNSPEC, ++#else /* !IPV6_SUPPORTED */ ++ .ai_family = AF_INET, ++#endif /* !IPV6_SUPPORTED */ ++ .ai_flags = AI_NUMERICHOST, ++ .ai_protocol = (int)IPPROTO_UDP, ++ }; ++ char buf[NI_MAXHOST]; ++ struct addrinfo *ai; ++ ++ ai = get_addrinfo(hostname, &hint); ++ if (ai != NULL) { ++ /* @hostname was a presentation address */ ++ _Bool result; ++ result = get_nameinfo(ai->ai_addr, ai->ai_addrlen, ++ buf, (socklen_t)sizeof(buf)); ++ freeaddrinfo(ai); ++ if (!result) ++ return NULL; ++ return strdup(buf); ++ } ++ ++ /* @hostname was a hostname */ ++ hint.ai_flags = AI_CANONNAME; ++ ai = get_addrinfo(hostname, &hint); ++ if (ai == NULL) ++ return NULL; ++ strcpy(buf, ai->ai_canonname); ++ freeaddrinfo(ai); ++ ++ return strdup(buf); ++} ++ ++/** ++ * statd_matchhostname - check if two hostnames are equivalent ++ * @hostname1: C string containing hostname ++ * @hostname2: C string containing hostname ++ * ++ * Returns true if the hostnames are the same, the hostnames resolve ++ * to the same canonical name, or the hostnames resolve to at least ++ * one address that is the same. False is returned if the hostnames ++ * do not match in any of these ways, if either hostname contains ++ * wildcard characters, if either hostname is a netgroup name, or ++ * if an error occurs. ++ */ ++_Bool ++statd_matchhostname(const char *hostname1, const char *hostname2) ++{ ++ struct addrinfo *ai1, *ai2, *results1 = NULL, *results2 = NULL; ++ struct addrinfo hint = { ++ .ai_family = AF_UNSPEC, ++ .ai_flags = AI_CANONNAME, ++ .ai_protocol = (int)IPPROTO_UDP, ++ }; ++ _Bool result = false; ++ ++ if (strcasecmp(hostname1, hostname2) == 0) { ++ result = true; ++ goto out; ++ } ++ ++ results1 = get_addrinfo(hostname1, &hint); ++ if (results1 == NULL) ++ goto out; ++ results2 = get_addrinfo(hostname2, &hint); ++ if (results2 == NULL) ++ goto out; ++ ++ if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) { ++ result = true; ++ goto out; ++ } ++ ++ for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next) ++ for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next) ++ if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) { ++ result = true; ++ break; ++ } ++ ++out: ++ freeaddrinfo(results2); ++ freeaddrinfo(results1); ++ ++ xlog(D_CALL, "%s: hostnames %s", __func__, ++ (result ? "matched" : "did not match")); ++ return result; ++} +diff --git a/utils/statd/log.c b/utils/statd/log.c +deleted file mode 100644 +index a6ca996..0000000 +--- a/utils/statd/log.c ++++ /dev/null +@@ -1,95 +0,0 @@ +-/* +- * Copyright (C) 1995 Olaf Kirch +- * Modified by Jeffrey A. Uphoff, 1995, 1997, 1999. +- * Modified by H.J. Lu, 1998. +- * Modified by Lon Hohberger, Oct. 2000 +- * +- * NSM for Linux. +- */ +- +-/* +- * log.c - logging functions for lockd/statd +- * 260295 okir started with simply syslog logging. +- */ +- +-#ifdef HAVE_CONFIG_H +-#include +-#endif +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include "log.h" +-#include "statd.h" +- +-static pid_t mypid; +- /* Turns on logging to console/stderr. */ +-#if 0 +-static int opt_debug = 0; /* Will be command-line option, eventually */ +-#endif +- +-void log_init(void) +-{ +- if (!(run_mode & MODE_LOG_STDERR)) +- openlog(name_p, LOG_PID | LOG_NDELAY, LOG_DAEMON); +- +- mypid = getpid(); +- +- note(N_WARNING,"Version %s Starting",version_p); +-} +- +-void log_background(void) +-{ +- /* NOP */ +-} +- +-void die(char *fmt, ...) +-{ +- char buffer[1024]; +- va_list ap; +- +- va_start(ap, fmt); +- vsnprintf (buffer, 1024, fmt, ap); +- va_end(ap); +- buffer[1023]=0; +- +- note(N_FATAL, "%s", buffer); +- +-#ifndef DEBUG +- exit (2); +-#else +- abort(); /* make a core */ +-#endif +-} +- +-void note(int level, char *fmt, ...) +-{ +- char buffer[1024]; +- va_list ap; +- +- va_start(ap, fmt); +- vsnprintf (buffer, 1024, fmt, ap); +- va_end(ap); +- buffer[1023]=0; +- +- if ((!(run_mode & MODE_LOG_STDERR)) && (level < N_DEBUG)) { +- syslog(level, "%s", buffer); +- } else if (run_mode & MODE_LOG_STDERR) { +- /* Log everything, including dprintf() stuff to stderr */ +- time_t now; +- struct tm * tm; +- +- time(&now); +- tm = localtime(&now); +- fprintf (stderr, "%02d/%02d/%04d %02d:%02d:%02d %s[%d]: %s\n", +- tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900, +- tm->tm_hour, tm->tm_min, tm->tm_sec, +- name_p, mypid, +- buffer); +- } +-} +diff --git a/utils/statd/log.h b/utils/statd/log.h +deleted file mode 100644 +index fc55d3c..0000000 +--- a/utils/statd/log.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* +- * Copyright (C) 1995 Olaf Kirch +- * Modified by Jeffrey A. Uphoff, 1996, 1997, 1999. +- * Modified by Lon Hohberger, Oct. 2000 +- * +- * NSM for Linux. +- */ +- +-/* +- * logging functionality +- * 260295 okir +- */ +- +-#ifndef _LOCKD_LOG_H_ +-#define _LOCKD_LOG_H_ +- +-#include +- +-void log_init(void); +-void log_background(void); +-void log_enable(int facility); +-int log_enabled(int facility); +-void note(int level, char *fmt, ...); +-void die(char *fmt, ...); +- +-/* +- * Map per-application severity to system severity. What's fatal for +- * lockd is merely an itching spot from the universe's point of view. +- */ +-#define N_CRIT LOG_CRIT +-#define N_FATAL LOG_ERR +-#define N_ERROR LOG_WARNING +-#define N_WARNING LOG_NOTICE +-#define N_DEBUG LOG_DEBUG +- +-#ifdef DEBUG +-#define dprintf note +-#else +-#define dprintf if (run_mode & MODE_LOG_STDERR) note +-#endif +- +-#endif /* _LOCKD_LOG_H_ */ +diff --git a/utils/statd/misc.c b/utils/statd/misc.c +index 7256291..f2a086f 100644 +--- a/utils/statd/misc.c ++++ b/utils/statd/misc.c +@@ -29,8 +29,7 @@ xmalloc (size_t size) + return ((void *)NULL); + + if (!(ptr = malloc (size))) +- /* SHIT! SHIT! SHIT! */ +- die ("malloc failed"); ++ xlog_err ("malloc failed"); + + return (ptr); + } +@@ -46,32 +45,7 @@ xstrdup (const char *string) + + /* Will only fail if underlying malloc() fails (ENOMEM). */ + if (!(result = strdup (string))) +- die ("strdup failed"); ++ xlog_err ("strdup failed"); + + return (result); + } +- +- +-/* +- * Unlinking a file. +- */ +-void +-xunlink (char *path, char *host) +-{ +- char *tozap; +- +- tozap = malloc(strlen(path)+strlen(host)+2); +- if (tozap == NULL) { +- note(N_ERROR, "xunlink: malloc failed: errno %d (%s)", +- errno, strerror(errno)); +- return; +- } +- sprintf (tozap, "%s/%s", path, host); +- +- if (unlink (tozap) == -1) +- note(N_ERROR, "unlink (%s): %s", tozap, strerror (errno)); +- else +- dprintf (N_DEBUG, "Unlinked %s", tozap); +- +- free(tozap); +-} +diff --git a/utils/statd/monitor.c b/utils/statd/monitor.c +index a2c9e2b..325dfd3 100644 +--- a/utils/statd/monitor.c ++++ b/utils/statd/monitor.c +@@ -21,34 +21,38 @@ + #include + #include + ++#include "sockaddr.h" + #include "rpcmisc.h" +-#include "misc.h" ++#include "nsm.h" + #include "statd.h" + #include "notlist.h" + #include "ha-callout.h" + + notify_list * rtnl = NULL; /* Run-time notify list. */ + +-#define LINELEN (4*(8+1)+SM_PRIV_SIZE*2+1) +- + /* + * Reject requests from non-loopback addresses in order + * to prevent attack described in CERT CA-99.05. ++ * ++ * Although the kernel contacts the statd service via only IPv4 ++ * transports, the statd service can receive other requests, such ++ * as SM_NOTIFY, from remote peers via IPv6. + */ +-static int ++static _Bool + caller_is_localhost(struct svc_req *rqstp) + { +- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); +- struct in_addr caller; +- +- caller = sin->sin_addr; +- if (caller.s_addr != htonl(INADDR_LOOPBACK)) { +- note(N_WARNING, +- "Call to statd from non-local host %s", +- inet_ntoa(caller)); +- return 0; +- } +- return 1; ++ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); ++ char buf[INET6_ADDRSTRLEN]; ++ ++ if (!nfs_is_v4_loopback(sap)) ++ goto out_nonlocal; ++ return true; ++ ++out_nonlocal: ++ if (!statd_present_address(sap, buf, sizeof(buf))) ++ buf[0] = '\0'; ++ xlog_warn("SM_MON/SM_UNMON call from non-local host %s", buf); ++ return false; + } + + /* +@@ -61,13 +65,15 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + char *mon_name = argp->mon_id.mon_name, + *my_name = argp->mon_id.my_id.my_name; + struct my_id *id = &argp->mon_id.my_id; +- char *path; + char *cp; +- int fd; + notify_list *clnt; +- struct in_addr my_addr; +- char *dnsname; +- struct hostent *hostinfo = NULL; ++ struct sockaddr_in my_addr = { ++ .sin_family = AF_INET, ++ .sin_addr.s_addr = htonl(INADDR_LOOPBACK), ++ }; ++ char *dnsname = NULL; ++ ++ xlog(D_CALL, "Received SM_MON for %s from %s", mon_name, my_name); + + /* Assume that we'll fail. */ + result.res_stat = STAT_FAIL; +@@ -79,7 +85,6 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + */ + if (!caller_is_localhost(rqstp)) + goto failure; +- my_addr.s_addr = htonl(INADDR_LOOPBACK); + + /* 2. Reject any registrations for non-lockd services. + * +@@ -92,8 +97,7 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + if (id->my_prog != 100021 || + (id->my_proc != 16 && id->my_proc != 24)) + { +- note(N_WARNING, +- "Attempt to register callback to %d/%d", ++ xlog_warn("Attempt to register callback to %d/%d", + id->my_prog, id->my_proc); + goto failure; + } +@@ -105,12 +109,9 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + + /* must check for /'s in hostname! See CERT's CA-96.09 for details. */ + if (strchr(mon_name, '/') || mon_name[0] == '.') { +- note(N_CRIT, "SM_MON request for hostname containing '/' " ++ xlog(L_ERROR, "SM_MON request for hostname containing '/' " + "or starting '.': %s", mon_name); +- note(N_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); +- goto failure; +- } else if ((hostinfo = gethostbyname(mon_name)) == NULL) { +- note(N_WARNING, "gethostbyname error for %s", mon_name); ++ xlog(L_ERROR, "POSSIBLE SPOOF/ATTACK ATTEMPT!"); + goto failure; + } + +@@ -124,15 +125,13 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + * Now choose a hostname to use for matching. We cannot + * really trust much in the incoming NOTIFY, so to make + * sure that multi-homed hosts work nicely, we get an +- * FQDN now, and use that for matching ++ * FQDN now, and use that for matching. + */ +- hostinfo = gethostbyaddr(hostinfo->h_addr, +- hostinfo->h_length, +- hostinfo->h_addrtype); +- if (hostinfo) +- dnsname = xstrdup(hostinfo->h_name); +- else +- dnsname = xstrdup(my_name); ++ dnsname = statd_canonical_name(mon_name); ++ if (dnsname == NULL) { ++ xlog(L_WARNING, "No canonical hostname found for %s", mon_name); ++ goto failure; ++ } + + /* Now check to see if this is a duplicate, and warn if so. + * I will also return STAT_FAIL. (I *think* this is how I should +@@ -146,18 +145,19 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + clnt = rtnl; + + while ((clnt = nlist_gethost(clnt, mon_name, 0))) { +- if (matchhostname(NL_MY_NAME(clnt), my_name) && ++ if (statd_matchhostname(NL_MY_NAME(clnt), my_name) && + NL_MY_PROC(clnt) == id->my_proc && + NL_MY_PROG(clnt) == id->my_prog && + NL_MY_VERS(clnt) == id->my_vers && + memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) { + /* Hey! We already know you guys! */ +- dprintf(N_DEBUG, ++ xlog(D_GENERAL, + "Duplicate SM_MON request for %s " + "from procedure on %s", + mon_name, my_name); + + /* But we'll let you pass anyway. */ ++ free(dnsname); + goto success; + } + clnt = NL_NEXT(clnt); +@@ -168,11 +168,11 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + * doesn't fail. (I should probably fix this assumption.) + */ + if (!(clnt = nlist_new(my_name, mon_name, 0))) { +- note(N_WARNING, "out of memory"); ++ free(dnsname); ++ xlog_warn("out of memory"); + goto failure; + } + +- NL_ADDR(clnt) = my_addr; + NL_MY_PROG(clnt) = id->my_prog; + NL_MY_VERS(clnt) = id->my_vers; + NL_MY_PROC(clnt) = id->my_proc; +@@ -182,40 +182,16 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + /* + * Now, Create file on stable storage for host. + */ +- +- path=xmalloc(strlen(SM_DIR)+strlen(dnsname)+2); +- sprintf(path, "%s/%s", SM_DIR, dnsname); +- if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT|O_APPEND, +- S_IRUSR|S_IWUSR)) < 0) { +- /* Didn't fly. We won't monitor. */ +- note(N_ERROR, "creat(%s) failed: %s", path, strerror (errno)); ++ if (!nsm_insert_monitored_host(dnsname, ++ (struct sockaddr *)(char *)&my_addr, argp)) { + nlist_free(NULL, clnt); +- free(path); + goto failure; + } +- { +- char buf[LINELEN + 1 + SM_MAXSTRLEN*2 + 4]; +- char *e; +- int i; +- e = buf + sprintf(buf, "%08x %08x %08x %08x ", +- my_addr.s_addr, id->my_prog, +- id->my_vers, id->my_proc); +- for (i=0; ipriv[i])); +- if (e+1-buf != LINELEN) abort(); +- e += sprintf(e, " %s %s\n", mon_name, my_name); +- if (write(fd, buf, e-buf) != (e-buf)) { +- note(N_WARNING, "writing to %s failed: errno %d (%s)", +- path, errno, strerror(errno)); +- } +- } + +- free(path); + /* PRC: do the HA callout: */ + ha_callout("add-client", mon_name, my_name, -1); + nlist_insert(&rtnl, clnt); +- close(fd); +- dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name); ++ xlog(D_GENERAL, "MONITORING %s for %s", mon_name, my_name); + success: + result.res_stat = STAT_SUCC; + /* SUN's sm_inter.x says this should be "state number of local site". +@@ -232,75 +208,49 @@ sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp) + return (&result); + + failure: +- note(N_WARNING, "STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); ++ xlog_warn("STAT_FAIL to %s for SM_MON of %s", my_name, mon_name); + return (&result); + } + +-void load_state(void) ++static unsigned int ++load_one_host(const char *hostname, ++ __attribute__ ((unused)) const struct sockaddr *sap, ++ const struct mon *m, ++ __attribute__ ((unused)) const time_t timestamp) + { +- DIR *d; +- struct dirent *de; +- char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; +- +- d = opendir(SM_DIR); +- if (!d) +- return; +- while ((de = readdir(d))) { +- char *path; +- FILE *f; +- int p; +- +- if (de->d_name[0] == '.') +- continue; +- path = xmalloc(strlen(SM_DIR)+strlen(de->d_name)+2); +- sprintf(path, "%s/%s", SM_DIR, de->d_name); +- f = fopen(path, "r"); +- free(path); +- if (f == NULL) +- continue; +- while (fgets(buf, sizeof(buf), f) != NULL) { +- int addr, proc, prog, vers; +- char priv[SM_PRIV_SIZE]; +- char *monname, *myname; +- char *b; +- int i; +- notify_list *clnt; +- +- buf[sizeof(buf)-1] = 0; +- b = strchr(buf, '\n'); +- if (b) *b = 0; +- sscanf(buf, "%x %x %x %x ", +- &addr, &prog, &vers, &proc); +- b = buf+36; +- for (i=0; idns_name = xstrdup(de->d_name); +- memcpy(NL_PRIV(clnt), priv, SM_PRIV_SIZE); +- nlist_insert(&rtnl, clnt); +- } +- fclose(f); ++ notify_list *clnt; ++ ++ clnt = nlist_new(m->mon_id.my_id.my_name, ++ m->mon_id.mon_name, 0); ++ if (clnt == NULL) ++ return 0; ++ ++ clnt->dns_name = strdup(hostname); ++ if (clnt->dns_name == NULL) { ++ nlist_free(NULL, clnt); ++ return 0; + } +- closedir(d); +-} + ++ xlog(D_GENERAL, "Adding record for %s to the monitor list...", ++ hostname); + ++ NL_MY_PROG(clnt) = m->mon_id.my_id.my_prog; ++ NL_MY_VERS(clnt) = m->mon_id.my_id.my_vers; ++ NL_MY_PROC(clnt) = m->mon_id.my_id.my_proc; ++ memcpy(NL_PRIV(clnt), m->priv, SM_PRIV_SIZE); + ++ nlist_insert(&rtnl, clnt); ++ return 1; ++} ++ ++void load_state(void) ++{ ++ unsigned int count; ++ ++ count = nsm_load_monitor_list(load_one_host); ++ if (count) ++ xlog(D_GENERAL, "Loaded %u previously monitored hosts"); ++} + + /* + * Services SM_UNMON requests. +@@ -320,6 +270,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) + struct my_id *id = &argp->my_id; + char *cp; + ++ xlog(D_CALL, "Received SM_UNMON for %s from %s", mon_name, my_name); ++ + result.state = MY_STATE; + + if (!caller_is_localhost(rqstp)) +@@ -333,9 +285,8 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) + + /* Check if we're monitoring anyone. */ + if (rtnl == NULL) { +- note(N_WARNING, +- "Received SM_UNMON request from %s for %s while not " +- "monitoring any hosts.", my_name, argp->mon_name); ++ xlog_warn("Received SM_UNMON request from %s for %s while not " ++ "monitoring any hosts", my_name, argp->mon_name); + return (&result); + } + clnt = rtnl; +@@ -347,18 +298,19 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) + * entry winds up in the list the way I'm currently handling them.) + */ + while ((clnt = nlist_gethost(clnt, mon_name, 0))) { +- if (matchhostname(NL_MY_NAME(clnt), my_name) && ++ if (statd_matchhostname(NL_MY_NAME(clnt), my_name) && + NL_MY_PROC(clnt) == id->my_proc && + NL_MY_PROG(clnt) == id->my_prog && + NL_MY_VERS(clnt) == id->my_vers) { + /* Match! */ +- dprintf(N_DEBUG, "UNMONITORING %s for %s", ++ xlog(D_GENERAL, "UNMONITORING %s for %s", + mon_name, my_name); + + /* PRC: do the HA callout: */ + ha_callout("del-client", mon_name, my_name, -1); + +- xunlink(SM_DIR, clnt->dns_name); ++ nsm_delete_monitored_host(clnt->dns_name, ++ mon_name, my_name); + nlist_free(&rtnl, clnt); + + return (&result); +@@ -367,7 +319,7 @@ sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp) + } + + failure: +- note(N_WARNING, "Received erroneous SM_UNMON request from %s for %s", ++ xlog_warn("Received erroneous SM_UNMON request from %s for %s", + my_name, mon_name); + return (&result); + } +@@ -381,13 +333,15 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) + notify_list *clnt; + char *my_name = argp->my_name; + ++ xlog(D_CALL, "Received SM_UNMON_ALL for %s", my_name); ++ + if (!caller_is_localhost(rqstp)) + goto failure; + + result.state = MY_STATE; + + if (rtnl == NULL) { +- note(N_WARNING, "Received SM_UNMON_ALL request from %s " ++ xlog_warn("Received SM_UNMON_ALL request from %s " + "while not monitoring any hosts", my_name); + return (&result); + } +@@ -401,7 +355,7 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) + char mon_name[SM_MAXSTRLEN + 1]; + notify_list *temp; + +- dprintf(N_DEBUG, ++ xlog(D_GENERAL, + "UNMONITORING (SM_UNMON_ALL) %s for %s", + NL_MON_NAME(clnt), NL_MY_NAME(clnt)); + strncpy(mon_name, NL_MON_NAME(clnt), +@@ -410,7 +364,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) + temp = NL_NEXT(clnt); + /* PRC: do the HA callout: */ + ha_callout("del-client", mon_name, my_name, -1); +- xunlink(SM_DIR, clnt->dns_name); ++ nsm_delete_monitored_host(clnt->dns_name, ++ mon_name, my_name); + nlist_free(&rtnl, clnt); + ++count; + clnt = temp; +@@ -419,8 +374,8 @@ sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp) + } + + if (!count) { +- dprintf(N_DEBUG, "SM_UNMON_ALL request from %s with no " +- "SM_MON requests from it.", my_name); ++ xlog(D_GENERAL, "SM_UNMON_ALL request from %s with no " ++ "SM_MON requests from it", my_name); + } + + failure: +diff --git a/utils/statd/notlist.c b/utils/statd/notlist.c +index 1698c26..0341c15 100644 +--- a/utils/statd/notlist.c ++++ b/utils/statd/notlist.c +@@ -17,7 +17,6 @@ + #endif + + #include +-#include "misc.h" + #include "statd.h" + #include "notlist.h" + +@@ -190,7 +189,6 @@ nlist_clone(notify_list *entry) + NL_MY_PROG(new) = NL_MY_PROG(entry); + NL_MY_VERS(new) = NL_MY_VERS(entry); + NL_MY_PROC(new) = NL_MY_PROC(entry); +- NL_ADDR(new) = NL_ADDR(entry); + memcpy(NL_PRIV(new), NL_PRIV(entry), SM_PRIV_SIZE); + + return new; +@@ -234,7 +232,8 @@ nlist_gethost(notify_list *list, char *host, int myname) + notify_list *lp; + + for (lp = list; lp; lp = lp->next) { +- if (matchhostname(host, myname? NL_MY_NAME(lp) : NL_MON_NAME(lp))) ++ if (statd_matchhostname(host, ++ myname? NL_MY_NAME(lp) : NL_MON_NAME(lp))) + return lp; + } + +diff --git a/utils/statd/notlist.h b/utils/statd/notlist.h +index 664c9d8..6ed0da8 100644 +--- a/utils/statd/notlist.h ++++ b/utils/statd/notlist.h +@@ -12,15 +12,14 @@ + */ + struct notify_list { + mon mon; /* Big honkin' NSM structure. */ +- struct in_addr addr; /* IP address for callback. */ +- unsigned short port; /* port number for callback */ ++ in_port_t port; /* port number for callback */ + short int times; /* Counter used for various things. */ + int state; /* For storing notified state for callbacks. */ + char *dns_name; /* used for matching incoming + * NOTIFY requests */ + struct notify_list *next; /* Linked list forward pointer. */ + struct notify_list *prev; /* Linked list backward pointer. */ +- u_int32_t xid; /* XID of MS_NOTIFY RPC call */ ++ uint32_t xid; /* XID of MS_NOTIFY RPC call */ + time_t when; /* notify: timeout for re-xmit */ + }; + +@@ -53,7 +52,6 @@ extern notify_list * nlist_gethost(notify_list *, char *, int); + #define NL_FIRST NL_NEXT + #define NL_PREV(L) ((L)->prev) + #define NL_DATA(L) ((L)->mon) +-#define NL_ADDR(L) ((L)->addr) + #define NL_STATE(L) ((L)->state) + #define NL_TIMES(L) ((L)->times) + #define NL_MON_ID(L) (NL_DATA((L)).mon_id) +diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c +index cc1a4a4..0e52fe2 100644 +--- a/utils/statd/rmtcall.c ++++ b/utils/statd/rmtcall.c +@@ -37,22 +37,19 @@ + #include + #include + #include +-#ifdef HAVE_IFADDRS_H +-#include +-#endif /* HAVE_IFADDRS_H */ ++ + #include "sm_inter.h" + #include "statd.h" + #include "notlist.h" +-#include "log.h" + #include "ha-callout.h" + ++#include "nsm.h" ++#include "nfsrpc.h" ++ + #if SIZEOF_SOCKLEN_T - 0 == 0 + #define socklen_t int + #endif + +-#define MAXMSGSIZE (2048 / sizeof(unsigned int)) +- +-static unsigned long xid = 0; /* RPC XID counter */ + static int sockfd = -1; /* notify socket */ + + /* +@@ -81,7 +78,7 @@ statd_get_socket(void) + if (sockfd >= 0) close(sockfd); + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { +- note(N_CRIT, "%s: Can't create socket: %m", __func__); ++ xlog(L_ERROR, "%s: Can't create socket: %m", __func__); + return -1; + } + +@@ -91,7 +88,7 @@ statd_get_socket(void) + sin.sin_addr.s_addr = INADDR_ANY; + + if (bindresvport(sockfd, &sin) < 0) { +- dprintf(N_WARNING, "%s: can't bind to reserved port", ++ xlog(D_GENERAL, "%s: can't bind to reserved port", + __func__); + break; + } +@@ -104,112 +101,37 @@ statd_get_socket(void) + return sockfd; + } + +-static unsigned long +-xmit_call(struct sockaddr_in *sin, +- u_int32_t prog, u_int32_t vers, u_int32_t proc, +- xdrproc_t func, void *obj) +-/* __u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */ +-{ +- unsigned int msgbuf[MAXMSGSIZE], msglen; +- struct rpc_msg mesg; +- struct pmap pmap; +- XDR xdr, *xdrs = &xdr; +- int err; +- +- if (!xid) +- xid = getpid() + time(NULL); +- +- mesg.rm_xid = ++xid; +- mesg.rm_direction = CALL; +- mesg.rm_call.cb_rpcvers = 2; +- if (sin->sin_port == 0) { +- sin->sin_port = htons(PMAPPORT); +- mesg.rm_call.cb_prog = PMAPPROG; +- mesg.rm_call.cb_vers = PMAPVERS; +- mesg.rm_call.cb_proc = PMAPPROC_GETPORT; +- pmap.pm_prog = prog; +- pmap.pm_vers = vers; +- pmap.pm_prot = IPPROTO_UDP; +- pmap.pm_port = 0; +- func = (xdrproc_t) xdr_pmap; +- obj = &pmap; +- } else { +- mesg.rm_call.cb_prog = prog; +- mesg.rm_call.cb_vers = vers; +- mesg.rm_call.cb_proc = proc; +- } +- mesg.rm_call.cb_cred.oa_flavor = AUTH_NULL; +- mesg.rm_call.cb_cred.oa_base = (caddr_t) NULL; +- mesg.rm_call.cb_cred.oa_length = 0; +- mesg.rm_call.cb_verf.oa_flavor = AUTH_NULL; +- mesg.rm_call.cb_verf.oa_base = (caddr_t) NULL; +- mesg.rm_call.cb_verf.oa_length = 0; +- +- /* Create XDR memory object for encoding */ +- xdrmem_create(xdrs, (caddr_t) msgbuf, sizeof(msgbuf), XDR_ENCODE); +- +- /* Encode the RPC header part and payload */ +- if (!xdr_callmsg(xdrs, &mesg) || !func(xdrs, obj)) { +- dprintf(N_WARNING, "%s: can't encode RPC message!", __func__); +- xdr_destroy(xdrs); +- return 0; +- } +- +- /* Get overall length of datagram */ +- msglen = xdr_getpos(xdrs); +- +- if ((err = sendto(sockfd, msgbuf, msglen, 0, +- (struct sockaddr *) sin, sizeof(*sin))) < 0) { +- dprintf(N_WARNING, "%s: sendto failed: %m", __func__); +- } else if (err != msglen) { +- dprintf(N_WARNING, "%s: short write: %m", __func__); +- } +- +- xdr_destroy(xdrs); +- +- return err == msglen? xid : 0; +-} +- + static notify_list * +-recv_rply(struct sockaddr_in *sin, u_long *portp) ++recv_rply(u_long *portp) + { +- unsigned int msgbuf[MAXMSGSIZE], msglen; +- struct rpc_msg mesg; ++ char msgbuf[NSM_MAXMSGSIZE]; ++ ssize_t msglen; + notify_list *lp = NULL; +- XDR xdr, *xdrs = &xdr; +- socklen_t alen = sizeof(*sin); +- +- /* Receive message */ +- if ((msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, +- (struct sockaddr *) sin, &alen)) < 0) { +- dprintf(N_WARNING, "%s: recvfrom failed: %m", __func__); ++ XDR xdr; ++ struct sockaddr_in sin; ++ socklen_t alen = (socklen_t)sizeof(sin); ++ uint32_t xid; ++ ++ memset(msgbuf, 0, sizeof(msgbuf)); ++ msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0, ++ (struct sockaddr *)(char *)&sin, &alen); ++ if (msglen == (ssize_t)-1) { ++ xlog_warn("%s: recvfrom failed: %m", __func__); + return NULL; + } + +- /* Create XDR object for decoding buffer */ +- xdrmem_create(xdrs, (caddr_t) msgbuf, msglen, XDR_DECODE); +- +- memset(&mesg, 0, sizeof(mesg)); +- mesg.rm_reply.rp_acpt.ar_results.where = NULL; +- mesg.rm_reply.rp_acpt.ar_results.proc = (xdrproc_t) xdr_void; +- +- if (!xdr_replymsg(xdrs, &mesg)) { +- note(N_WARNING, "%s: can't decode RPC message!", __func__); ++ memset(&xdr, 0, sizeof(xdr)); ++ xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); ++ xid = nsm_parse_reply(&xdr); ++ if (xid == 0) + goto done; +- } ++ if (sin.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { ++ struct in_addr addr = sin.sin_addr; ++ char buf[INET_ADDRSTRLEN]; + +- if (mesg.rm_reply.rp_stat != 0) { +- note(N_WARNING, "%s: [%s] RPC status %d", +- __func__, +- inet_ntoa(sin->sin_addr), +- mesg.rm_reply.rp_stat); +- goto done; +- } +- if (mesg.rm_reply.rp_acpt.ar_stat != 0) { +- note(N_WARNING, "%s: [%s] RPC status %d", +- __func__, +- inet_ntoa(sin->sin_addr), +- mesg.rm_reply.rp_acpt.ar_stat); ++ xlog_warn("%s: Unrecognized reply from %s", __func__, ++ inet_ntop(AF_INET, &addr, buf, ++ (socklen_t)sizeof(buf))); + goto done; + } + +@@ -217,32 +139,15 @@ recv_rply(struct sockaddr_in *sin, u_long *portp) + /* LH - this was a bug... it should have been checking + * the xid from the response message from the client, + * not the static, internal xid */ +- if (lp->xid != mesg.rm_xid) ++ if (lp->xid != xid) + continue; +- if (lp->addr.s_addr != sin->sin_addr.s_addr) { +- char addr [18]; +- strncpy (addr, inet_ntoa(lp->addr), +- sizeof (addr) - 1); +- addr [sizeof (addr) - 1] = '\0'; +- dprintf(N_WARNING, "%s: address mismatch: " +- "expected %s, got %s", __func__, +- addr, inet_ntoa(sin->sin_addr)); +- } +- if (lp->port == 0) { +- if (!xdr_u_long(xdrs, portp)) { +- note(N_WARNING, +- "%s: [%s] can't decode reply body!", +- __func__, +- inet_ntoa(sin->sin_addr)); +- lp = NULL; +- goto done; +- } +- } ++ if (lp->port == 0) ++ *portp = nsm_recv_getport(&xdr); + break; + } + + done: +- xdr_destroy(xdrs); ++ xdr_destroy(&xdr); + return lp; + } + +@@ -253,15 +158,10 @@ static int + process_entry(notify_list *lp) + { + struct sockaddr_in sin; +- struct status new_status; +- xdrproc_t func; +- void *objp; +- u_int32_t proc, vers, prog; +-/* __u32 proc, vers, prog; */ + + if (NL_TIMES(lp) == 0) { +- note(N_DEBUG, "%s: Cannot notify %s, giving up.", +- __func__, inet_ntoa(NL_ADDR(lp))); ++ xlog(D_GENERAL, "%s: Cannot notify localhost, giving up", ++ __func__); + return 0; + } + +@@ -270,23 +170,31 @@ process_entry(notify_list *lp) + sin.sin_port = lp->port; + /* LH - moved address into switch */ + +- prog = NL_MY_PROG(lp); +- vers = NL_MY_VERS(lp); +- proc = NL_MY_PROC(lp); +- + /* __FORCE__ loopback for callbacks to lockd ... */ + /* Just in case we somehow ignored it thus far */ + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + +- func = (xdrproc_t) xdr_status; +- objp = &new_status; +- new_status.mon_name = NL_MON_NAME(lp); +- new_status.state = NL_STATE(lp); +- memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE); ++ if (sin.sin_port == 0) ++ lp->xid = nsm_xmit_getport(sockfd, &sin, ++ (rpcprog_t)NL_MY_PROG(lp), ++ (rpcvers_t)NL_MY_VERS(lp)); ++ else { ++ struct mon m; ++ ++ memcpy(m.priv, NL_PRIV(lp), SM_PRIV_SIZE); + +- lp->xid = xmit_call(&sin, prog, vers, proc, func, objp); +- if (!lp->xid) { +- note(N_WARNING, "%s: failed to notify port %d", ++ m.mon_id.mon_name = NL_MON_NAME(lp); ++ m.mon_id.my_id.my_name = NULL; ++ m.mon_id.my_id.my_prog = NL_MY_PROG(lp); ++ m.mon_id.my_id.my_vers = NL_MY_VERS(lp); ++ m.mon_id.my_id.my_proc = NL_MY_PROC(lp); ++ ++ lp->xid = nsm_xmit_nlmcall(sockfd, ++ (struct sockaddr *)(char *)&sin, ++ (socklen_t)sizeof(sin), &m, NL_STATE(lp)); ++ } ++ if (lp->xid == 0) { ++ xlog_warn("%s: failed to notify port %d", + __func__, ntohs(lp->port)); + } + NL_TIMES(lp) -= 1; +@@ -300,14 +208,13 @@ process_entry(notify_list *lp) + int + process_reply(FD_SET_TYPE *rfds) + { +- struct sockaddr_in sin; + notify_list *lp; + u_long port; + + if (sockfd == -1 || !FD_ISSET(sockfd, rfds)) + return 0; + +- if (!(lp = recv_rply(&sin, &port))) ++ if (!(lp = recv_rply(&port))) + return 1; + + if (lp->port == 0) { +@@ -319,10 +226,10 @@ process_reply(FD_SET_TYPE *rfds) + nlist_insert_timer(¬ify, lp); + return 1; + } +- note(N_WARNING, "%s: [%s] service %d not registered", +- __func__, inet_ntoa(lp->addr), NL_MY_PROG(lp)); ++ xlog_warn("%s: service %d not registered on localhost", ++ __func__, NL_MY_PROG(lp)); + } else { +- dprintf(N_DEBUG, "%s: Callback to %s (for %d) succeeded.", ++ xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded", + __func__, NL_MY_NAME(lp), NL_MON_NAME(lp)); + } + nlist_free(¬ify, lp); +@@ -346,8 +253,8 @@ process_notify_list(void) + nlist_remove(¬ify, entry); + nlist_insert_timer(¬ify, entry); + } else { +- note(N_ERROR, +- "%s: Can't callback %s (%d,%d), giving up.", ++ xlog(L_ERROR, ++ "%s: Can't callback %s (%d,%d), giving up", + __func__, + NL_MY_NAME(entry), + NL_MY_PROG(entry), +diff --git a/utils/statd/simu.c b/utils/statd/simu.c +index a7ecb85..825e428 100644 +--- a/utils/statd/simu.c ++++ b/utils/statd/simu.c +@@ -8,8 +8,10 @@ + #include + #endif + ++#include + #include + ++#include "sockaddr.h" + #include "rpcmisc.h" + #include "statd.h" + #include "notlist.h" +@@ -19,32 +21,29 @@ extern void my_svc_exit (void); + + /* + * Services SM_SIMU_CRASH requests. ++ * ++ * Although the kernel contacts the statd service via only IPv4 ++ * transports, the statd service can receive other requests, such ++ * as SM_NOTIFY, from remote peers via IPv6. + */ + void * +-sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp) ++sm_simu_crash_1_svc (__attribute__ ((unused)) void *argp, struct svc_req *rqstp) + { +- struct sockaddr_in *sin = nfs_getrpccaller_in(rqstp->rq_xprt); ++ struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt); ++ char buf[INET6_ADDRSTRLEN]; + static char *result = NULL; +- struct in_addr caller; + +- if (sin->sin_family != AF_INET) { +- note(N_WARNING, "Call to statd from non-AF_INET address"); +- goto failure; +- } ++ xlog(D_CALL, "Received SM_SIMU_CRASH"); + +- caller = sin->sin_addr; +- if (caller.s_addr != htonl(INADDR_LOOPBACK)) { +- note(N_WARNING, "Call to statd from non-local host %s", +- inet_ntoa(caller)); +- goto failure; +- } ++ if (!nfs_is_v4_loopback(sap)) ++ goto out_nonlocal; + +- if (ntohs(sin->sin_port) >= 1024) { +- note(N_WARNING, "Call to statd-simu-crash from unprivileged port"); ++ if ((int)nfs_get_port(sap) >= IPPORT_RESERVED) { ++ xlog_warn("SM_SIMU_CRASH call from unprivileged port"); + goto failure; + } + +- note (N_WARNING, "*** SIMULATING CRASH! ***"); ++ xlog_warn("*** SIMULATING CRASH! ***"); + my_svc_exit (); + + if (rtnl) +@@ -52,4 +51,10 @@ sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp) + + failure: + return ((void *)&result); ++ ++ out_nonlocal: ++ if (!statd_present_address(sap, buf, sizeof(buf))) ++ buf[0] = '\0'; ++ xlog_warn("SM_SIMU_CRASH call from non-local host %s", buf); ++ goto failure; + } +diff --git a/utils/statd/simulate.c b/utils/statd/simulate.c +index de8f1c9..4ed1468 100644 +--- a/utils/statd/simulate.c ++++ b/utils/statd/simulate.c +@@ -38,7 +38,9 @@ extern void svc_exit (void); + void + simulator (int argc, char **argv) + { +- log_enable (1); ++ xlog_stderr (1); ++ xlog_syslog (0); ++ xlog_open ("statd simulator"); + + if (argc == 2) + if (!strcasecmp (*argv, "crash")) +@@ -61,7 +63,7 @@ simulator (int argc, char **argv) + simulate_mon (*(&argv[1]), *(&argv[2]), *(&argv[3]), *(&argv[4]), + *(&argv[5])); + } +- die ("WTF? Give me something I can use!"); ++ xlog_err ("WTF? Give me something I can use!"); + } + + static void +@@ -72,11 +74,11 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy, + sm_stat_res *result; + mon mon; + +- dprintf (N_DEBUG, "Calling %s (as %s) to monitor %s", calling, as, ++ xlog (D_GENERAL, "Calling %s (as %s) to monitor %s", calling, as, + monitoring); + + if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) +- die ("%s", clnt_spcreateerror ("clnt_create")); ++ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); + + memcpy (mon.priv, fool, SM_PRIV_SIZE); + mon.mon_id.my_id.my_name = xstrdup (as); +@@ -87,16 +89,15 @@ simulate_mon (char *calling, char *monitoring, char *as, char *proggy, + mon.mon_id.mon_name = monitoring; + + if (!(result = sm_mon_1 (&mon, client))) +- die ("%s", clnt_sperror (client, "sm_mon_1")); ++ xlog_err ("%s", clnt_sperror (client, "sm_mon_1")); + + free (mon.mon_id.my_id.my_name); + + if (result->res_stat != STAT_SUCC) { +- note (N_FATAL, "SM_MON request failed, state: %d", result->state); +- exit (0); ++ xlog_err ("SM_MON request failed, state: %d", result->state); + } else { +- dprintf (N_DEBUG, "SM_MON result successful, state: %d\n", result->state); +- dprintf (N_DEBUG, "Waiting for callback."); ++ xlog (D_GENERAL, "SM_MON result successful, state: %d\n", result->state); ++ xlog (D_GENERAL, "Waiting for callback"); + daemon_simulator (); + exit (0); + } +@@ -109,11 +110,11 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy) + sm_stat *result; + mon_id mon_id; + +- dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor %s", calling, as, ++ xlog (D_GENERAL, "Calling %s (as %s) to unmonitor %s", calling, as, + unmonitoring); + + if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) +- die ("%s", clnt_spcreateerror ("clnt_create")); ++ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); + + mon_id.my_id.my_name = xstrdup (as); + mon_id.my_id.my_prog = atoi (proggy) * SIM_SM_PROG; +@@ -122,10 +123,10 @@ simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy) + mon_id.mon_name = unmonitoring; + + if (!(result = sm_unmon_1 (&mon_id, client))) +- die ("%s", clnt_sperror (client, "sm_unmon_1")); ++ xlog_err ("%s", clnt_sperror (client, "sm_unmon_1")); + + free (mon_id.my_id.my_name); +- dprintf (N_DEBUG, "SM_UNMON request returned state: %d\n", result->state); ++ xlog (D_GENERAL, "SM_UNMON request returned state: %d\n", result->state); + exit (0); + } + +@@ -136,10 +137,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy) + sm_stat *result; + my_id my_id; + +- dprintf (N_DEBUG, "Calling %s (as %s) to unmonitor all hosts", calling, as); ++ xlog (D_GENERAL, "Calling %s (as %s) to unmonitor all hosts", calling, as); + + if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) +- die ("%s", clnt_spcreateerror ("clnt_create")); ++ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); + + my_id.my_name = xstrdup (as); + my_id.my_prog = atoi (proggy) * SIM_SM_PROG; +@@ -147,10 +148,10 @@ simulate_unmon_all (char *calling, char *as, char *proggy) + my_id.my_proc = SIM_SM_MON; + + if (!(result = sm_unmon_all_1 (&my_id, client))) +- die ("%s", clnt_sperror (client, "sm_unmon_all_1")); ++ xlog_err ("%s", clnt_sperror (client, "sm_unmon_all_1")); + + free (my_id.my_name); +- dprintf (N_DEBUG, "SM_UNMON_ALL request returned state: %d\n", result->state); ++ xlog (D_GENERAL, "SM_UNMON_ALL request returned state: %d\n", result->state); + exit (0); + } + +@@ -160,10 +161,10 @@ simulate_crash (char *host) + CLIENT *client; + + if ((client = clnt_create (host, SM_PROG, SM_VERS, "udp")) == NULL) +- die ("%s", clnt_spcreateerror ("clnt_create")); ++ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); + + if (!sm_simu_crash_1 (NULL, client)) +- die ("%s", clnt_sperror (client, "sm_simu_crash_1")); ++ xlog_err ("%s", clnt_sperror (client, "sm_simu_crash_1")); + + exit (0); + } +@@ -176,18 +177,18 @@ simulate_stat (char *calling, char *monitoring) + sm_stat_res *result; + + if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL) +- die ("%s", clnt_spcreateerror ("clnt_create")); ++ xlog_err ("%s", clnt_spcreateerror ("clnt_create")); + + checking.mon_name = monitoring; + + if (!(result = sm_stat_1 (&checking, client))) +- die ("%s", clnt_sperror (client, "sm_stat_1")); ++ xlog_err ("%s", clnt_sperror (client, "sm_stat_1")); + + if (result->res_stat == STAT_SUCC) +- dprintf (N_DEBUG, "STAT_SUCC from %s for %s, state: %d", calling, ++ xlog (D_GENERAL, "STAT_SUCC from %s for %s, state: %d", calling, + monitoring, result->state); + else +- dprintf (N_DEBUG, "STAT_FAIL from %s for %s, state: %d", calling, ++ xlog (D_GENERAL, "STAT_FAIL from %s for %s, state: %d", calling, + monitoring, result->state); + + exit (0); +@@ -196,9 +197,8 @@ simulate_stat (char *calling, char *monitoring) + static void + sim_killer (int sig) + { +- note (N_FATAL, "Simulator caught signal %d, un-registering and exiting.", sig); + pmap_unset (sim_port, SIM_SM_VERS); +- exit (0); ++ xlog_err ("Simulator caught signal %d, un-registering and exiting", sig); + } + + static void +@@ -219,7 +219,7 @@ sim_sm_mon_1_svc (struct status *argp, struct svc_req *rqstp) + { + static char *result; + +- dprintf (N_DEBUG, "Recieved state %d for mon_name %s (opaque \"%s\")", ++ xlog (D_GENERAL, "Recieved state %d for mon_name %s (opaque \"%s\")", + argp->state, argp->mon_name, argp->priv); + svc_exit (); + return ((void *)&result); +diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c +index 72dcff4..3259a3e 100644 +--- a/utils/statd/sm-notify.c ++++ b/utils/statd/sm-notify.c +@@ -8,6 +8,7 @@ + #include + #endif + ++#include + #include + #include + #include +@@ -28,129 +29,114 @@ + #include + #include + +-#ifndef BASEDIR +-# ifdef NFS_STATEDIR +-# define BASEDIR NFS_STATEDIR +-# else +-# define BASEDIR "/var/lib/nfs" +-# endif +-#endif +- +-#define DEFAULT_SM_STATE_PATH BASEDIR "/state" +-#define DEFAULT_SM_DIR_PATH BASEDIR "/sm" +-#define DEFAULT_SM_BAK_PATH DEFAULT_SM_DIR_PATH ".bak" ++#include "sockaddr.h" ++#include "xlog.h" ++#include "nsm.h" ++#include "nfsrpc.h" + +-char *_SM_BASE_PATH = BASEDIR; +-char *_SM_STATE_PATH = DEFAULT_SM_STATE_PATH; +-char *_SM_DIR_PATH = DEFAULT_SM_DIR_PATH; +-char *_SM_BAK_PATH = DEFAULT_SM_BAK_PATH; ++#ifndef HAVE_DECL_AI_ADDRCONFIG ++#define AI_ADDRCONFIG 0 ++#endif + +-#define NSM_PROG 100024 +-#define NSM_PROGRAM 100024 +-#define NSM_VERSION 1 + #define NSM_TIMEOUT 2 +-#define NSM_NOTIFY 6 + #define NSM_MAX_TIMEOUT 120 /* don't make this too big */ +-#define MAXMSGSIZE 256 + + struct nsm_host { + struct nsm_host * next; + char * name; +- char * path; +- struct sockaddr_storage addr; ++ char * mon_name; ++ char * my_name; + struct addrinfo *ai; + time_t last_used; + time_t send_next; + unsigned int timeout; + unsigned int retries; +- unsigned int xid; ++ uint32_t xid; + }; + + static char nsm_hostname[256]; +-static uint32_t nsm_state; ++static int nsm_state; ++static int nsm_family = AF_INET; + static int opt_debug = 0; +-static int opt_quiet = 0; +-static int opt_update_state = 1; ++static _Bool opt_update_state = true; + static unsigned int opt_max_retry = 15 * 60; +-static char * opt_srcaddr = 0; +-static uint16_t opt_srcport = 0; +-static int log_syslog = 0; ++static char * opt_srcaddr = NULL; ++static char * opt_srcport = NULL; + +-static unsigned int nsm_get_state(int); +-static void notify(void); ++static void notify(const int sock); + static int notify_host(int, struct nsm_host *); + static void recv_reply(int); +-static void backup_hosts(const char *, const char *); +-static void get_hosts(const char *); + static void insert_host(struct nsm_host *); + static struct nsm_host *find_host(uint32_t); +-static void nsm_log(int fac, const char *fmt, ...); + static int record_pid(void); +-static void drop_privs(void); +-static void set_kernel_nsm_state(int state); + + static struct nsm_host * hosts = NULL; + +-/* +- * Address handling utilities +- */ +- +-static unsigned short smn_get_port(const struct sockaddr *sap) ++__attribute_malloc__ ++static struct addrinfo * ++smn_lookup(const char *name) + { +- switch (sap->sa_family) { +- case AF_INET: +- return ntohs(((struct sockaddr_in *)sap)->sin_port); +- case AF_INET6: +- return ntohs(((struct sockaddr_in6 *)sap)->sin6_port); +- } +- return 0; +-} ++ struct addrinfo *ai = NULL; ++ struct addrinfo hint = { ++ .ai_flags = AI_ADDRCONFIG, ++ .ai_family = (nsm_family == AF_INET ? AF_INET: AF_UNSPEC), ++ .ai_protocol = (int)IPPROTO_UDP, ++ }; ++ int error; + +-static void smn_set_port(struct sockaddr *sap, const unsigned short port) +-{ +- switch (sap->sa_family) { +- case AF_INET: +- ((struct sockaddr_in *)sap)->sin_port = htons(port); +- break; +- case AF_INET6: +- ((struct sockaddr_in6 *)sap)->sin6_port = htons(port); +- break; ++ error = getaddrinfo(name, NULL, &hint, &ai); ++ if (error != 0) { ++ xlog(D_GENERAL, "getaddrinfo(3): %s", gai_strerror(error)); ++ return NULL; + } ++ ++ return ai; + } + +-static struct addrinfo *smn_lookup(const char *name) ++__attribute_malloc__ ++static struct nsm_host * ++smn_alloc_host(const char *hostname, const char *mon_name, ++ const char *my_name, const time_t timestamp) + { +- struct addrinfo *ai, hint = { +-#if HAVE_DECL_AI_ADDRCONFIG +- .ai_flags = AI_ADDRCONFIG, +-#endif /* HAVE_DECL_AI_ADDRCONFIG */ +- .ai_family = AF_INET, +- .ai_protocol = IPPROTO_UDP, +- }; +- int error; ++ struct nsm_host *host; + +- error = getaddrinfo(name, NULL, &hint, &ai); +- switch (error) { +- case 0: +- return ai; +- case EAI_SYSTEM: +- if (opt_debug) +- nsm_log(LOG_ERR, "getaddrinfo(3): %s", +- strerror(errno)); +- break; +- default: +- if (opt_debug) +- nsm_log(LOG_ERR, "getaddrinfo(3): %s", +- gai_strerror(error)); ++ host = calloc(1, sizeof(*host)); ++ if (host == NULL) ++ goto out_nomem; ++ ++ host->name = strdup(hostname); ++ host->mon_name = strdup(mon_name); ++ host->my_name = strdup(my_name); ++ if (host->name == NULL || ++ host->mon_name == NULL || ++ host->my_name == NULL) { ++ free(host->my_name); ++ free(host->mon_name); ++ free(host->name); ++ free(host); ++ goto out_nomem; + } + ++ host->last_used = timestamp; ++ host->timeout = NSM_TIMEOUT; ++ host->retries = 100; /* force address retry */ ++ ++ return host; ++ ++out_nomem: ++ xlog_warn("Unable to allocate memory"); + return NULL; + } + + static void smn_forget_host(struct nsm_host *host) + { +- unlink(host->path); +- free(host->path); ++ xlog(D_CALL, "Removing %s (%s, %s) from notify list", ++ host->name, host->mon_name, host->my_name); ++ ++ nsm_delete_notified_host(host->name, host->mon_name, host->my_name); ++ ++ free(host->my_name); ++ free(host->mon_name); + free(host->name); + if (host->ai) + freeaddrinfo(host->ai); +@@ -158,13 +144,219 @@ static void smn_forget_host(struct nsm_host *host) + free(host); + } + ++static unsigned int ++smn_get_host(const char *hostname, ++ __attribute__ ((unused)) const struct sockaddr *sap, ++ const struct mon *m, const time_t timestamp) ++{ ++ struct nsm_host *host; ++ ++ host = smn_alloc_host(hostname, ++ m->mon_id.mon_name, m->mon_id.my_id.my_name, timestamp); ++ if (host == NULL) ++ return 0; ++ ++ insert_host(host); ++ xlog(D_GENERAL, "Added host %s to notify list", hostname); ++ return 1; ++} ++ ++#ifdef IPV6_SUPPORTED ++static int smn_socket(void) ++{ ++ int sock; ++ ++ /* ++ * Use an AF_INET socket if IPv6 is disabled on the ++ * local system. ++ */ ++ sock = socket(AF_INET6, SOCK_DGRAM, 0); ++ if (sock == -1) { ++ if (errno != EAFNOSUPPORT) { ++ xlog(L_ERROR, "Failed to create RPC socket: %m"); ++ return -1; ++ } ++ sock = socket(AF_INET, SOCK_DGRAM, 0); ++ if (sock < 0) { ++ xlog(L_ERROR, "Failed to create RPC socket: %m"); ++ return -1; ++ } ++ } else ++ nsm_family = AF_INET6; ++ ++ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { ++ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); ++ goto out_close; ++ } ++ ++ /* ++ * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4. However, ++ * since sm-notify open-codes all of its RPC support, it can ++ * use a single socket and let the local network stack provide ++ * the correct mapping between address families automatically. ++ * This is the same thing that is done in the kernel. ++ */ ++ if (nsm_family == AF_INET6) { ++ const int zero = 0; ++ socklen_t zerolen = (socklen_t)sizeof(zero); ++ ++ if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, ++ (char *)&zero, zerolen) == -1) { ++ xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m"); ++ goto out_close; ++ } ++ } ++ ++ return sock; ++ ++out_close: ++ (void)close(sock); ++ return -1; ++} ++#else /* !IPV6_SUPPORTED */ ++static int smn_socket(void) ++{ ++ int sock; ++ ++ sock = socket(AF_INET, SOCK_DGRAM, 0); ++ if (sock == -1) { ++ xlog(L_ERROR, "Failed to create RPC socket: %m"); ++ return -1; ++ } ++ ++ if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { ++ xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); ++ (void)close(sock); ++ return -1; ++ } ++ ++ return sock; ++} ++#endif /* !IPV6_SUPPORTED */ ++ ++/* ++ * If admin specified a source address or srcport, then convert those ++ * to a sockaddr and return it. Otherwise, return an ANYADDR address. ++ */ ++__attribute_malloc__ ++static struct addrinfo * ++smn_bind_address(const char *srcaddr, const char *srcport) ++{ ++ struct addrinfo *ai = NULL; ++ struct addrinfo hint = { ++ .ai_flags = AI_NUMERICSERV, ++ .ai_family = nsm_family, ++ .ai_protocol = (int)IPPROTO_UDP, ++ }; ++ int error; ++ ++ if (srcaddr == NULL) ++ hint.ai_flags |= AI_PASSIVE; ++ ++ if (srcport == NULL) ++ error = getaddrinfo(srcaddr, "", &hint, &ai); ++ else ++ error = getaddrinfo(srcaddr, srcport, &hint, &ai); ++ if (error != 0) { ++ xlog(L_ERROR, ++ "Invalid bind address or port for RPC socket: %s", ++ gai_strerror(error)); ++ return NULL; ++ } ++ ++ return ai; ++} ++ ++#ifdef HAVE_LIBTIRPC ++static int ++smn_bindresvport(int sock, struct sockaddr *sap) ++{ ++ return bindresvport_sa(sock, sap); ++} ++ ++#else /* !HAVE_LIBTIRPC */ ++static int ++smn_bindresvport(int sock, struct sockaddr *sap) ++{ ++ if (sap->sa_family != AF_INET) { ++ errno = EAFNOSUPPORT; ++ return -1; ++ } ++ ++ return bindresvport(sock, (struct sockaddr_in *)(char *)sap); ++} ++#endif /* !HAVE_LIBTIRPC */ ++ ++/* ++ * Prepare a socket for sending RPC requests ++ * ++ * Returns a bound datagram socket file descriptor, or -1 if ++ * an error occurs. ++ */ ++static int ++smn_create_socket(const char *srcaddr, const char *srcport) ++{ ++ int sock, retry_cnt = 0; ++ struct addrinfo *ai; ++ ++retry: ++ sock = smn_socket(); ++ if (sock == -1) ++ return -1; ++ ++ ai = smn_bind_address(srcaddr, srcport); ++ if (ai == NULL) { ++ (void)close(sock); ++ return -1; ++ } ++ ++ /* Use source port if provided on the command line, ++ * otherwise use bindresvport */ ++ if (srcport) { ++ if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { ++ xlog(L_ERROR, "Failed to bind RPC socket: %m"); ++ freeaddrinfo(ai); ++ (void)close(sock); ++ return -1; ++ } ++ } else { ++ struct servent *se; ++ ++ if (smn_bindresvport(sock, ai->ai_addr) == -1) { ++ xlog(L_ERROR, ++ "bindresvport on RPC socket failed: %m"); ++ freeaddrinfo(ai); ++ (void)close(sock); ++ return -1; ++ } ++ ++ /* try to avoid known ports */ ++ se = getservbyport((int)nfs_get_port(ai->ai_addr), "udp"); ++ if (se != NULL && retry_cnt < 100) { ++ retry_cnt++; ++ freeaddrinfo(ai); ++ (void)close(sock); ++ goto retry; ++ } ++ } ++ ++ freeaddrinfo(ai); ++ return sock; ++} ++ + int + main(int argc, char **argv) + { +- int c; +- int force = 0; ++ int c, sock, force = 0; ++ char * progname; + +- while ((c = getopt(argc, argv, "dm:np:v:qP:f")) != -1) { ++ progname = strrchr(argv[0], '/'); ++ if (progname != NULL) ++ progname++; ++ else ++ progname = argv[0]; ++ ++ while ((c = getopt(argc, argv, "dm:np:v:P:f")) != -1) { + switch (c) { + case 'f': + force = 1; +@@ -176,32 +368,17 @@ main(int argc, char **argv) + opt_max_retry = atoi(optarg) * 60; + break; + case 'n': +- opt_update_state = 0; ++ opt_update_state = false; + break; + case 'p': +- opt_srcport = atoi(optarg); ++ opt_srcport = optarg; + break; + case 'v': + opt_srcaddr = optarg; + break; +- case 'q': +- opt_quiet = 1; +- break; + case 'P': +- _SM_BASE_PATH = strdup(optarg); +- _SM_STATE_PATH = malloc(strlen(optarg)+1+sizeof("state")); +- _SM_DIR_PATH = malloc(strlen(optarg)+1+sizeof("sm")); +- _SM_BAK_PATH = malloc(strlen(optarg)+1+sizeof("sm.bak")); +- if (_SM_BASE_PATH == NULL || +- _SM_STATE_PATH == NULL || +- _SM_DIR_PATH == NULL || +- _SM_BAK_PATH == NULL) { +- nsm_log(LOG_ERR, "unable to allocate memory"); ++ if (!nsm_setup_pathnames(argv[0], optarg)) + exit(1); +- } +- strcat(strcpy(_SM_STATE_PATH, _SM_BASE_PATH), "/state"); +- strcat(strcpy(_SM_DIR_PATH, _SM_BASE_PATH), "/sm"); +- strcat(strcpy(_SM_BAK_PATH, _SM_BASE_PATH), "/sm.bak"); + break; + + default: +@@ -211,18 +388,26 @@ main(int argc, char **argv) + + if (optind < argc) { + usage: fprintf(stderr, +- "Usage: sm-notify [-dfq] [-m max-retry-minutes] [-p srcport]\n" +- " [-P /path/to/state/directory] [-v my_host_name]\n"); ++ "Usage: %s -notify [-dfq] [-m max-retry-minutes] [-p srcport]\n" ++ " [-P /path/to/state/directory] [-v my_host_name]\n", ++ progname); + exit(1); + } + +- log_syslog = 1; +- openlog("sm-notify", LOG_PID, LOG_DAEMON); ++ xlog_syslog(1); ++ if (opt_debug) { ++ xlog_stderr(1); ++ xlog_config(D_ALL, 1); ++ } else ++ xlog_stderr(0); ++ ++ xlog_open(progname); ++ xlog(L_NOTICE, "Version " VERSION " starting"); + +- if (strcmp(_SM_BASE_PATH, BASEDIR) == 0) { +- if (record_pid() == 0 && force == 0 && opt_update_state == 1) { ++ if (nsm_is_default_parentdir()) { ++ if (record_pid() == 0 && force == 0 && opt_update_state) { + /* already run, don't try again */ +- nsm_log(LOG_NOTICE, "Already notifying clients; Exiting!"); ++ xlog(L_NOTICE, "Already notifying clients; Exiting!"); + exit(0); + } + } +@@ -231,31 +416,26 @@ usage: fprintf(stderr, + strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1); + } else + if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) { +- nsm_log(LOG_ERR, "Failed to obtain name of local host: %s", +- strerror(errno)); ++ xlog(L_ERROR, "Failed to obtain name of local host: %m"); + exit(1); + } + +- backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH); +- get_hosts(_SM_BAK_PATH); +- +- /* If there are not hosts to notify, just exit */ +- if (!hosts) { +- nsm_log(LOG_DEBUG, "No hosts to notify; exiting"); ++ (void)nsm_retire_monitored_hosts(); ++ if (nsm_load_notify_list(smn_get_host) == 0) { ++ xlog(D_GENERAL, "No hosts to notify; exiting"); + return 0; + } + +- /* Get and update the NSM state. This will call sync() */ + nsm_state = nsm_get_state(opt_update_state); +- set_kernel_nsm_state(nsm_state); ++ if (nsm_state == 0) ++ exit(1); ++ nsm_update_kernel_state(nsm_state); + + if (!opt_debug) { +- if (!opt_quiet) +- printf("Backgrounding to notify hosts...\n"); ++ xlog(L_NOTICE, "Backgrounding to notify hosts...\n"); + + if (daemon(0, 0) < 0) { +- nsm_log(LOG_ERR, "unable to background: %s", +- strerror(errno)); ++ xlog(L_ERROR, "unable to background: %m"); + exit(1); + } + +@@ -264,15 +444,21 @@ usage: fprintf(stderr, + close(2); + } + +- notify(); ++ sock = smn_create_socket(opt_srcaddr, opt_srcport); ++ if (sock == -1) ++ exit(1); ++ ++ if (!nsm_drop_privileges(-1)) ++ exit(1); ++ ++ notify(sock); + + if (hosts) { + struct nsm_host *hp; + + while ((hp = hosts) != 0) { + hosts = hp->next; +- nsm_log(LOG_NOTICE, +- "Unable to notify %s, giving up", ++ xlog(L_NOTICE, "Unable to notify %s, giving up", + hp->name); + } + exit(1); +@@ -285,69 +471,13 @@ usage: fprintf(stderr, + * Notify hosts + */ + static void +-notify(void) ++notify(const int sock) + { +- struct sockaddr_storage address; +- struct sockaddr *local_addr = (struct sockaddr *)&address; + time_t failtime = 0; +- int sock = -1; +- int retry_cnt = 0; +- +- retry: +- sock = socket(AF_INET, SOCK_DGRAM, 0); +- if (sock < 0) { +- nsm_log(LOG_ERR, "Failed to create RPC socket: %s", +- strerror(errno)); +- exit(1); +- } +- fcntl(sock, F_SETFL, O_NONBLOCK); +- +- memset(&address, 0, sizeof(address)); +- local_addr->sa_family = AF_INET; /* Default to IPv4 */ +- +- /* Bind source IP if provided on command line */ +- if (opt_srcaddr) { +- struct addrinfo *ai = smn_lookup(opt_srcaddr); +- if (!ai) { +- nsm_log(LOG_ERR, +- "Not a valid hostname or address: \"%s\"", +- opt_srcaddr); +- exit(1); +- } +- +- /* We know it's IPv4 at this point */ +- memcpy(local_addr, ai->ai_addr, ai->ai_addrlen); +- +- freeaddrinfo(ai); +- } +- +- /* Use source port if provided on the command line, +- * otherwise use bindresvport */ +- if (opt_srcport) { +- smn_set_port(local_addr, opt_srcport); +- if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) { +- nsm_log(LOG_ERR, "Failed to bind RPC socket: %s", +- strerror(errno)); +- exit(1); +- } +- } else { +- struct servent *se; +- struct sockaddr_in *sin = (struct sockaddr_in *)local_addr; +- (void) bindresvport(sock, sin); +- /* try to avoid known ports */ +- se = getservbyport(sin->sin_port, "udp"); +- if (se && retry_cnt < 100) { +- retry_cnt++; +- close(sock); +- goto retry; +- } +- } + + if (opt_max_retry) + failtime = time(NULL) + opt_max_retry; + +- drop_privs(); +- + while (hosts) { + struct pollfd pfd; + time_t now = time(NULL); +@@ -383,7 +513,7 @@ notify(void) + if (hosts == NULL) + return; + +- nsm_log(LOG_DEBUG, "Host %s due in %ld seconds", ++ xlog(D_GENERAL, "Host %s due in %ld seconds", + hosts->name, wait); + + pfd.fd = sock; +@@ -405,34 +535,18 @@ notify(void) + static int + notify_host(int sock, struct nsm_host *host) + { +- struct sockaddr_storage address; +- struct sockaddr *dest = (struct sockaddr *)&address; +- socklen_t destlen = sizeof(address); +- static unsigned int xid = 0; +- uint32_t msgbuf[MAXMSGSIZE], *p; +- unsigned int len; +- +- if (!xid) +- xid = getpid() + time(NULL); +- if (!host->xid) +- host->xid = xid++; ++ struct sockaddr *sap; ++ socklen_t salen; + + if (host->ai == NULL) { + host->ai = smn_lookup(host->name); + if (host->ai == NULL) { +- nsm_log(LOG_WARNING, +- "DNS resolution of %s failed; " ++ xlog_warn("DNS resolution of %s failed; " + "retrying later", host->name); + return 0; + } + } + +- memset(msgbuf, 0, sizeof(msgbuf)); +- p = msgbuf; +- *p++ = htonl(host->xid); +- *p++ = 0; +- *p++ = htonl(2); +- + /* If we retransmitted 4 times, reset the port to force + * a new portmap lookup (in case statd was restarted). + * We also rotate through multiple IP addresses at this +@@ -440,10 +554,7 @@ notify_host(int sock, struct nsm_host *host) + */ + if (host->retries >= 4) { + /* don't rotate if there is only one addrinfo */ +- if (host->ai->ai_next == NULL) +- memcpy(&host->addr, host->ai->ai_addr, +- host->ai->ai_addrlen); +- else { ++ if (host->ai->ai_next != NULL) { + struct addrinfo *first = host->ai; + struct addrinfo **next = &host->ai; + +@@ -456,213 +567,100 @@ notify_host(int sock, struct nsm_host *host) + next = & (*next)->ai_next; + /* put first entry at end */ + *next = first; +- memcpy(&host->addr, first->ai_addr, +- first->ai_addrlen); + } + +- smn_set_port((struct sockaddr *)&host->addr, 0); ++ nfs_set_port(host->ai->ai_addr, 0); + host->retries = 0; + } + +- memcpy(dest, &host->addr, destlen); +- if (smn_get_port(dest) == 0) { +- /* Build a PMAP packet */ +- nsm_log(LOG_DEBUG, "Sending portmap query to %s", host->name); ++ sap = host->ai->ai_addr; ++ salen = host->ai->ai_addrlen; + +- smn_set_port(dest, 111); +- *p++ = htonl(100000); +- *p++ = htonl(2); +- *p++ = htonl(3); +- +- /* Auth and verf */ +- *p++ = 0; *p++ = 0; +- *p++ = 0; *p++ = 0; +- +- *p++ = htonl(NSM_PROGRAM); +- *p++ = htonl(NSM_VERSION); +- *p++ = htonl(IPPROTO_UDP); +- *p++ = 0; +- } else { +- /* Build an SM_NOTIFY packet */ +- nsm_log(LOG_DEBUG, "Sending SM_NOTIFY to %s", host->name); +- +- *p++ = htonl(NSM_PROGRAM); +- *p++ = htonl(NSM_VERSION); +- *p++ = htonl(NSM_NOTIFY); +- +- /* Auth and verf */ +- *p++ = 0; *p++ = 0; +- *p++ = 0; *p++ = 0; +- +- /* state change */ +- len = strlen(nsm_hostname); +- *p++ = htonl(len); +- memcpy(p, nsm_hostname, len); +- p += (len + 3) >> 2; +- *p++ = htonl(nsm_state); +- } +- len = (p - msgbuf) << 2; +- +- if (sendto(sock, msgbuf, len, 0, dest, destlen) < 0) +- nsm_log(LOG_WARNING, "Sending Reboot Notification to " +- "'%s' failed: errno %d (%s)", host->name, errno, strerror(errno)); ++ if (nfs_get_port(sap) == 0) ++ 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); + + return 0; + } + + /* +- * Receive reply from remote host ++ * Extract the returned port number and set up the SM_NOTIFY call. + */ + static void +-recv_reply(int sock) ++recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr) + { +- struct nsm_host *hp; +- struct sockaddr *sap; +- uint32_t msgbuf[MAXMSGSIZE], *p, *end; +- uint32_t xid; +- int res; +- +- res = recv(sock, msgbuf, sizeof(msgbuf), 0); +- if (res < 0) +- return; +- +- nsm_log(LOG_DEBUG, "Received packet..."); ++ uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr); + +- p = msgbuf; +- end = p + (res >> 2); +- +- xid = ntohl(*p++); +- if (*p++ != htonl(1) /* must be REPLY */ +- || *p++ != htonl(0) /* must be ACCEPTED */ +- || *p++ != htonl(0) /* must be NULL verifier */ +- || *p++ != htonl(0) +- || *p++ != htonl(0)) /* must be SUCCESS */ +- return; ++ host->send_next = time(NULL); ++ host->xid = 0; + +- /* Before we look at the data, find the host struct for +- this reply */ +- if ((hp = find_host(xid)) == NULL) +- return; +- sap = (struct sockaddr *)&hp->addr; +- +- if (smn_get_port(sap) == 0) { +- /* This was a portmap request */ +- unsigned int port; +- +- port = ntohl(*p++); +- if (p > end) +- goto fail; +- +- hp->send_next = time(NULL); +- if (port == 0) { +- /* No binding for statd. Delay the next +- * portmap query for max timeout */ +- nsm_log(LOG_DEBUG, "No statd on %s", hp->name); +- hp->timeout = NSM_MAX_TIMEOUT; +- hp->send_next += NSM_MAX_TIMEOUT; +- } else { +- smn_set_port(sap, port); +- if (hp->timeout >= NSM_MAX_TIMEOUT / 4) +- hp->timeout = NSM_MAX_TIMEOUT / 4; +- } +- hp->xid = 0; ++ if (port == 0) { ++ /* No binding for statd... */ ++ xlog(D_GENERAL, "No statd on host %s", host->name); ++ host->timeout = NSM_MAX_TIMEOUT; ++ host->send_next += NSM_MAX_TIMEOUT; + } else { +- /* Successful NOTIFY call. Server returns void, +- * so nothing we need to do here (except +- * check that we didn't read past the end of the +- * packet) +- */ +- if (p <= end) { +- nsm_log(LOG_DEBUG, "Host %s notified successfully", +- hp->name); +- smn_forget_host(hp); +- return; +- } ++ nfs_set_port(sap, port); ++ if (host->timeout >= NSM_MAX_TIMEOUT / 4) ++ host->timeout = NSM_MAX_TIMEOUT / 4; + } + +-fail: /* Re-insert the host */ +- insert_host(hp); ++ insert_host(host); + } + + /* +- * Back up all hosts from the sm directory to sm.bak ++ * Successful NOTIFY call. Server returns void, so nothing ++ * we need to do here. + */ + static void +-backup_hosts(const char *dirname, const char *bakname) ++recv_notify_reply(struct nsm_host *host) + { +- struct dirent *de; +- DIR *dir; +- +- if (!(dir = opendir(dirname))) { +- nsm_log(LOG_WARNING, +- "Failed to open %s: %s", dirname, strerror(errno)); +- return; +- } ++ xlog(D_GENERAL, "Host %s notified successfully", host->name); + +- while ((de = readdir(dir)) != NULL) { +- char src[1024], dst[1024]; +- +- if (de->d_name[0] == '.') +- continue; +- +- snprintf(src, sizeof(src), "%s/%s", dirname, de->d_name); +- snprintf(dst, sizeof(dst), "%s/%s", bakname, de->d_name); +- if (rename(src, dst) < 0) { +- nsm_log(LOG_WARNING, +- "Failed to rename %s -> %s: %m", +- src, dst); +- } +- } +- closedir(dir); ++ smn_forget_host(host); + } + + /* +- * Get all entries from sm.bak and convert them to host entries ++ * Receive reply from remote host + */ + static void +-get_hosts(const char *dirname) ++recv_reply(int sock) + { +- struct nsm_host *host; +- struct dirent *de; +- DIR *dir; ++ struct nsm_host *hp; ++ struct sockaddr *sap; ++ char msgbuf[NSM_MAXMSGSIZE]; ++ uint32_t xid; ++ ssize_t msglen; ++ XDR xdr; + +- if (!(dir = opendir(dirname))) { +- nsm_log(LOG_WARNING, +- "Failed to open %s: %s", dirname, strerror(errno)); ++ memset(msgbuf, 0 , sizeof(msgbuf)); ++ msglen = recv(sock, msgbuf, sizeof(msgbuf), 0); ++ if (msglen < 0) + return; +- } +- +- host = NULL; +- while ((de = readdir(dir)) != NULL) { +- struct stat stb; +- char path[1024]; + +- if (de->d_name[0] == '.') +- continue; +- if (host == NULL) +- host = calloc(1, sizeof(*host)); +- if (host == NULL) { +- nsm_log(LOG_WARNING, "Unable to allocate memory"); +- return; +- } ++ xlog(D_GENERAL, "Received packet..."); + +- snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name); +- if (stat(path, &stb) < 0) +- continue; ++ memset(&xdr, 0, sizeof(xdr)); ++ xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); ++ xid = nsm_parse_reply(&xdr); ++ if (xid == 0) ++ goto out; + +- host->last_used = stb.st_mtime; +- host->timeout = NSM_TIMEOUT; +- host->path = strdup(path); +- host->name = strdup(de->d_name); +- host->retries = 100; /* force address retry */ ++ /* Before we look at the data, find the host struct for ++ this reply */ ++ if ((hp = find_host(xid)) == NULL) ++ goto out; + +- insert_host(host); +- host = NULL; +- } +- closedir(dir); ++ sap = hp->ai->ai_addr; ++ if (nfs_get_port(sap) == 0) ++ recv_rpcbind_reply(sap, hp, &xdr); ++ else ++ recv_notify_reply(hp); + +- if (host) +- free(host); ++out: ++ xdr_destroy(&xdr); + } + + /* +@@ -712,84 +710,6 @@ find_host(uint32_t xid) + return NULL; + } + +- +-/* +- * Retrieve the current NSM state +- */ +-static unsigned int +-nsm_get_state(int update) +-{ +- char newfile[PATH_MAX]; +- int fd, state; +- +- if ((fd = open(_SM_STATE_PATH, O_RDONLY)) < 0) { +- if (!opt_quiet) { +- nsm_log(LOG_WARNING, "%s: %m", _SM_STATE_PATH); +- nsm_log(LOG_WARNING, "Creating %s, set initial state 1", +- _SM_STATE_PATH); +- } +- state = 1; +- update = 1; +- } else { +- if (read(fd, &state, sizeof(state)) != sizeof(state)) { +- nsm_log(LOG_WARNING, +- "%s: bad file size, setting state = 1", +- _SM_STATE_PATH); +- state = 1; +- update = 1; +- } else { +- if (!(state & 1)) +- state += 1; +- } +- close(fd); +- } +- +- if (update) { +- state += 2; +- snprintf(newfile, sizeof(newfile), +- "%s.new", _SM_STATE_PATH); +- if ((fd = open(newfile, O_CREAT|O_WRONLY, 0644)) < 0) { +- nsm_log(LOG_ERR, "Cannot create %s: %m", newfile); +- exit(1); +- } +- if (write(fd, &state, sizeof(state)) != sizeof(state)) { +- nsm_log(LOG_ERR, +- "Failed to write state to %s", newfile); +- exit(1); +- } +- close(fd); +- if (rename(newfile, _SM_STATE_PATH) < 0) { +- nsm_log(LOG_ERR, +- "Cannot create %s: %m", _SM_STATE_PATH); +- exit(1); +- } +- sync(); +- } +- +- return state; +-} +- +-/* +- * Log a message +- */ +-static void +-nsm_log(int fac, const char *fmt, ...) +-{ +- va_list ap; +- +- if (fac == LOG_DEBUG && !opt_debug) +- return; +- +- va_start(ap, fmt); +- if (log_syslog) +- vsyslog(fac, fmt, ap); +- else { +- vfprintf(stderr, fmt, ap); +- fputs("\n", stderr); +- } +- va_end(ap); +-} +- + /* + * Record pid in /var/run/sm-notify.pid + * This file should remain until a reboot, even if the +@@ -799,61 +719,20 @@ nsm_log(int fac, const char *fmt, ...) + static int record_pid(void) + { + char pid[20]; ++ ssize_t len; + int fd; + +- snprintf(pid, 20, "%d\n", getpid()); ++ (void)snprintf(pid, sizeof(pid), "%d\n", (int)getpid()); + fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + return 0; +- if (write(fd, pid, strlen(pid)) != strlen(pid)) { +- nsm_log(LOG_WARNING, "Writing to pid file failed: errno %d(%s)", +- errno, strerror(errno)); +- } +- close(fd); +- return 1; +-} + +-/* Drop privileges to match owner of state-directory +- * (in case a reply triggers some unknown bug). +- */ +-static void drop_privs(void) +-{ +- struct stat st; +- +- if (stat(_SM_DIR_PATH, &st) == -1 && +- stat(_SM_BASE_PATH, &st) == -1) { +- st.st_uid = 0; +- st.st_gid = 0; +- } +- +- if (st.st_uid == 0) { +- nsm_log(LOG_WARNING, +- "sm-notify running as root. chown %s to choose different user", +- _SM_DIR_PATH); +- return; +- } +- +- setgroups(0, NULL); +- if (setgid(st.st_gid) == -1 +- || setuid(st.st_uid) == -1) { +- nsm_log(LOG_ERR, "Fail to drop privileges"); +- exit(1); ++ len = write(fd, pid, strlen(pid)); ++ if ((len < 0) || ((size_t)len != strlen(pid))) { ++ xlog_warn("Writing to pid file failed: errno %d (%m)", ++ errno); + } +-} + +-static void set_kernel_nsm_state(int state) +-{ +- int fd; +- const char *file = "/proc/sys/fs/nfs/nsm_local_state"; +- +- fd = open(file ,O_WRONLY); +- if (fd >= 0) { +- char buf[20]; +- snprintf(buf, sizeof(buf), "%d", state); +- if (write(fd, buf, strlen(buf)) != strlen(buf)) { +- nsm_log(LOG_WARNING, "Writing to '%s' failed: errno %d (%s)", +- file, errno, strerror(errno)); +- } +- close(fd); +- } ++ (void)close(fd); ++ return 1; + } +diff --git a/utils/statd/sm-notify.man b/utils/statd/sm-notify.man +index dd03b8d..163713e 100644 +--- a/utils/statd/sm-notify.man ++++ b/utils/statd/sm-notify.man +@@ -1,166 +1,315 @@ +-.\" +-.\" sm-notify(8) ++.\"@(#)sm-notify.8" + .\" + .\" Copyright (C) 2004 Olaf Kirch +-.TH sm-notify 8 "19 Mar 2007 ++.\" ++.\" Rewritten by Chuck Lever , 2009. ++.\" Copyright 2009 Oracle. All rights reserved. ++.\" ++.TH SM-NOTIFY 8 "1 November 2009 + .SH NAME +-sm-notify \- Send out NSM reboot notifications ++sm-notify \- send reboot notifications to NFS peers + .SH SYNOPSIS +-.BI "/sbin/sm-notify [-dfq] [-m " time "] [-p " port "] [-P " path "] [-v " my_name " ] ++.BI "/usr/sbin/sm-notify [-dfn] [-m " minutes "] [-v " name "] [-p " notify-port "] [-P " path "] + .SH DESCRIPTION +-File locking over NFS (v2 and v3) requires a facility to notify peers in +-case of a reboot, so that clients can reclaim locks after +-a server crash, and/or +-servers can release locks held by the rebooted client. ++File locks are not part of persistent file system state. ++Lock state is thus lost when a host reboots. ++.PP ++Network file systems must also detect when lock state is lost ++because a remote host has rebooted. ++After an NFS client reboots, an NFS server must release all file locks ++held by applications that were running on that client. ++After a server reboots, a client must remind the ++server of file locks held by applications running on that client. + .PP +-This is a two-step process: during normal +-operations, a mechanism is required to keep track of which +-hosts need to be informed of a reboot. And of course, +-notifications need to be sent out during reboot. +-The protocol used for this is called NSM, for +-.IR "Network Status Monitor" . ++For NFS version 2 and version 3, the ++.I Network Status Monitor ++protocol (or NSM for short) ++is used to notify NFS peers of reboots. ++On Linux, two separate user-space components constitute the NSM service: ++.TP ++.B sm-notify ++A helper program that notifies NFS peers after the local system reboots ++.TP ++.B rpc.statd ++A daemon that listens for reboot notifications from other hosts, and ++manages the list of hosts to be notified when the local system reboots + .PP +-This implementation separates these into separate program. ++The local NFS lock manager alerts its local + .B rpc.statd +-tracks hosts which need to be notified and this ++of each remote peer that should be monitored. ++When the local system reboots, the + .B sm-notify +-performs the notification. When ++command notifies the NSM service on monitored peers of the reboot. ++When a remote reboots, that peer notifies the local ++.BR rpc.statd , ++which in turn passes the reboot notification ++back to the local NFS lock manager. ++.SH NSM OPERATION IN DETAIL ++The first file locking interaction between an NFS client and server causes ++the NFS lock managers on both peers to contact their local NSM service to ++store information about the opposite peer. ++On Linux, the local lock manager contacts ++.BR rpc.statd . ++.PP ++.B rpc.statd ++records information about each monitored NFS peer on persistent storage. ++This information describes how to contact a remote peer ++in case the local system reboots, ++how to recognize which monitored peer is reporting a reboot, ++and how to notify the local lock manager when a monitored peer ++indicates it has rebooted. ++.PP ++An NFS client sends a hostname, known as the client's ++.IR caller_name , ++in each file lock request. ++An NFS server can use this hostname to send asynchronous GRANT ++calls to a client, or to notify the client it has rebooted. ++.PP ++The Linux NFS server can provide the client's ++.I caller_name ++or the client's network address to ++.BR rpc.statd . ++For the purposes of the NSM protocol, ++this name or address is known as the monitored peer's ++.IR mon_name . ++In addition, the local lock manager tells + .B rpc.statd +-is started it will typically started ++what it thinks its own hostname is. ++For the purposes of the NSM protocol, ++this hostname is known as ++.IR my_name . ++.PP ++There is no equivalent interaction between an NFS server and a client ++to inform the client of the server's ++.IR caller_name . ++Therefore NFS clients do not actually know what ++.I mon_name ++an NFS server might use in an SM_NOTIFY request. ++The Linux NFS client records the server's hostname used on the mount command ++to identify rebooting NFS servers. ++.SS Reboot notification ++When the local system reboots, the ++.B sm-notify ++command reads the list of monitored peers from persistent storage and ++sends an SM_NOTIFY request to the NSM service on each listed remote peer. ++It uses the ++.I mon_name ++string as the destination. ++To identify which host has rebooted, the + .B sm-notify +-but this is configurable. +-.SS Operation +-For each NFS client or server machine to be monitored, ++command normally sends the results of ++.BR gethostname (3) ++as the ++.I my_name ++string. ++The remote + .B rpc.statd +-creates a file in +-.BR /var/lib/nfs/sm ", " +-and removes the file if monitoring is no longer required. ++matches incoming SM_NOTIFY requests using this string, ++or the caller's network address, ++to one or more peers on its own monitor list. + .PP +-When the machine is rebooted, ++If ++.B rpc.statd ++does not find a peer on its monitor list that matches ++an incoming SM_NOTIFY request, ++the notification is not forwarded to the local lock manager. ++In addition, each peer has its own ++.IR "NSM state number" , ++a 32-bit integer that is bumped after each reboot by the + .B sm-notify +-iterates through these files and notifies the peer +-.B statd +-server on those machines. ++command. ++.B rpc.statd ++uses this number to distinguish between actual reboots ++and replayed notifications. + .PP +-Each machine has an +-.I "NSM state" , +-which is basically an integer counter that is incremented +-each time the machine reboots. This counter is stored +-in +-.BR /var/lib/nfs/state , +-and updated by +-.BR sm-notify . +-.SS Security +-.B sm-notify +-has little need for root privileges and so drops them as soon as +-possible. +-It continues to need to make changes to the +-.B sm +-and +-.B sm.bak +-directories so to be able to drop privileges, these must be writable +-by a non-privileged user. If these directories are owned by a +-non-root user, +-.B sm-notify +-will drop privilege to match that user once it has created sockets for +-sending out request (for which it needs privileged) but before it +-processes any reply (which is the most likely source of possible +-privilege abuse). ++Part of NFS lock recovery is rediscovering ++which peers need to be monitored again. ++The ++.B sm-notify ++command clears the monitor list on persistent storage after each reboot. + .SH OPTIONS + .TP +-.BI -m " failtime +-When notifying hosts, ++.B -d ++Keeps ++.B sm-notify ++attached to its controlling terminal and running in the foreground ++so that notification progress may be monitored directly. ++.TP ++.B -f ++Send notifications even if ++.B sm-notify ++has already run since the last system reboot. ++.TP ++.BI -m " retry-time ++Specifies the length of time, in minutes, to continue retrying ++notifications to unresponsive hosts. ++If this option is not specified, + .B sm-notify +-will try to contact each host for up to 15 minutes, +-and will give up if unable to reach it within this time +-frame. ++attempts to send notifications for 15 minutes. ++Specifying a value of 0 causes ++.B sm-notify ++to continue sending notifications to unresponsive peers ++until it is manually killed. ++.IP ++Notifications are retried if sending fails, ++the remote does not respond, ++the remote's NSM service is not registered, ++or if there is a DNS failure ++which prevents the remote's ++.I mon_name ++from being resolved to an address. + .IP +-Using the +-.B -m +-option, you can override this. A value of 0 tells +-sm-notify to retry indefinitely; any other value is +-interpreted as the maximum retry time in minutes. ++Hosts are not removed from the notification list until a valid ++reply has been received. ++However, the SM_NOTIFY procedure has a void result. ++There is no way for ++.B sm-notify ++to tell if the remote recognized the sender and has started ++appropriate lock recovery. + .TP +-.BI -v " ipaddr-or-hostname +-This option tells +-.B sm-notify +-to bind to the specified +-.IR ipaddr , +-(or the ipaddr of the given +-.IR hostname ) +-so that all notification packets originate from this address. +-This is useful for NFS failover. The given name is also used as the +-.I name +-of this host in the NSM request. ++.B -n ++Prevents ++.B sm-notify ++from updating the local system's NSM state number. + .TP + .BI -p " port +-instructs ++Specifies the source port number + .B sm-notify +-to bind to the indicated IP +-.IR port +-number. If this option is not given, it will try to bind to +-a randomly chosen privileged port below 1024. +-.TP +-.B -q +-Be quiet. This suppresses all messages except error +-messages while collecting the list of hosts. ++should use when sending reboot notifications. ++If this option is not specified, a randomly chosen ephemeral port is used. ++.IP ++This option can be used to traverse a firewall between client and server. + .TP +-.BI -P " /path/to/state/directory +-If ++.BI "\-P, " "" \-\-state\-directory\-path " pathname ++Specifies the pathname of the parent directory ++where NSM state information resides. ++If this option is not specified, ++.B sm-notify ++uses ++.I /var/lib/nfs ++by default. ++.IP ++After starting, + .B sm-notify +-should look in a no-standard place of state file, the path can be +-given here. The directories +-.B sm +-and +-.B sm.bak +-and the file +-.B state +-must exist in that directory with the standard names. ++attempts to set its effective UID and GID to the owner ++and group of this directory. + .TP +-.B -f +-If the state path has not been reset with +-.BR -P , ++.BI -v " ipaddr " | " hostname ++Specifies the network address from which to send reboot notifications, ++and the ++.I mon_name ++argument to use when sending SM_NOTIFY requests. ++If this option is not specified, + .B sm-notify +-will normally create a file in +-.B /var/run +-to indicate that it has been +-run. If this file is found when ++uses a wildcard address as the transport bind address, ++and uses the results of ++.BR gethostname (3) ++as the ++.I mon_name ++argument. ++.IP ++The ++.I ipaddr ++form can be expressed as either an IPv4 or an IPv6 presentation address. ++.IP ++This option can be useful in multi-homed configurations where ++the remote requires notification from a specific network address. ++.SH SECURITY ++The + .B sm-notify +-starts, it will not run again (as it is normally only needed once per +-reboot). +-If +-.B -f +-(for +-.BR force ) +-is given, ++command must be started as root to acquire privileges needed ++to access the state information database. ++It drops root privileges ++as soon as it starts up to reduce the risk of a privilege escalation attack. ++.PP ++During normal operation, ++the effective user ID it chooses is the owner of the state directory. ++This allows it to continue to access files in that directory after it ++has dropped its root privileges. ++To control which user ID ++.B rpc.statd ++chooses, simply use ++.BR chown (1) ++to set the owner of ++the state directory. ++.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 ++should be observed, including: ++.IP ++The UTS nodename of your systems should match the DNS names that NFS ++peers use to contact them ++.IP ++The UTS nodenames of your systems should always be fully qualified domain names ++.IP ++The forward and reverse DNS mapping of the UTS nodenames should be ++consistent ++.IP ++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. ++Both may continue monitoring each other for a time in case subsequent ++NFS traffic between the two results in fresh mounts and additional ++file locking. ++.PP ++On Linux, if the ++.B lockd ++kernel module is unloaded during normal operation, ++all remote NFS peers are unmonitored. ++This can happen on an NFS client, for example, ++if an automounter removes all NFS mount ++points due to inactivity. ++.SS IPv6 and TI-RPC support ++TI-RPC is a pre-requisite for supporting NFS on IPv6. ++If TI-RPC support is built into the + .B sm-notify +-will run even if the file in +-.B /var/run +-is present. +-.TP +-.B -n +-Do not update the NSM state. This is for testing only. Setting this +-flag implies +-.BR -f . +-.TP +-.B -d +-Enables debugging. +-By default, ++command ,it will choose an appropriate IPv4 or IPv6 transport ++based on the network address returned by DNS for each remote peer. ++It should be fully compatible with remote systems ++that do not support TI-RPC or IPv6. ++.PP ++Currently, the + .B sm-notify +-forks and puts itself in the background after obtaining the +-list of hosts from +-.BR /var/lib/nfs/sm . ++command supports sending notification only via datagram transport protocols. + .SH FILES +-.BR /var/lib/nfs/state +-.br +-.BR /var/lib/nfs/sm/* ++.TP 2.5i ++.I /var/lib/nfs/sm ++directory containing monitor list ++.TP 2.5i ++.I /var/lib/nfs/sm.bak ++directory containing notify list ++.TP 2.5i ++.I /var/lib/nfs/state ++NSM state number for this host ++.TP 2.5i ++.I /proc/sys/fs/nfs/nsm_local_state ++kernel's copy of the NSM state number ++.SH SEE ALSO ++.BR rpc.statd (8), ++.BR nfs (5), ++.BR uname (2), ++.BR hostname (7) ++.PP ++RFC 1094 - "NFS: Network File System Protocol Specification" + .br +-.BR /var/lib/nfs/sm.bak/* ++RFC 1813 - "NFS Version 3 Protocol Specification" + .br +-.BR /var/run/sm-notify.pid +-.SH SEE ALSO +-.BR rpc.nfsd(8), +-.BR portmap(8) ++OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11 + .SH AUTHORS +-.br + Olaf Kirch ++.br ++Chuck Lever +diff --git a/utils/statd/sm_inter.x b/utils/statd/sm_inter.x +deleted file mode 100644 +index d8e0ad7..0000000 +--- a/utils/statd/sm_inter.x ++++ /dev/null +@@ -1,131 +0,0 @@ +-/* +- * Copyright (C) 1986 Sun Microsystems, Inc. +- * Modified by Jeffrey A. Uphoff, 1995, 1997-1999. +- * Modified by Olaf Kirch, 1996. +- * Modified by H.J. Lu, 1998. +- * +- * NSM for Linux. +- */ +- +-/* +- * Copyright (c) 2009, Sun Microsystems, Inc. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without +- * modification, are permitted provided that the following conditions are met: +- * - Redistributions of source code must retain the above copyright notice, +- * this list of conditions and the following disclaimer. +- * - Redistributions in binary form must reproduce the above copyright notice, +- * this list of conditions and the following disclaimer in the documentation +- * and/or other materials provided with the distribution. +- * - Neither the name of Sun Microsystems, Inc. nor the names of its +- * contributors may be used to endorse or promote products derived +- * from this software without specific prior written permission. +- * +- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +- * POSSIBILITY OF SUCH DAMAGE. +- */ +- +-/* +- * Status monitor protocol specification +- */ +- +-#ifdef RPC_CLNT +-%#include +-#endif +- +-program SM_PROG { +- version SM_VERS { +- /* res_stat = stat_succ if status monitor agrees to monitor */ +- /* res_stat = stat_fail if status monitor cannot monitor */ +- /* if res_stat == stat_succ, state = state number of site sm_name */ +- struct sm_stat_res SM_STAT(struct sm_name) = 1; +- +- /* res_stat = stat_succ if status monitor agrees to monitor */ +- /* res_stat = stat_fail if status monitor cannot monitor */ +- /* stat consists of state number of local site */ +- struct sm_stat_res SM_MON(struct mon) = 2; +- +- /* stat consists of state number of local site */ +- struct sm_stat SM_UNMON(struct mon_id) = 3; +- +- /* stat consists of state number of local site */ +- struct sm_stat SM_UNMON_ALL(struct my_id) = 4; +- +- void SM_SIMU_CRASH(void) = 5; +- +- void SM_NOTIFY(struct stat_chge) = 6; +- +- } = 1; +-} = 100024; +- +-const SM_MAXSTRLEN = 1024; +-const SM_PRIV_SIZE = 16; +- +-struct sm_name { +- string mon_name; +-}; +- +-struct my_id { +- string my_name; /* name of the site iniates the monitoring request*/ +- int my_prog; /* rpc program # of the requesting process */ +- int my_vers; /* rpc version # of the requesting process */ +- int my_proc; /* rpc procedure # of the requesting process */ +-}; +- +-struct mon_id { +- string mon_name; /* name of the site to be monitored */ +- struct my_id my_id; +-}; +- +- +-struct mon { +- struct mon_id mon_id; +- opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */ +-}; +- +-struct stat_chge { +- string mon_name; /* name of the site that had the state change */ +- int state; +-}; +- +-/* +- * state # of status monitor monitonically increases each time +- * status of the site changes: +- * an even number (>= 0) indicates the site is down and +- * an odd number (> 0) indicates the site is up; +- */ +-struct sm_stat { +- int state; /* state # of status monitor */ +-}; +- +-enum res { +- stat_succ = 0, /* status monitor agrees to monitor */ +- stat_fail = 1 /* status monitor cannot monitor */ +-}; +- +-struct sm_stat_res { +- res res_stat; +- int state; +-}; +- +-/* +- * structure of the status message sent back by the status monitor +- * when monitor site status changes +- */ +-struct status { +- string mon_name; +- int state; +- opaque priv[SM_PRIV_SIZE]; /* stored private information */ +-}; +- +-%#define SM_INTER_X +diff --git a/utils/statd/stat.c b/utils/statd/stat.c +index 799239f..8d8b65e 100644 +--- a/utils/statd/stat.c ++++ b/utils/statd/stat.c +@@ -12,7 +12,7 @@ + #include + #include "statd.h" + +-/* ++/* + * Services SM_STAT requests. + * + * According the the X/Open spec's on this procedure: "Implementations +@@ -37,18 +37,23 @@ + * other way to get it. + * 3/ That's what we always did in the past. + */ +-struct sm_stat_res * +-sm_stat_1_svc (struct sm_name *argp, struct svc_req *rqstp) ++struct sm_stat_res * ++sm_stat_1_svc(struct sm_name *argp, ++ __attribute__ ((unused)) struct svc_req *rqstp) + { + static sm_stat_res result; ++ char *name; ++ ++ xlog(D_CALL, "Received SM_STAT from %s", argp->mon_name); + +- if (gethostbyname (argp->mon_name) == NULL) { +- note (N_WARNING, "gethostbyname error for %s", argp->mon_name); ++ name = statd_canonical_name(argp->mon_name); ++ if (name == NULL) { + result.res_stat = STAT_FAIL; +- dprintf (N_DEBUG, "STAT_FAIL for %s", argp->mon_name); ++ xlog (D_GENERAL, "STAT_FAIL for %s", argp->mon_name); + } else { + result.res_stat = STAT_SUCC; +- dprintf (N_DEBUG, "STAT_SUCC for %s", argp->mon_name); ++ xlog (D_GENERAL, "STAT_SUCC for %s", argp->mon_name); ++ free(name); + } + result.state = MY_STATE; + return(&result); +diff --git a/utils/statd/statd.c b/utils/statd/statd.c +index 1c5247e..01fdb41 100644 +--- a/utils/statd/statd.c ++++ b/utils/statd/statd.c +@@ -25,33 +25,21 @@ + #include + #include + #include ++ + #include "statd.h" +-#include "version.h" + #include "nfslib.h" ++#include "nsm.h" + + /* Socket operations */ + #include + #include + +-/* Added to enable specification of state directory path at run-time +- * j_carlos_gomez@yahoo.com +- */ +- +-char * DIR_BASE = DEFAULT_DIR_BASE; +- +-char * SM_DIR = DEFAULT_SM_DIR; +-char * SM_BAK_DIR = DEFAULT_SM_BAK_DIR; +-char * SM_STAT_PATH = DEFAULT_SM_STAT_PATH; +- +-/* ----- end of state directory path stuff ------- */ +- + int run_mode = 0; /* foreground logging mode */ + + /* LH - I had these local to main, but it seemed silly to have + * two copies of each - one in main(), one static in log.c... + * It also eliminates the 256-char static in log.c */ +-char *name_p = NULL; +-const char *version_p = NULL; ++static char *name_p = NULL; + + /* PRC: a high-availability callout program can be specified with -H + * When this is done, the program will receive callouts whenever clients +@@ -75,7 +63,6 @@ static struct option longopts[] = + }; + + extern void sm_prog_1 (struct svc_req *, register SVCXPRT *); +-static void load_state_number(void); + + #ifdef SIMULATIONS + extern void simulator (int, char **); +@@ -88,11 +75,8 @@ extern void simulator (int, char **); + static void + sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp) + { +- struct sockaddr_in *sin = nfs_getrpccaller_in(transp); +- + /* remote host authorization check */ +- if (sin->sin_family == AF_INET && +- !check_default("statd", sin, rqstp->rq_proc, SM_PROG)) { ++ if (!check_default("statd", nfs_getrpccaller(transp), SM_PROG)) { + svcerr_auth (transp, AUTH_FAILED); + return; + } +@@ -103,23 +87,26 @@ sm_prog_1_wrapper (struct svc_req *rqstp, register SVCXPRT *transp) + #define sm_prog_1 sm_prog_1_wrapper + #endif + ++static void ++statd_unregister(void) { ++ nfs_svc_unregister(SM_PROG, SM_VERS); ++} ++ + /* + * Signal handler. + */ + static void + killer (int sig) + { +- note (N_FATAL, "Caught signal %d, un-registering and exiting.", sig); +- pmap_unset (SM_PROG, SM_VERS); +- +- exit (0); ++ statd_unregister (); ++ xlog_err ("Caught signal %d, un-registering and exiting", sig); + } + + static void + sigusr (int sig) + { + extern void my_svc_exit (void); +- dprintf (N_DEBUG, "Caught signal %d, re-notifying (state %d).", sig, ++ xlog(D_GENERAL, "Caught signal %d, re-notifying (state %d)", sig, + MY_STATE); + my_svc_exit(); + } +@@ -140,8 +127,11 @@ static void log_modes(void) + strcat(buf,"No-Daemon "); + if (run_mode & MODE_LOG_STDERR) + strcat(buf,"Log-STDERR "); ++#ifdef HAVE_LIBTIRPC ++ strcat(buf, "TI-RPC "); ++#endif + +- note(N_WARNING,buf); ++ xlog_warn(buf); + } + + /* +@@ -175,13 +165,12 @@ static void create_pidfile(void) + unlink(pidfile); + fp = fopen(pidfile, "w"); + if (!fp) +- die("Opening %s failed: %s\n", +- pidfile, strerror(errno)); ++ xlog_err("Opening %s failed: %m\n", pidfile); + fprintf(fp, "%d\n", getpid()); + pidfd = dup(fileno(fp)); + if (fclose(fp) < 0) { +- note(N_WARNING, "Flushing pid file failed: errno %d (%s)\n", +- errno, strerror(errno)); ++ xlog_warn("Flushing pid file failed: errno %d (%m)\n", ++ errno); + } + } + +@@ -189,42 +178,10 @@ static void truncate_pidfile(void) + { + if (pidfd >= 0) { + if (ftruncate(pidfd, 0) < 0) { +- note(N_WARNING, "truncating pid file failed: errno %d (%s)\n", +- errno, strerror(errno)); +- } +- } +-} +- +-static void drop_privs(void) +-{ +- struct stat st; +- +- if (stat(SM_DIR, &st) == -1 && +- stat(DIR_BASE, &st) == -1) { +- st.st_uid = 0; +- st.st_gid = 0; +- } +- +- if (st.st_uid == 0) { +- note(N_WARNING, "statd running as root. chown %s to choose different user\n", +- SM_DIR); +- return; +- } +- /* better chown the pid file before dropping, as if it +- * if over nfs we might loose access +- */ +- if (pidfd >= 0) { +- if (fchown(pidfd, st.st_uid, st.st_gid) < 0) { +- note(N_ERROR, "Unable to change owner of %s: %d (%s)", +- SM_DIR, strerror (errno)); ++ xlog_warn("truncating pid file failed: errno %d (%m)\n", ++ errno); + } + } +- setgroups(0, NULL); +- if (setgid(st.st_gid) == -1 +- || setuid(st.st_uid) == -1) { +- note(N_ERROR, "Fail to drop privileges"); +- exit(1); +- } + } + + static void run_sm_notify(int outport) +@@ -266,6 +223,8 @@ int main (int argc, char **argv) + + /* Default: daemon mode, no other options */ + run_mode = 0; ++ xlog_stderr(0); ++ xlog_syslog(1); + + /* Set the basename */ + if ((name_p = strrchr(argv[0],'/')) != NULL) { +@@ -274,13 +233,6 @@ int main (int argc, char **argv) + name_p = argv[0]; + } + +- /* Get the version */ +- if ((version_p = strrchr(VERSION,' ')) != NULL) { +- version_p++; +- } else { +- version_p = VERSION; +- } +- + /* Set hostname */ + MY_NAME = NULL; + +@@ -289,7 +241,7 @@ int main (int argc, char **argv) + switch (arg) { + case 'V': /* Version */ + case 'v': +- printf("%s version %s\n",name_p,version_p); ++ printf("%s version " VERSION "\n",name_p); + exit(0); + case 'F': /* Foreground/nodaemon mode */ + run_mode |= MODE_NODAEMON; +@@ -326,34 +278,8 @@ int main (int argc, char **argv) + MY_NAME = xstrdup(optarg); + break; + case 'P': +- +- if ((DIR_BASE = xstrdup(optarg)) == NULL) { +- fprintf(stderr, "%s: xstrdup(%s) failed!\n", +- argv[0], optarg); +- exit(1); +- } +- +- SM_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm")); +- SM_BAK_DIR = xmalloc(strlen(DIR_BASE) + 1 + sizeof("sm.bak")); +- SM_STAT_PATH = xmalloc(strlen(DIR_BASE) + 1 + sizeof("state")); +- +- if ((SM_DIR == NULL) +- || (SM_BAK_DIR == NULL) +- || (SM_STAT_PATH == NULL)) { +- +- fprintf(stderr, "%s: xmalloc() failed!\n", +- argv[0]); ++ if (!nsm_setup_pathnames(argv[0], optarg)) + exit(1); +- } +- if (DIR_BASE[strlen(DIR_BASE)-1] == '/') { +- sprintf(SM_DIR, "%ssm", DIR_BASE ); +- sprintf(SM_BAK_DIR, "%ssm.bak", DIR_BASE ); +- sprintf(SM_STAT_PATH, "%sstate", DIR_BASE ); +- } else { +- sprintf(SM_DIR, "%s/sm", DIR_BASE ); +- sprintf(SM_BAK_DIR, "%s/sm.bak", DIR_BASE ); +- sprintf(SM_STAT_PATH, "%s/state", DIR_BASE ); +- } + break; + case 'H': /* PRC: specify the ha-callout program */ + if ((ha_callout_prog = xstrdup(optarg)) == NULL) { +@@ -383,7 +309,6 @@ int main (int argc, char **argv) + run_sm_notify(out_port); + } + +- + if (!(run_mode & MODE_NODAEMON)) { + run_mode &= ~MODE_LOG_STDERR; /* Never log to console in + daemon mode. */ +@@ -432,10 +357,6 @@ int main (int argc, char **argv) + /* Child. */ + close(pipefds[0]); + setsid (); +- if (chdir (DIR_BASE) == -1) { +- perror("statd: Could not chdir"); +- exit(1); +- } + + while (pipefds[1] <= 2) { + pipefds[1] = dup(pipefds[1]); +@@ -455,7 +376,13 @@ int main (int argc, char **argv) + + /* Child. */ + +- log_init (/*name_p,version_p*/); ++ if (run_mode & MODE_LOG_STDERR) { ++ xlog_syslog(0); ++ xlog_stderr(1); ++ xlog_config(D_ALL, 1); ++ } ++ xlog_open(name_p); ++ xlog(L_NOTICE, "Version " VERSION " starting"); + + log_modes(); + +@@ -495,25 +422,48 @@ int main (int argc, char **argv) + * pass on any SM_NOTIFY that arrives + */ + load_state(); +- load_state_number(); +- pmap_unset (SM_PROG, SM_VERS); + +- /* this registers both UDP and TCP services */ +- rpc_init("statd", SM_PROG, SM_VERS, sm_prog_1, port); ++ MY_STATE = nsm_get_state(0); ++ if (MY_STATE == 0) ++ exit(1); ++ xlog(D_GENERAL, "Local NSM state number: %d", MY_STATE); ++ nsm_update_kernel_state(MY_STATE); ++ ++ /* ++ * ORDER ++ * Clear old listeners while still root, to override any ++ * permission checking done by rpcbind. ++ */ ++ statd_unregister(); ++ ++ /* ++ * ORDER ++ */ ++ if (!nsm_drop_privileges(pidfd)) ++ exit(1); ++ ++ /* ++ * ORDER ++ * Create RPC listeners after dropping privileges. This permits ++ * statd to unregister its own listeners when it exits. ++ */ ++ if (nfs_svc_create("statd", SM_PROG, SM_VERS, sm_prog_1, port) == 0) { ++ xlog(L_ERROR, "failed to create RPC listeners, exiting"); ++ exit(1); ++ } ++ atexit(statd_unregister); + + /* If we got this far, we have successfully started, so notify parent */ + if (pipefds[1] > 0) { + status = 0; + if (write(pipefds[1], &status, 1) != 1) { +- note(N_WARNING, "writing to parent pipe failed: errno %d (%s)\n", ++ xlog_warn("writing to parent pipe failed: errno %d (%s)\n", + errno, strerror(errno)); + } + close(pipefds[1]); + pipefds[1] = -1; + } + +- drop_privs(); +- + for (;;) { + /* + * Handle incoming requests: SM_NOTIFY socket requests, as +@@ -541,29 +491,3 @@ int main (int argc, char **argv) + } + return 0; + } +- +-static void +-load_state_number(void) +-{ +- int fd; +- const char *file = "/proc/sys/fs/nfs/nsm_local_state"; +- +- if ((fd = open(SM_STAT_PATH, O_RDONLY)) == -1) +- return; +- +- if (read(fd, &MY_STATE, sizeof(MY_STATE)) != sizeof(MY_STATE)) { +- note(N_WARNING, "Unable to read state from '%s': errno %d (%s)", +- SM_STAT_PATH, errno, strerror(errno)); +- } +- close(fd); +- fd = open(file, O_WRONLY); +- if (fd >= 0) { +- char buf[20]; +- snprintf(buf, sizeof(buf), "%d", MY_STATE); +- if (write(fd, buf, strlen(buf)) != strlen(buf)) +- note(N_WARNING, "Writing to '%s' failed: errno %d (%s)", +- file, errno, strerror(errno)); +- close(fd); +- } +- +-} +diff --git a/utils/statd/statd.h b/utils/statd/statd.h +index 88ba208..e89e666 100644 +--- a/utils/statd/statd.h ++++ b/utils/statd/statd.h +@@ -11,29 +11,7 @@ + + #include "sm_inter.h" + #include "system.h" +-#include "log.h" +- +-/* +- * Paths and filenames. +- */ +-#if defined(NFS_STATEDIR) +-# define DEFAULT_DIR_BASE NFS_STATEDIR "/" +-#else +-# define DEFAULT_DIR_BASE "/var/lib/nfs/" +-#endif +- +-#define DEFAULT_SM_DIR DEFAULT_DIR_BASE "sm" +-#define DEFAULT_SM_BAK_DIR DEFAULT_DIR_BASE "sm.bak" +-#define DEFAULT_SM_STAT_PATH DEFAULT_DIR_BASE "state" +- +-/* Added to support run-time specification of state directory path. +- * j_carlos_gomez@yahoo.com +- */ +- +-extern char * DIR_BASE; +-extern char * SM_DIR; +-extern char * SM_BAK_DIR; +-extern char * SM_STAT_PATH; ++#include "xlog.h" + + /* + * Status definitions. +@@ -44,7 +22,12 @@ extern char * SM_STAT_PATH; + /* + * Function prototypes. + */ +-extern void change_state(void); ++extern _Bool statd_matchhostname(const char *hostname1, const char *hostname2); ++extern _Bool statd_present_address(const struct sockaddr *sap, char *buf, ++ const size_t buflen); ++__attribute_malloc__ ++extern char * statd_canonical_name(const char *hostname); ++ + extern void my_svc_run(void); + extern void notify_hosts(void); + extern void shuffle_dirs(void); +@@ -53,7 +36,6 @@ extern int process_notify_list(void); + extern int process_reply(FD_SET_TYPE *); + extern char * xstrdup(const char *); + extern void * xmalloc(size_t); +-extern void xunlink (char *, char *); + extern void load_state(void); + + /* +@@ -84,10 +66,3 @@ extern int run_mode; + * another host.... */ + #define STATIC_HOSTNAME 8 /* Always use the hostname set by -n */ + #define MODE_NO_NOTIFY 16 /* Don't notify peers of a reboot */ +-/* +- * Program name and version pointers -- See statd.c for the reasoning +- * as to why they're global. +- */ +-extern char *name_p; /* program basename */ +-extern const char *version_p; /* program version */ +- +diff --git a/utils/statd/statd.man b/utils/statd/statd.man +index e8be9f3..ffc5e95 100644 +--- a/utils/statd/statd.man ++++ b/utils/statd/statd.man +@@ -1,191 +1,400 @@ +-.\" +-.\" statd(8) ++.\"@(#)rpc.statd.8" + .\" + .\" Copyright (C) 1999 Olaf Kirch + .\" Modified by Jeffrey A. Uphoff, 1999, 2002, 2005. + .\" Modified by Lon Hohberger, 2000. + .\" Modified by Paul Clements, 2004. +-.TH rpc.statd 8 "31 Aug 2004" ++.\" ++.\" Rewritten by Chuck Lever , 2009. ++.\" Copyright 2009 Oracle. All rights reserved. ++.\" ++.TH RPC.STATD 8 "1 November 2009 + .SH NAME +-rpc.statd \- NSM status monitor ++rpc.statd \- NSM service daemon + .SH SYNOPSIS +-.B "rpc.statd [-FNL] [-d] [-?] [-n " name "] [-o " port "] [-p " port "] [-H " prog "] [-V]" ++.BI "rpc.statd [-dh?FLNvVw] [-H " prog "] [-n " my-name "] [-o " outgoing-port "] [-p " listener-port "] [-P " path " ] + .SH DESCRIPTION +-The ++File locks are not part of persistent file system state. ++Lock state is thus lost when a host reboots. ++.PP ++Network file systems must also detect when lock state is lost ++because a remote host has rebooted. ++After an NFS client reboots, an NFS server must release all file locks ++held by applications that were running on that client. ++After a server reboots, a client must remind the ++server of file locks held by applications running on that client. ++.PP ++For NFS version 2 [RFC1094] and NFS version 3 [RFC1813], the ++.I Network Status Monitor ++protocol (or NSM for short) ++is used to notify NFS peers of reboots. ++On Linux, two separate user-space components constitute the NSM service: ++.TP + .B rpc.statd +-server implements the NSM (Network Status Monitor) RPC protocol. +-This service is somewhat misnamed, since it doesn't actually provide +-active monitoring as one might suspect; instead, NSM implements a +-reboot notification service. It is used by the NFS file locking service, +-.BR rpc.lockd , +-to implement lock recovery when the NFS server machine crashes and +-reboots. +-.SS Operation +-For each NFS client or server machine to be monitored, +-.B rpc.statd +-creates a file in +-.BR /var/lib/nfs/sm . +-When starting, it normally runs ++A daemon that listens for reboot notifications from other hosts, and ++manages the list of hosts to be notified when the local system reboots ++.TP ++.B sm-notify ++A helper program that notifies NFS peers after the local system reboots ++.PP ++The local NFS lock manager alerts its local ++.B rpc.statd ++of each remote peer that should be monitored. ++When the local system reboots, the + .B sm-notify +-to iterate through these files and notify the +-peer ++command notifies the NSM service on monitored peers of the reboot. ++When a remote reboots, that peer notifies the local ++.BR rpc.statd , ++which in turn passes the reboot notification ++back to the local NFS lock manager. ++.SH NSM OPERATION IN DETAIL ++The first file locking interaction between an NFS client and server causes ++the NFS lock managers on both peers to contact their local NSM service to ++store information about the opposite peer. ++On Linux, the local lock manager contacts ++.BR rpc.statd . ++.PP ++.B rpc.statd ++records information about each monitored NFS peer on persistent storage. ++This information describes how to contact a remote peer ++in case the local system reboots, ++how to recognize which monitored peer is reporting a reboot, ++and how to notify the local lock manager when a monitored peer ++indicates it has rebooted. ++.PP ++An NFS client sends a hostname, known as the client's ++.IR caller_name , ++in each file lock request. ++An NFS server can use this hostname to send asynchronous GRANT ++calls to a client, or to notify the client it has rebooted. ++.PP ++The Linux NFS server can provide the client's ++.I caller_name ++or the client's network address to ++.BR rpc.statd . ++For the purposes of the NSM protocol, ++this name or address is known as the monitored peer's ++.IR mon_name . ++In addition, the local lock manager tells + .B rpc.statd +-on those machines. ++what it thinks its own hostname is. ++For the purposes of the NSM protocol, ++this hostname is known as ++.IR my_name . ++.PP ++There is no equivalent interaction between an NFS server and a client ++to inform the client of the server's ++.IR caller_name . ++Therefore NFS clients do not actually know what ++.I mon_name ++an NFS server might use in an SM_NOTIFY request. ++The Linux NFS client uses the server hostname from the mount command ++to identify rebooting NFS servers. ++.SS Reboot notification ++When the local system reboots, the ++.B sm-notify ++command reads the list of monitored peers from persistent storage and ++sends an SM_NOTIFY request to the NSM service on each listed remote peer. ++It uses the ++.I mon_name ++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 ++.I my_name ++string. ++The remote ++.B rpc.statd ++matches incoming SM_NOTIFY requests using this string, ++or the caller's network address, ++to one or more peers on its own monitor list. ++.PP ++If ++.B rpc.statd ++does not find a peer on its monitor list that matches ++an incoming SM_NOTIFY request, ++the notification is not forwarded to the local lock manager. ++In addition, each peer has its own ++.IR "NSM state number" , ++a 32-bit integer that is bumped after each reboot by the ++.B sm-notify ++command. ++.B rpc.statd ++uses this number to distinguish between actual reboots ++and replayed notifications. ++.PP ++Part of NFS lock recovery is rediscovering ++which peers need to be monitored again. ++The ++.B sm-notify ++command clears the monitor list on persistent storage after each reboot. + .SH OPTIONS + .TP +-.B -F +-By default, ++.BR -d , " --no-syslog ++Causes + .B rpc.statd +-forks and puts itself in the background when started. The +-.B -F +-argument tells it to remain in the foreground. This option is +-mainly for debugging purposes. +-.TP +-.B -d +-By default, +-.B rpc.statd +-sends logging messages via +-.BR syslog (3) +-to system log. The +-.B -d +-argument forces it to log verbose output to +-.B stderr +-instead. This option is mainly for debugging purposes, and may only +-be used in conjunction with the ++to write log messages on ++.I stderr ++instead of to the system log, ++if the + .B -F +-parameter. ++option was also specified. + .TP +-.BI "\-n," "" " \-\-name " name +-specify a name for +-.B rpc.statd +-to use as the local hostname. By default, +-.BR rpc.statd +-will call +-.BR gethostname (2) +-to get the local hostname. Specifying +-a local hostname may be useful for machines with more than one +-interfaces. ++.BR -F , " --foreground ++Keeps ++.B rpc.statd ++attached to its controlling terminal so that NSM ++operation can be monitored directly or run under a debugger. ++If this option is not specified, ++.B rpc.statd ++backgrounds itself soon after it starts. + .TP +-.BI "\-o," "" " \-\-outgoing\-port " port +-specify a port for +-.B rpc.statd +-to send outgoing status requests from. By default, +-.BR rpc.statd +-will ask +-.BR portmap (8) +-to assign it a port number. As of this writing, there is not +-a standard port number that +-.BR portmap +-always or usually assigns. Specifying +-a port may be useful when implementing a firewall. ++.BR -h , " -?" , " --help ++Causes ++.B rpc.statd ++to display usage information on ++.I stderr ++and then exit. + .TP +-.BI "\-p," "" " \-\-port " port +-specify a port for +-.B rpc.statd +-to listen on. By default, +-.BR rpc.statd +-will ask +-.BR portmap (8) +-to assign it a port number. As of this writing, there is not +-a standard port number that +-.BR portmap +-always or usually assigns. Specifying +-a port may be useful when implementing a firewall. ++.BI "\-H," "" " \-\-ha-callout " prog ++Specifies a high availability callout program. ++If this option is not specified, no callouts are performed. ++See the ++.B High-availability callouts ++section below for details. + .TP +-.BI "\-P," "" " \-\-state\-directory\-path " directory +-specify a directory in which to place statd state information. +-If this option is not specified the default of +-.BR /var/lib/nfs +-is used. ++.BR -L , " --no-notify ++Prevents ++.B rpc.statd ++from running the ++.B sm-notify ++command when it starts up, ++preserving the existing NSM state number and monitor list. ++.IP ++Note: the ++.B sm-notify ++command contains a check to ensure it runs only once after each system reboot. ++This prevents spurious reboot notification if ++.B rpc.statd ++restarts without the ++.B -L ++option. + .TP +-.B -N +-Causes statd to run in the notify-only mode. When started in this mode, the +-statd program will check its state directory, send notifications to any +-monitored nodes, and exit once the notifications have been sent. This mode is +-used to enable Highly Available NFS implementations (i.e. HA-NFS). +-This mode is deprecated \- ++.BI "\-n, " "" "\-\-name " ipaddr " | " hostname ++Specifies the bind address used for RPC listener sockets. ++The ++.I ipaddr ++form can be expressed as either an IPv4 or an IPv6 presentation address. ++If this option is not specified, ++.B rpc.statd ++uses a wildcard address as the transport bind address. ++.IP ++This string is also passed to the + .B sm-notify +-should be used directly instead. ++command to be used as the source address from which ++to send reboot notification requests. ++See ++.BR sm-notify (8) ++for details. + .TP +-.BR -L , " --no-notify +-Inhibits the running of +-.BR sm-notify . +-If ++.BR -N ++Causes ++.B rpc.statd ++to run the + .B sm-notify +-is run by some other script at boot time, there is no need for +-.B statd +-to start sm-notify itself. This can be appropriate if starting of +-statd needs to be delayed until it is actually need. In such cases ++command, and then exit. ++Since the ++.B sm-notify ++command can also be run directly, this option is deprecated. ++.TP ++.BI "\-o," "" " \-\-outgoing\-port " port ++Specifies the source port number the + .B sm-notify +-should still be run at boot time. ++command should use when sending reboot notifications. ++See ++.BR sm-notify (8) ++for details. + .TP +-.BI "\-H, " "" " \-\-ha-callout " prog +-Specify a high availability callout program, which will receive callouts +-for all client monitor and unmonitor requests. This allows ++.BI "\-p," "" " \-\-port " port ++Specifies the port number used for RPC listener sockets. ++If this option is not specified, + .B rpc.statd +-to be used in a High Availability NFS (HA-NFS) environment. The +-program will be run with 3 arguments: The first is either +-.B add-client +-or +-.B del-client +-depending on the reason for the callout. +-The second will be the name of the client. +-The third will be the name of the server as known to the client. ++chooses a random ephemeral port for each listener socket. ++.IP ++This option can be used to fix the port value of its listeners when ++SM_NOTIFY requests must traverse a firewall between clients and servers. + .TP +-.B -? +-Causes ++.BI "\-P, " "" \-\-state\-directory\-path " pathname ++Specifies the pathname of the parent directory ++where NSM state information resides. ++If this option is not specified, + .B rpc.statd +-to print out command-line help and exit. ++uses ++.I /var/lib/nfs ++by default. ++.IP ++After starting, ++.B rpc.statd ++attempts to set its effective UID and GID to the owner ++and group of this directory. + .TP +-.B -V ++.BR -v ", " -V ", " --version + Causes + .B rpc.statd +-to print out version information and exit. +- +- +- +-.SH TCP_WRAPPERS SUPPORT +-This ++to display version information on ++.I stderr ++and then exit. ++.SH SECURITY ++The + .B rpc.statd +-version is protected by the ++daemon must be started as root to acquire privileges needed ++to create sockets with privileged source ports, and to access the ++state information database. ++Because ++.B rpc.statd ++maintains a long-running network service, however, it drops root privileges ++as soon as it starts up to reduce the risk of a privilege escalation attack. ++.PP ++During normal operation, ++the effective user ID it chooses is the owner of the state directory. ++This allows it to continue to access files in that directory after it ++has dropped its root privileges. ++To control which user ID ++.B rpc.statd ++chooses, simply use ++.BR chown (1) ++to set the owner of ++the state directory. ++.PP ++You can also protect your ++.B rpc.statd ++listeners using the ++.B tcp_wrapper ++library or ++.BR iptables (8). ++To use the + .B tcp_wrapper +-library. You have to give the clients access to +-.B rpc.statd +-if they should be allowed to use it. To allow connects from clients of +-the .bar.com domain you could use the following line in /etc/hosts.allow: +- +-statd: .bar.com +- +-You have to use the daemon name ++library, add the hostnames of peers that should be allowed access to ++.IR /etc/hosts.allow . ++Use the daemon name + .B statd +-for the daemon name (even if the binary has a different name). +- +-For further information please have a look at the ++even if the ++.B rpc.statd ++binary has a different filename. ++.P ++For further information see the + .BR tcpd (8) + and + .BR hosts_access (5) +-manual pages. +- +-.SH SIGNALS +-.BR SIGUSR1 +-causes +-.B rpc.statd +-to re-read the notify list from disk +-and send notifications to clients. This can be used in High Availability NFS +-(HA-NFS) environments to notify clients to reacquire file locks upon takeover +-of an NFS export from another server. +- ++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 ++should be observed, including: ++.IP ++The UTS nodename of your systems should match the DNS names that NFS ++peers use to contact them ++.IP ++The UTS nodenames of your systems should always be fully qualified domain names ++.IP ++The forward and reverse DNS mapping of the UTS nodenames should be ++consistent ++.IP ++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. ++Both may continue monitoring each other for a time in case subsequent ++NFS traffic between the two results in fresh mounts and additional ++file locking. ++.PP ++On Linux, if the ++.B lockd ++kernel module is unloaded during normal operation, ++all remote NFS peers are unmonitored. ++This can happen on an NFS client, for example, ++if an automounter removes all NFS mount ++points due to inactivity. ++.SS High-availability callouts ++.B rpc.statd ++can exec a special callout program during processing of ++successful SM_MON, SM_UNMON, and SM_UNMON_ALL requests. ++Such a program may be used in High Availability NFS (HA-NFS) ++environments to track lock state that may need to be migrated after ++a system reboot. ++.PP ++The name of the callout program is specified with the ++.B -H ++option. ++The program is run with 3 arguments: ++The first is either ++.B add-client ++or ++.B del-client ++depending on the reason for the callout. ++The second is the ++.I mon_name ++of the monitored peer. ++The third is the ++.I caller_name ++of the requesting lock manager. ++.SS IPv6 and TI-RPC support ++TI-RPC is a pre-requisite for supporting NFS on IPv6. ++If TI-RPC support is built into ++.BR rpc.statd , ++it attempts to start listeners on network transports marked ++'visible' in ++.IR /etc/netconfig . ++As long as at least one network transport listener starts successfully, ++.B rpc.statd ++will operate. + .SH FILES +-.BR /var/lib/nfs/state ++.TP 2.5i ++.I /var/lib/nfs/sm ++directory containing monitor list ++.TP 2.5i ++.I /var/lib/nfs/sm.bak ++directory containing notify list ++.TP 2.5i ++.I /var/lib/nfs/state ++NSM state number for this host ++.TP 2.5i ++.I /var/run/run.statd.pid ++pid file ++.TP 2.5i ++.I /etc/netconfig ++network transport capability database ++.SH SEE ALSO ++.BR sm-notify (8), ++.BR nfs (5), ++.BR rpc.nfsd (8), ++.BR rpcbind (8), ++.BR tcpd (8), ++.BR hosts_access (5), ++.BR iptables (8), ++.BR netconfig (5) ++.sp ++RFC 1094 - "NFS: Network File System Protocol Specification" + .br +-.BR /var/lib/nfs/sm/* ++RFC 1813 - "NFS Version 3 Protocol Specification" + .br +-.BR /var/lib/nfs/sm.bak/* +-.SH SEE ALSO +-.BR rpc.nfsd(8), +-.BR portmap(8) ++OpenGroup Protocols for Interworking: XNFS, Version 3W - Chapter 11 + .SH AUTHORS +-.br + Jeff Uphoff + .br + Olaf Kirch +@@ -195,3 +404,5 @@ H.J. Lu + Lon Hohberger + .br + Paul Clements ++.br ++Chuck Lever +diff --git a/utils/statd/svc_run.c b/utils/statd/svc_run.c +index 14ee663..d98ecee 100644 +--- a/utils/statd/svc_run.c ++++ b/utils/statd/svc_run.c +@@ -101,12 +101,12 @@ my_svc_run(void) + + tv.tv_sec = NL_WHEN(notify) - now; + tv.tv_usec = 0; +- dprintf(N_DEBUG, "Waiting for reply... (timeo %d)", ++ xlog(D_GENERAL, "Waiting for reply... (timeo %d)", + tv.tv_sec); + selret = select(FD_SETSIZE, &readfds, + (void *) 0, (void *) 0, &tv); + } else { +- dprintf(N_DEBUG, "Waiting for client connections."); ++ xlog(D_GENERAL, "Waiting for client connections"); + selret = select(FD_SETSIZE, &readfds, + (void *) 0, (void *) 0, (struct timeval *) 0); + } +@@ -116,8 +116,7 @@ my_svc_run(void) + if (errno == EINTR || errno == ECONNREFUSED + || errno == ENETUNREACH || errno == EHOSTUNREACH) + continue; +- note(N_ERROR, "my_svc_run() - select: %s", +- strerror (errno)); ++ xlog(L_ERROR, "my_svc_run() - select: %m"); + return; + + case 0: +diff --git a/utils/statd/version.h b/utils/statd/version.h +deleted file mode 100644 +index 12f16bd..0000000 +--- a/utils/statd/version.h ++++ /dev/null +@@ -1,7 +0,0 @@ +-/* +- * Copyright (C) 1997-1999 Jeffrey A. Uphoff +- * +- * NSM for Linux. +- */ +- +-#define STATD_RELEASE "1.1.1" diff --git a/nfs-utils.spec b/nfs-utils.spec index 5ea425a..d8f2ad5 100644 --- a/nfs-utils.spec +++ b/nfs-utils.spec @@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser Name: nfs-utils URL: http://sourceforge.net/projects/nfs Version: 1.2.1 -Release: 13%{?dist} +Release: 14%{?dist} Epoch: 1 # group all 32bit related archs @@ -18,11 +18,9 @@ Source13: rpcgssd.init Source14: rpcsvcgssd.init Source15: nfs.sysconfig -Patch000: nfs-utils-1.2.2-rc8.patch -Patch001: nfs-utils-1.2.1-compile.patch -Patch002: nfs-utils-1.2.1-statdpath.patch -Patch003: nfs-utils-1.2.1-mount-config.patch -Patch004: nfs-utils-1.2.1-default-family.patch +Patch000: nfs-utils-1.2.2-rc9.patch +Patch001: nfs-utils-1.2.1-statdpath.patch +Patch002: nfs-utils-1.2.1-default-family.patch Patch100: nfs-utils-1.2.1-statdpath-man.patch Patch101: nfs-utils-1.2.1-exp-subtree-warn-off.patch @@ -77,8 +75,6 @@ This package also contains the mount.nfs and umount.nfs program. %patch000 -p1 %patch001 -p1 %patch002 -p1 -%patch003 -p1 -%patch004 -p1 %patch100 -p1 %patch101 -p1 @@ -112,7 +108,7 @@ make all %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT{/sbin,/usr/sbin} -mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/{man5,man8} +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man8 mkdir -p $RPM_BUILD_ROOT{/etc/rc.d/init.d,/etc/sysconfig} make DESTDIR=$RPM_BUILD_ROOT install install -s -m 755 tools/rpcdebug/rpcdebug $RPM_BUILD_ROOT/usr/sbin @@ -255,6 +251,9 @@ fi %attr(4755,root,root) /sbin/umount.nfs4 %changelog +* Fri Jan 22 2010 Steve Dickson 1.2.1-14 +- Update to upstream RC release: nfs-utils-1-2-2-rc9 + * Thu Jan 21 2010 Steve Dickson 1.2.1-13 - mount.nfs: Configuration file parser ignoring options - mount.nfs: Set the default family for lookups based on