diff options
Diffstat (limited to '')
-rw-r--r-- | utils/mount/network.c | 1801 |
1 files changed, 1801 insertions, 0 deletions
diff --git a/utils/mount/network.c b/utils/mount/network.c new file mode 100644 index 0000000..01ead49 --- /dev/null +++ b/utils/mount/network.c @@ -0,0 +1,1801 @@ +/* + * network.c -- Provide common network functions for NFS mount/umount + * + * Copyright (C) 2007 Oracle. All rights reserved. + * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com> + * + * 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 0211-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <netdb.h> +#include <time.h> +#include <grp.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#if defined(__GLIBC__) && ((__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 24)) +/* Cannot safely include linux/in6.h in old glibc, so hardcode the needed values */ +# define IPV6_PREFER_SRC_PUBLIC 2 +# define IPV6_ADDR_PREFERENCES 72 +#else +# include <linux/in6.h> +#endif +#include <netinet/in.h> +#include <rpc/rpc.h> +#include <rpc/pmap_prot.h> +#include <rpc/pmap_clnt.h> +#include <net/if.h> +#include <ifaddrs.h> + +#include "sockaddr.h" +#include "xcommon.h" +#include "mount.h" +#include "nls.h" +#include "nfs_mount.h" +#include "mount_constants.h" +#include "nfsrpc.h" +#include "parse_opt.h" +#include "network.h" +#include "conffile.h" +#include "nfslib.h" + +#define PMAP_TIMEOUT (10) +#define CONNECT_TIMEOUT (20) +#define MOUNT_TIMEOUT (30) +#define STATD_TIMEOUT (10) + +#define SAFE_SOCKADDR(x) (struct sockaddr *)(char *)(x) + +extern int nfs_mount_data_version; +extern char *progname; +extern int verbose; + +static const char *nfs_mnt_pgmtbl[] = { + "mount", + "mountd", + NULL, +}; + +static const char *nfs_nfs_pgmtbl[] = { + "nfs", + "nfsprog", + NULL, +}; + +static const char *nfs_transport_opttbl[] = { + "udp", + "tcp", + "rdma", + "proto", + NULL, +}; + +static const char *nfs_version_opttbl[] = { + "v2", /* no longer supported */ + "v3", + "v4", + "vers", + "nfsvers", + NULL, +}; + +static const unsigned long nfs_to_mnt[] = { + 0, + 0, + 1, + 3, +}; + +static const unsigned long mnt_to_nfs[] = { + 0, + 2, + 2, + 3, +}; + +/* + * Map an NFS version into the corresponding Mountd version + */ +unsigned long nfsvers_to_mnt(const unsigned long vers) +{ + if (vers <= 3) + return nfs_to_mnt[vers]; + return 0; +} + +/* + * Map a Mountd version into the corresponding NFS version + */ +static unsigned long mntvers_to_nfs(const unsigned long vers) +{ + if (vers <= 3) + return mnt_to_nfs[vers]; + return 0; +} + +static const unsigned int probe_udp_only[] = { + IPPROTO_UDP, + 0, +}; + +static const unsigned int probe_udp_first[] = { + IPPROTO_UDP, + IPPROTO_TCP, + 0, +}; + +static const unsigned int probe_tcp_first[] = { + IPPROTO_TCP, + IPPROTO_UDP, + 0, +}; + +static const unsigned long probe_nfs2_only[] = { + 2, + 0, +}; + +static const unsigned long probe_nfs3_only[] = { + 3, + 0, +}; + +static const unsigned long probe_mnt1_first[] = { + 1, + 2, + 0, +}; + +static const unsigned long probe_mnt3_only[] = { + 3, + 0, +}; + +static const unsigned int *nfs_default_proto(void); +#ifdef MOUNT_CONFIG +static const unsigned int *nfs_default_proto(void) +{ + extern unsigned long config_default_proto; + /* + * If the default proto has been set and + * its not TCP, start with UDP + */ + if (config_default_proto && config_default_proto != IPPROTO_TCP) + return probe_udp_first; + + return probe_tcp_first; +} +#else +static const unsigned int *nfs_default_proto() +{ + return probe_tcp_first; +} +#endif /* MOUNT_CONFIG */ + +/** + * 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 = { + .ai_family = family, + }; + socklen_t len = *salen; + int error, ret = 0; + + *salen = 0; + + error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results); + switch (error) { + case 0: + break; + case EAI_SYSTEM: + nfs_error(_("%s: DNS resolution failed for %s: %s"), + progname, hostname, strerror(errno)); + return ret; + default: + nfs_error(_("%s: DNS resolution failed for %s: %s"), + progname, hostname, gai_strerror(error)); + return ret; + } + + switch (gai_results->ai_addr->sa_family) { + case AF_INET: + case AF_INET6: + if (len >= gai_results->ai_addrlen) { + *salen = gai_results->ai_addrlen; + memcpy(sap, gai_results->ai_addr, *salen); + ret = 1; + } + break; + default: + /* things are really broken if we get here, so warn */ + nfs_error(_("%s: unrecognized DNS resolution results for %s"), + progname, hostname); + break; + } + + nfs_freeaddrinfo(gai_results); + return ret; +} + +/** + * nfs_gethostbyname - resolve a hostname to an IPv4 address + * @hostname: pointer to a C string containing a DNS hostname + * @sin: returns an IPv4 address + * + * Returns 1 if successful, otherwise zero. + */ +int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) +{ + socklen_t len = sizeof(*sin); + + return nfs_lookup(hostname, AF_INET, (struct sockaddr *)sin, &len); +} + +/** + * nfs_string_to_sockaddr - convert string address to sockaddr + * @address: pointer to presentation format address to convert + * @sap: pointer to socket address buffer to fill in + * @salen: IN: length of address buffer + * OUT: length of converted socket address + * + * Convert a presentation format address string to a socket address. + * 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. + * + * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details + * on presenting IPv6 addresses as text strings. + */ +int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap, + socklen_t *salen) +{ + struct addrinfo *gai_results; + struct addrinfo gai_hint = { + .ai_flags = AI_NUMERICHOST, + }; + socklen_t len = *salen; + int ret = 0; + + *salen = 0; + + if (getaddrinfo(address, NULL, &gai_hint, &gai_results) == 0) { + switch (gai_results->ai_addr->sa_family) { + case AF_INET: + case AF_INET6: + if (len >= gai_results->ai_addrlen) { + *salen = gai_results->ai_addrlen; + memcpy(sap, gai_results->ai_addr, *salen); + ret = 1; + } + break; + } + nfs_freeaddrinfo(gai_results); + } + + return ret; +} + +/** + * nfs_present_sockaddr - convert sockaddr to string + * @sap: pointer to socket address to convert + * @salen: length of socket address + * @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 1 if successful; otherwise zero. + * + * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details + * on presenting IPv6 addresses as text strings. + */ +int nfs_present_sockaddr(const struct sockaddr *sap, const socklen_t salen, + char *buf, const size_t buflen) +{ +#ifdef HAVE_GETNAMEINFO + int result; + + result = getnameinfo(sap, salen, buf, buflen, + NULL, 0, NI_NUMERICHOST); + if (!result) + return 1; + + nfs_error(_("%s: invalid server address: %s"), progname, + gai_strerror(result)); + return 0; +#else /* HAVE_GETNAMEINFO */ + char *addr; + + if (sap->sa_family == AF_INET) { + addr = inet_ntoa(((struct sockaddr_in *)sap)->sin_addr); + if (addr && strlen(addr) < buflen) { + strcpy(buf, addr); + return 1; + } + } + + nfs_error(_("%s: invalid server address"), progname); + return 0; +#endif /* HAVE_GETNAMEINFO */ +} + +/* + * Attempt to connect a socket, but time out after "timeout" seconds. + * + * On error return, caller closes the socket. + */ +static int connect_to(int fd, struct sockaddr *addr, + socklen_t addrlen, int timeout) +{ + int ret, saved; + fd_set rset, wset; + struct timeval tv = { + .tv_sec = timeout, + }; + + saved = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, saved | O_NONBLOCK); + + ret = connect(fd, addr, addrlen); + if (ret < 0 && errno != EINPROGRESS) + return -1; + if (ret == 0) + goto out; + + FD_ZERO(&rset); + FD_SET(fd, &rset); + wset = rset; + ret = select(fd + 1, &rset, &wset, NULL, &tv); + if (ret == 0) { + errno = ETIMEDOUT; + return -1; + } + if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + return -1; + if (error) { + errno = error; + return -1; + } + } else + return -1; + +out: + fcntl(fd, F_SETFL, saved); + return 0; +} + +/* + * Create a socket that is locally bound to a reserved or non-reserved port. + * + * The caller should check rpc_createerr to determine the cause of any error. + */ +static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot, + unsigned int timeout, int resvp, int conn) +{ + int so, cc, type; + struct sockaddr_in laddr; + socklen_t namelen = sizeof(laddr); + + type = (p_prot == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM); + if ((so = socket (AF_INET, type, p_prot)) < 0) + goto err_socket; + + laddr.sin_family = AF_INET; + laddr.sin_port = 0; + laddr.sin_addr.s_addr = htonl(INADDR_ANY); + if (resvp) { + if (bindresvport(so, &laddr) < 0) + goto err_bindresvport; + } + if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) { + cc = connect_to(so, SAFE_SOCKADDR(saddr), namelen, + timeout); + if (cc < 0) + goto err_connect; + } + return so; + +err_socket: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to create %s socket: errno %d (%s)\n"), + progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"), + errno, strerror(errno)); + } + return RPC_ANYSOCK; + +err_bindresvport: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to bindresvport %s socket: errno %d" + " (%s)\n"), + progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"), + errno, strerror(errno)); + } + close(so); + return RPC_ANYSOCK; + +err_connect: + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + if (verbose) { + nfs_error(_("%s: Unable to connect to %s:%d, errno %d (%s)\n"), + progname, inet_ntoa(saddr->sin_addr), + ntohs(saddr->sin_port), errno, strerror(errno)); + } + close(so); + return RPC_ANYSOCK; +} + +static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen, + const rpcprog_t program, const rpcvers_t version, + const unsigned short protocol, + const unsigned short port) +{ + char buf[NI_MAXHOST]; + + if (!verbose) + return; + + if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) { + buf[0] = '\0'; + strcat(buf, "unknown host"); + } + + fprintf(stderr, _("%s: trying %s prog %lu vers %lu prot %s port %d\n"), + progname, buf, (unsigned long)program, + (unsigned long)version, + (protocol == IPPROTO_UDP ? _("UDP") : _("TCP")), + port); +} + +static void nfs_pp_debug2(const char *str) +{ + if (!verbose) + return; + + if (rpc_createerr.cf_error.re_status == RPC_CANTRECV || + rpc_createerr.cf_error.re_status == RPC_CANTSEND) + nfs_error(_("%s: portmap query %s%s - %s"), + progname, str, clnt_spcreateerror(""), + strerror(rpc_createerr.cf_error.re_errno)); + else + nfs_error(_("%s: portmap query %s%s"), + progname, str, clnt_spcreateerror("")); +} + +/* + * Use the portmapper to discover whether or not the service we want is + * available. The lists 'versions' and 'protos' define ordered sequences + * of service versions and udp/tcp protocols to probe for. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. Note that if a port is already specified + * in @pmap and it matches the rpcbind query result, nfs_probe_port() does + * not perform an RPC ping. + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap, const unsigned long *versions, + const unsigned int *protos) +{ + 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; + unsigned long vers = pmap->pm_vers; + unsigned short p_port; + + memcpy(saddr, sap, salen); + p_prot = prot ? &prot : protos; + p_vers = vers ? &vers : versions; + + for (;;) { + if (verbose) + printf(_("%s: prog %lu, trying vers=%lu, prot=%u\n"), + progname, prog, *p_vers, *p_prot); + p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot); + if (p_port) { + if (!port || port == p_port) { + nfs_set_port(saddr, p_port); + nfs_pp_debug(saddr, salen, prog, *p_vers, + *p_prot, p_port); + if (nfs_rpc_ping(saddr, salen, prog, + *p_vers, *p_prot, NULL)) + goto out_ok; + } else + rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED; + } + if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED && + rpc_createerr.cf_stat != RPC_TIMEDOUT && + rpc_createerr.cf_stat != RPC_CANTRECV && + rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH) + break; + + if (!prot) { + if (*++p_prot) { + nfs_pp_debug2("retrying"); + continue; + } + p_prot = protos; + } + if (rpc_createerr.cf_stat == RPC_TIMEDOUT || + rpc_createerr.cf_stat == RPC_CANTRECV) + break; + + if (vers || !*++p_vers) + break; + } + + nfs_pp_debug2("failed"); + return 0; + +out_ok: + if (!vers) + pmap->pm_vers = *p_vers; + if (!prot) + pmap->pm_prot = *p_prot; + if (!port) + pmap->pm_port = p_port; + nfs_clear_rpc_createerr(); + return 1; +} +/* + * Probe a server's NFS service to determine which versions and + * transport protocols are supported. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. If all three are already specified, simply + * return success without an rpcbind query or RPC ping (we may be trying + * to mount an NFS service that is not advertised via rpcbind). + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap, int checkv4) +{ + if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) + return 1; + + if (nfs_mount_data_version >= 4) { + const unsigned int *probe_proto; + int ret; + struct sockaddr_storage save_sa; + + probe_proto = nfs_default_proto(); + memcpy(&save_sa, sap, salen); + + ret = nfs_probe_port(sap, salen, pmap, + probe_nfs3_only, probe_proto); + if (!ret || !checkv4 || probe_proto != probe_tcp_first) + return ret; + + nfs_set_port((struct sockaddr *)&save_sa, NFS_PORT); + ret = nfs_rpc_ping((struct sockaddr *)&save_sa, salen, + NFS_PROGRAM, 4, IPPROTO_TCP, NULL); + if (ret) { + rpc_createerr.cf_stat = RPC_FAILED; + rpc_createerr.cf_error.re_errno = EAGAIN; + return 0; + } + return 1; + } else + return nfs_probe_port(sap, salen, pmap, + probe_nfs2_only, probe_udp_only); +} + +/* + * Probe a server's mountd service to determine which versions and + * transport protocols are supported. + * + * Returns 1 if the requested service port is unambiguous and pingable; + * @pmap is filled in with the version, port, and transport protocol used + * during the successful ping. If all three are already specified, simply + * return success without an rpcbind query or RPC ping (we may be trying + * to mount an NFS service that is not advertised via rpcbind). + * + * If an error occurs or the requested service isn't available, zero is + * returned; rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_mntport(const struct sockaddr *sap, const socklen_t salen, + struct pmap *pmap) +{ + if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) + return 1; + + if (nfs_mount_data_version >= 4) + return nfs_probe_port(sap, salen, pmap, + probe_mnt3_only, probe_udp_first); + else + return nfs_probe_port(sap, salen, pmap, + probe_mnt1_first, probe_udp_only); +} + +/* + * Probe a server's mountd service to determine which versions and + * transport protocols are supported. Invoked when the protocol + * version is already known for both the NFS and mountd service. + * + * Returns 1 and fills in both @pmap structs if the requested service + * ports are unambiguous and pingable. Otherwise zero is returned; + * rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +static int nfs_probe_version_fixed(const struct sockaddr *mnt_saddr, + const socklen_t mnt_salen, + struct pmap *mnt_pmap, + const struct sockaddr *nfs_saddr, + const socklen_t nfs_salen, + struct pmap *nfs_pmap) +{ + if (!nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap, 0)) + return 0; + return nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap); +} + +/** + * nfs_probe_bothports - discover the RPC endpoints of mountd and NFS server + * @mnt_saddr: pointer to socket address of mountd server + * @mnt_salen: length of mountd server's address + * @mnt_pmap: IN: partially filled-in mountd RPC service tuple; + * OUT: fully filled-in mountd RPC service tuple + * @nfs_saddr: pointer to socket address of NFS server + * @nfs_salen: length of NFS server's address + * @nfs_pmap: IN: partially filled-in NFS RPC service tuple; + * OUT: fully filled-in NFS RPC service tuple + * @checkv4: Flag indicating that if v3 is available we must also + * check v4, and if that is available, set re_errno to EAGAIN. + * + * Returns 1 and fills in both @pmap structs if the requested service + * ports are unambiguous and pingable. Otherwise zero is returned; + * rpccreateerr.cf_stat is set to reflect the nature of the error. + */ +int nfs_probe_bothports(const struct sockaddr *mnt_saddr, + const socklen_t mnt_salen, + struct pmap *mnt_pmap, + const struct sockaddr *nfs_saddr, + const socklen_t nfs_salen, + struct pmap *nfs_pmap, + int checkv4) +{ + struct pmap save_nfs, save_mnt; + const unsigned long *probe_vers; + + if (mnt_pmap->pm_vers && !nfs_pmap->pm_vers) + nfs_pmap->pm_vers = mntvers_to_nfs(mnt_pmap->pm_vers); + else if (nfs_pmap->pm_vers && !mnt_pmap->pm_vers) + mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers); + + if (nfs_pmap->pm_vers) + return nfs_probe_version_fixed(mnt_saddr, mnt_salen, mnt_pmap, + nfs_saddr, nfs_salen, nfs_pmap); + + memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs)); + memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt)); + probe_vers = (nfs_mount_data_version >= 4) ? + probe_mnt3_only : probe_mnt1_first; + + for (; *probe_vers; probe_vers++) { + nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers); + if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap, checkv4) != 0) { + mnt_pmap->pm_vers = *probe_vers; + if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0) + return 1; + memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap)); + } + switch (rpc_createerr.cf_stat) { + case RPC_PROGVERSMISMATCH: + case RPC_PROGNOTREGISTERED: + break; + default: + return 0; + } + memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap)); + } + + return 0; +} + +/** + * probe_bothports - discover the RPC endpoints of mountd and NFS server + * @mnt_server: pointer to address and pmap argument for mountd results + * @nfs_server: pointer to address and pmap argument for NFS server + * + * This is the legacy API that takes "clnt_addr_t" for both servers, + * but supports only AF_INET addresses. + * + * Returns 1 and fills in the pmap field in both clnt_addr_t structs + * if the requested service ports are unambiguous and pingable. + * Otherwise zero is returned; rpccreateerr.cf_stat is set to reflect + * the nature of the error. + */ +int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server) +{ + struct sockaddr *mnt_addr = SAFE_SOCKADDR(&mnt_server->saddr); + struct sockaddr *nfs_addr = SAFE_SOCKADDR(&nfs_server->saddr); + + return nfs_probe_bothports(mnt_addr, sizeof(mnt_server->saddr), + &mnt_server->pmap, + nfs_addr, sizeof(nfs_server->saddr), + &nfs_server->pmap, 0); +} + +/** + * start_statd - attempt to start rpc.statd + * + * Returns 1 if statd is running; otherwise zero. + */ +int start_statd(void) +{ +#ifdef START_STATD + struct stat stb; +#endif + + if (nfs_probe_statd()) + return 1; + +#ifdef START_STATD + if (stat(START_STATD, &stb) == 0) { + if (S_ISREG(stb.st_mode) && (stb.st_mode & S_IXUSR)) { + int cnt = STATD_TIMEOUT * 10; + int status = 0; + char * const envp[1] = { NULL }; + const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 100000000, + }; + pid_t pid = fork(); + switch (pid) { + case 0: /* child */ + setgroups(0, NULL); + if (setgid(0) < 0) + nfs_error(_("%s: setgid(0) failed: %s"), + progname, strerror(errno)); + if (setuid(0) < 0) + nfs_error(_("%s: setuid(0) failed: %s"), + progname, strerror(errno)); + execle(START_STATD, START_STATD, NULL, envp); + exit(1); + case -1: /* error */ + nfs_error(_("%s: fork failed: %s"), + progname, strerror(errno)); + break; + default: /* parent */ + if (waitpid(pid, &status,0) == pid && + status == 0) + /* assume it worked */ + return 1; + break; + } + while (1) { + if (nfs_probe_statd()) + return 1; + if (! cnt--) + return 0; + nanosleep(&ts, NULL); + } + } + } +#endif + + return 0; +} + +/** + * nfs_advise_umount - ask the server to remove a share from it's rmtab + * @sap: pointer to IP address of server to call + * @salen: length of server address + * @pmap: partially filled-in mountd RPC service tuple + * @argp: directory path of share to "unmount" + * + * Returns one if the unmount call succeeded; zero if the unmount + * failed for any reason; rpccreateerr.cf_stat is set to reflect + * the nature of the error. + * + * We use a fast timeout since this call is advisory only. + */ +int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen, + const struct pmap *pmap, const dirpath *argp) +{ + union nfs_sockaddr address; + struct sockaddr *saddr = &address.sa; + struct pmap mnt_pmap = *pmap; + struct timeval timeout = { + .tv_sec = MOUNT_TIMEOUT >> 3, + }; + CLIENT *client; + enum clnt_stat res = 0; + + memcpy(saddr, sap, salen); + if (nfs_probe_mntport(saddr, salen, &mnt_pmap) == 0) { + if (verbose) + nfs_error(_("%s: Failed to discover mountd port%s"), + progname, clnt_spcreateerror("")); + return 0; + } + nfs_set_port(saddr, mnt_pmap.pm_port); + + client = nfs_get_priv_rpcclient(saddr, salen, mnt_pmap.pm_prot, + mnt_pmap.pm_prog, mnt_pmap.pm_vers, + &timeout); + if (client == NULL) { + if (verbose) + nfs_error(_("%s: Failed to create RPC client%s"), + progname, clnt_spcreateerror("")); + return 0; + } + + client->cl_auth = nfs_authsys_create(); + if (client->cl_auth == NULL) { + if (verbose) + nfs_error(_("%s: Failed to create RPC auth handle"), + progname); + CLNT_DESTROY(client); + return 0; + } + + res = CLNT_CALL(client, MOUNTPROC_UMNT, + (xdrproc_t)xdr_dirpath, (caddr_t)argp, + (xdrproc_t)xdr_void, NULL, + timeout); + if (res != RPC_SUCCESS) { + rpc_createerr.cf_stat = res; + CLNT_GETERR(client, &rpc_createerr.cf_error); + if (verbose) + nfs_error(_("%s: UMNT call failed: %s"), + progname, clnt_sperrno(res)); + + } + auth_destroy(client->cl_auth); + CLNT_DESTROY(client); + + if (res != RPC_SUCCESS) + return 0; + return 1; +} + +/** + * nfs_call_umount - ask the server to remove a share from it's rmtab + * @mnt_server: address of RPC MNT program server + * @argp: directory path of share to "unmount" + * + * Returns one if the unmount call succeeded; zero if the unmount + * failed for any reason. + * + * Note that a side effect of calling this function is that rpccreateerr + * is set. + */ +int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp) +{ + struct sockaddr *sap = SAFE_SOCKADDR(&mnt_server->saddr); + socklen_t salen = sizeof(mnt_server->saddr); + struct pmap *pmap = &mnt_server->pmap; + CLIENT *clnt; + enum clnt_stat res = 0; + int msock; + + if (!nfs_probe_mntport(sap, salen, pmap)) + return 0; + clnt = mnt_openclnt(mnt_server, &msock); + if (!clnt) + return 0; + res = clnt_call(clnt, MOUNTPROC_UMNT, + (xdrproc_t)xdr_dirpath, (caddr_t)argp, + (xdrproc_t)xdr_void, NULL, + TIMEOUT); + mnt_closeclnt(clnt, msock); + + if (res == RPC_SUCCESS) + return 1; + return 0; +} + +/** + * mnt_openclnt - get a handle for a remote mountd service + * @mnt_server: address and pmap arguments of mountd service + * @msock: returns a file descriptor of the underlying transport socket + * + * Returns an active handle for the remote's mountd service + */ +CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock) +{ + struct sockaddr_in *mnt_saddr = &mnt_server->saddr; + struct pmap *mnt_pmap = &mnt_server->pmap; + CLIENT *clnt = NULL; + + mnt_saddr->sin_port = htons((u_short)mnt_pmap->pm_port); + *msock = get_socket(mnt_saddr, mnt_pmap->pm_prot, MOUNT_TIMEOUT, + TRUE, FALSE); + if (*msock == RPC_ANYSOCK) { + if (rpc_createerr.cf_error.re_errno == EADDRINUSE) + /* + * Probably in-use by a TIME_WAIT connection, + * It is worth waiting a while and trying again. + */ + rpc_createerr.cf_stat = RPC_TIMEDOUT; + return NULL; + } + + switch (mnt_pmap->pm_prot) { + case IPPROTO_UDP: + clnt = clntudp_bufcreate(mnt_saddr, + mnt_pmap->pm_prog, mnt_pmap->pm_vers, + RETRY_TIMEOUT, msock, + MNT_SENDBUFSIZE, MNT_RECVBUFSIZE); + break; + case IPPROTO_TCP: + clnt = clnttcp_create(mnt_saddr, + mnt_pmap->pm_prog, mnt_pmap->pm_vers, + msock, + MNT_SENDBUFSIZE, MNT_RECVBUFSIZE); + break; + } + if (clnt) { + /* try to mount hostname:dirname */ + clnt->cl_auth = nfs_authsys_create(); + if (clnt->cl_auth) + return clnt; + CLNT_DESTROY(clnt); + } + return NULL; +} + +/** + * mnt_closeclnt - terminate a handle for a remote mountd service + * @clnt: pointer to an active handle for a remote mountd service + * @msock: file descriptor of the underlying transport socket + * + */ +void mnt_closeclnt(CLIENT *clnt, int msock) +{ + auth_destroy(clnt->cl_auth); + clnt_destroy(clnt); + close(msock); +} + +/** + * clnt_ping - send an RPC ping to the remote RPC service endpoint + * @saddr: server's address + * @prog: target RPC program number + * @vers: target RPC version number + * @prot: target RPC protocol + * @caddr: filled in with our network address + * + * Sigh... GETPORT queries don't actually check the version number. + * In order to make sure that the server actually supports the service + * we're requesting, we open an RPC client, and fire off a NULL + * RPC call. + * + * caddr is the network address that the server will use to call us back. + * On multi-homed clients, this address depends on which NIC we use to + * route requests to the server. + * + * Returns one if successful, otherwise zero. + */ +int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog, + const unsigned long vers, const unsigned int prot, + struct sockaddr_in *caddr) +{ + CLIENT *clnt = NULL; + int sock, status; + static char clnt_res; + struct sockaddr dissolve; + + rpc_createerr.cf_stat = status = 0; + sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE); + if (sock == RPC_ANYSOCK) { + if (rpc_createerr.cf_error.re_errno == ETIMEDOUT) { + /* + * TCP timeout. Bubble up the error to see + * how it should be handled. + */ + rpc_createerr.cf_stat = RPC_TIMEDOUT; + } + return 0; + } + + if (caddr) { + /* Get the address of our end of this connection */ + socklen_t len = sizeof(*caddr); + if (getsockname(sock, (struct sockaddr *) caddr, &len) != 0) + caddr->sin_family = 0; + } + + switch(prot) { + case IPPROTO_UDP: + /* The socket is connected (so we could getsockname successfully), + * but some servers on multi-homed hosts reply from + * the wrong address, so if we stay connected, we lose the reply. + */ + dissolve.sa_family = AF_UNSPEC; + connect(sock, &dissolve, sizeof(dissolve)); + + clnt = clntudp_bufcreate(saddr, prog, vers, + RETRY_TIMEOUT, &sock, + RPCSMALLMSGSIZE, RPCSMALLMSGSIZE); + break; + case IPPROTO_TCP: + clnt = clnttcp_create(saddr, prog, vers, &sock, + RPCSMALLMSGSIZE, RPCSMALLMSGSIZE); + break; + } + if (!clnt) { + close(sock); + return 0; + } + memset(&clnt_res, 0, sizeof(clnt_res)); + status = clnt_call(clnt, NULLPROC, + (xdrproc_t)xdr_void, (caddr_t)NULL, + (xdrproc_t)xdr_void, (caddr_t)&clnt_res, + TIMEOUT); + if (status) { + clnt_geterr(clnt, &rpc_createerr.cf_error); + rpc_createerr.cf_stat = status; + } + clnt_destroy(clnt); + close(sock); + + if (status == RPC_SUCCESS) + return 1; + else + return 0; +} + +/* + * Try a getsockname() on a connected datagram socket. + * + * Returns 1 and fills in @buf if successful; otherwise, zero. + * + * A connected datagram socket prevents leaving a socket in TIME_WAIT. + * This conserves the ephemeral port number space, helping reduce failed + * socket binds during mount storms. + */ +static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen, + struct sockaddr *buf, socklen_t *buflen) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_ANY), + }; + struct sockaddr_in6 sin6 = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT, + }; + int sock, result = 0; + int val; + + sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) + return 0; + + switch (sap->sa_family) { + case AF_INET: + if (bind(sock, SAFE_SOCKADDR(&sin), sizeof(sin)) < 0) + goto out; + break; + case AF_INET6: + /* Make sure the call-back address is public/permanent */ + val = IPV6_PREFER_SRC_PUBLIC; + setsockopt(sock, SOL_IPV6, IPV6_ADDR_PREFERENCES, &val, sizeof(val)); + if (bind(sock, SAFE_SOCKADDR(&sin6), sizeof(sin6)) < 0) + goto out; + break; + default: + errno = EAFNOSUPPORT; + goto out; + } + + if (connect(sock, sap, salen) < 0) + goto out; + + result = !getsockname(sock, buf, buflen); + +out: + close(sock); + return result; +} + +/* + * Try to generate an address that prevents the server from calling us. + * + * Returns 1 and fills in @buf if successful; otherwise, zero. + */ +static int nfs_ca_gai(const struct sockaddr *sap, + struct sockaddr *buf, socklen_t *buflen) +{ + struct addrinfo *gai_results; + struct addrinfo gai_hint = { + .ai_family = sap->sa_family, + .ai_flags = AI_PASSIVE, /* ANYADDR */ + }; + + if (getaddrinfo(NULL, "", &gai_hint, &gai_results)) + return 0; + + *buflen = gai_results->ai_addrlen; + memcpy(buf, gai_results->ai_addr, *buflen); + + nfs_freeaddrinfo(gai_results); + + return 1; +} + +/** + * nfs_callback_address - acquire our local network address + * @sap: pointer to address of remote + * @sap_len: length of address + * @buf: pointer to buffer to be filled in with local network address + * @buflen: IN: length of buffer to fill in; OUT: length of filled-in address + * + * Discover a network address that an NFSv4 server can use to call us back. + * On multi-homed clients, this address depends on which NIC we use to + * route requests to the server. + * + * Returns 1 and fills in @buf if an unambiguous local address is + * available; returns 1 and fills in an appropriate ANYADDR address + * if a local address isn't available; otherwise, returns zero. + */ +int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen, + struct sockaddr *buf, socklen_t *buflen) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf; + + if (nfs_ca_sockname(sap, salen, buf, buflen) == 0) + if (nfs_ca_gai(sap, buf, buflen) == 0) + goto out_failed; + + /* + * The server can't use an interface ID that was generated + * here on the client, so always clear sin6_scope_id. + */ + if (sin6->sin6_family == AF_INET6) + sin6->sin6_scope_id = 0; + + return 1; + +out_failed: + *buflen = 0; + if (verbose) + nfs_error(_("%s: failed to construct callback address"), + progname); + return 0; +} + +/* + * "nfsprog" is supported only by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns TRUE if @program contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_nfs_program(struct mount_options *options, unsigned long *program) +{ + long tmp; + + switch (po_get_numeric(options, "nfsprog", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp > 0) { + *program = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'nfsprog=' option"), + progname); + return 0; + } + + /* + * NFS RPC program wasn't specified. The RPC program + * cannot be determined via an rpcbind query. + */ + *program = nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl); + return 1; +} + +/* + * Returns TRUE if @version contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +int +nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *version) +{ + char *version_key, *version_val = NULL, *cptr; + int i, found = -1; + + version->v_mode = V_DEFAULT; + + for (i = 0; nfs_version_opttbl[i]; i++) { + if (po_contains_prefix(options, nfs_version_opttbl[i], + &version_key, 0) == PO_FOUND) { + if (found >= 0) + goto ret_error_multiple; + if (po_contains_prefix(options, nfs_version_opttbl[i], + NULL, 1) == PO_FOUND) + goto ret_error_multiple; + found = i; + } + } + + if (found < 0 && strcmp(type, "nfs4") == 0) + version_val = type + 3; + else if (found < 0) + return 1; + else if (found <= 2 ) { + /* v3, v4 */ + version_val = version_key + 1; + version->v_mode = V_SPECIFIC; + } else if (found > 2 ) { + /* vers=, nfsvers= */ + version_val = po_get(options, version_key); + } + + if (!version_val) + goto ret_error; + + version->major = strtol(version_val, &cptr, 10); + if (cptr == version_val || (*cptr && *cptr != '.')) + goto ret_error; + if (version->major == 4 && *cptr != '.' && + (version_val = po_get(options, "minorversion")) != NULL) { + version->minor = strtol(version_val, &cptr, 10); + found = -1; + if (*cptr) + goto ret_error; + version->v_mode = V_SPECIFIC; + } else if (version->major < 4) + version->v_mode = V_SPECIFIC; + else if (*cptr == '.') { + version_val = ++cptr; + if (!(version->minor = strtol(version_val, &cptr, 10)) && cptr == version_val) + goto ret_error; + version->v_mode = V_SPECIFIC; + } else if (version->major > 3 && *cptr == '\0') { + version_val = po_get(options, "minorversion"); + if (version_val != NULL) { + version->minor = strtol(version_val, &cptr, 10); + version->v_mode = V_SPECIFIC; + } else + version->v_mode = V_GENERAL; + } + if (*cptr != '\0') + goto ret_error; + + return 1; + +ret_error_multiple: + nfs_error(_("%s: multiple version options not permitted"), + progname); + found = 10; /* avoid other errors */ +ret_error: + if (found < 0) { + nfs_error(_("%s: parsing error on 'minorversion=' option"), + progname); + } else if (found <= 2 ) { + nfs_error(_("%s: parsing error on 'v' option"), + progname); + } else if (found == 3 ) { + nfs_error(_("%s: parsing error on 'vers=' option"), + progname); + } else if (found == 4) { + nfs_error(_("%s: parsing error on 'nfsvers=' option"), + progname); + } + version->v_mode = V_PARSE_ERR; + errno = EINVAL; + return 0; +} + +/* + * Returns TRUE if @protocol contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. On + * error, errno is set. + */ +int +nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol) +{ + sa_family_t family; + char *option; + + switch (po_rightmost(options, nfs_transport_opttbl)) { + case 0: /* udp */ + *protocol = IPPROTO_UDP; + return 1; + case 1: /* tcp */ + *protocol = IPPROTO_TCP; + return 1; + case 2: /* rdma */ + *protocol = NFSPROTO_RDMA; + return 1; + case 3: /* proto */ + option = po_get(options, "proto"); + if (option != NULL) { + if (!nfs_get_proto(option, &family, protocol)) { + errno = EPROTONOSUPPORT; + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + return 0; + } + return 1; + } + } + + /* + * NFS transport protocol wasn't specified. The pmap + * protocol value will be filled in later by an rpcbind + * query in this case. + */ + *protocol = 0; + return 1; +} + +/* + * Returns TRUE if @port contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_nfs_port(struct mount_options *options, unsigned long *port) +{ + long tmp; + + switch (po_get_numeric(options, "port", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 0 && tmp <= 65535) { + *port = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'port=' option"), + progname); + return 0; + } + + /* + * NFS service port wasn't specified. The pmap port value + * will be filled in later by an rpcbind query in this case. + */ + *port = 0; + return 1; +} + +#ifdef IPV6_SUPPORTED +sa_family_t config_default_family = AF_UNSPEC; + +static int +nfs_verify_family(sa_family_t UNUSED(family)) +{ + return 1; +} +#else /* IPV6_SUPPORTED */ +sa_family_t config_default_family = AF_INET; + +static int +nfs_verify_family(sa_family_t family) +{ + if (family != AF_INET) + return 0; + + return 1; +} +#endif /* IPV6_SUPPORTED */ + +/* + * 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 + * or if the protocol family isn't supported. On error, errno is set. + */ +int nfs_nfs_proto_family(struct mount_options *options, + sa_family_t *family) +{ + unsigned long protocol; + char *option; + sa_family_t tmp_family = config_default_family; + + switch (po_rightmost(options, nfs_transport_opttbl)) { + case 0: /* udp */ + case 1: /* tcp */ + case 2: /* rdma */ + /* for compatibility; these are always AF_INET */ + *family = AF_INET; + return 1; + case 3: /* proto */ + option = po_get(options, "proto"); + if (option != NULL && + !nfs_get_proto(option, &tmp_family, &protocol)) { + + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + errno = EPROTONOSUPPORT; + return 0; + } + } + + if (!nfs_verify_family(tmp_family)) + goto out_err; + *family = tmp_family; + return 1; +out_err: + errno = EAFNOSUPPORT; + return 0; +} + +/* + * "mountprog" is supported only by the legacy mount command. The + * kernel mount client does not support this option. + * + * Returns TRUE if @program contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_program(struct mount_options *options, unsigned long *program) +{ + long tmp; + + switch (po_get_numeric(options, "mountprog", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp > 0) { + *program = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountprog=' option"), + progname); + return 0; + } + + /* + * MNT RPC program wasn't specified. The RPC program + * cannot be determined via an rpcbind query. + */ + *program = nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl); + return 1; +} + +/* + * Returns TRUE if @version contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_version(struct mount_options *options, unsigned long *version) +{ + long tmp; + + switch (po_get_numeric(options, "mountvers", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 1 && tmp <= 4) { + *version = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountvers=' option"), + progname); + return 0; + } + + /* + * MNT version wasn't specified. The pmap version value + * will be filled in later by an rpcbind query in this case. + */ + *version = 0; + return 1; +} + +/* + * Returns TRUE if @protocol contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. On + * error, errno is set. + */ +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 != NULL) { + if (!nfs_get_proto(option, &family, protocol)) { + errno = EPROTONOSUPPORT; + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + return 0; + } + return 1; + } + + /* + * MNT transport protocol wasn't specified. If the NFS + * transport protocol was specified, use that; otherwise + * set @protocol to zero. The pmap protocol value will + * be filled in later by an rpcbind query in this case. + */ + if (!nfs_nfs_protocol(options, protocol)) + return 0; + if (*protocol == NFSPROTO_RDMA) + *protocol = IPPROTO_TCP; + return 1; +} + +/* + * Returns TRUE if @port contains a valid value for this option, + * or FALSE if the option was specified with an invalid value. + */ +static int +nfs_mount_port(struct mount_options *options, unsigned long *port) +{ + long tmp; + + switch (po_get_numeric(options, "mountport", &tmp)) { + case PO_NOT_FOUND: + break; + case PO_FOUND: + if (tmp >= 0 && tmp <= 65535) { + *port = tmp; + return 1; + } + /* FALLTHRU */ + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'mountport=' option"), + progname); + return 0; + } + + /* + * MNT service port wasn't specified. The pmap port value + * will be filled in later by an rpcbind query in this case. + */ + *port = 0; + 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 + * or if the protocol family isn't supported. On error, errno is set. + */ +int nfs_mount_proto_family(struct mount_options *options, + sa_family_t *family) +{ + unsigned long protocol; + char *option; + sa_family_t tmp_family = config_default_family; + + option = po_get(options, "mountproto"); + if (option != NULL) { + if (!nfs_get_proto(option, &tmp_family, &protocol)) { + nfs_error(_("%s: Failed to find '%s' protocol"), + progname, option); + errno = EPROTONOSUPPORT; + goto out_err; + } + if (!nfs_verify_family(tmp_family)) + goto out_err; + *family = tmp_family; + return 1; + } + + /* + * 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); +out_err: + errno = EAFNOSUPPORT; + return 0; +} + +/** + * nfs_options2pmap - set up pmap structs based on mount options + * @options: pointer to mount options + * @nfs_pmap: OUT: pointer to pmap arguments for NFS server + * @mnt_pmap: OUT: pointer to pmap arguments for mountd server + * + * Returns TRUE if the pmap options specified in @options have valid + * values; otherwise FALSE is returned. + */ +int nfs_options2pmap(struct mount_options *options, + struct pmap *nfs_pmap, struct pmap *mnt_pmap) +{ + struct nfs_version version; + memset(&version, 0, sizeof(version)); + + if (!nfs_nfs_program(options, &nfs_pmap->pm_prog)) + return 0; + if (!nfs_nfs_version("nfs", options, &version)) + return 0; + if (version.v_mode == V_DEFAULT) + nfs_pmap->pm_vers = 0; + else + nfs_pmap->pm_vers = version.major; + if (!nfs_nfs_protocol(options, &nfs_pmap->pm_prot)) + return 0; + if (!nfs_nfs_port(options, &nfs_pmap->pm_port)) + return 0; + + if (!nfs_mount_program(options, &mnt_pmap->pm_prog)) + return 0; + if (!nfs_mount_version(options, &mnt_pmap->pm_vers)) + return 0; + if (!nfs_mount_protocol(options, &mnt_pmap->pm_prot)) + return 0; + if (!nfs_mount_port(options, &mnt_pmap->pm_port)) + return 0; + + return 1; +} + +/* + * Discover mount server's hostname/address by examining mount options + * + * Returns a pointer to a string that the caller must free, on + * success; otherwise NULL is returned. + */ +static char *nfs_umount_hostname(struct mount_options *options, + char *hostname) +{ + char *option; + + option = po_get(options, "mountaddr"); + if (option) + goto out; + option = po_get(options, "mounthost"); + if (option) + goto out; + option = po_get(options, "addr"); + if (option) + goto out; + + return hostname; + +out: + free(hostname); + return strdup(option); +} + + +/* + * Returns EX_SUCCESS if mount options and device name have been + * parsed successfully; otherwise EX_FAIL. + */ +int nfs_umount_do_umnt(struct mount_options *options, + char **hostname, char **dirname) +{ + union nfs_sockaddr 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)) + return EX_FAIL; + + /* Skip UMNT call for vers=4 mounts */ + if (nfs_pmap.pm_vers == 4) + return EX_SUCCESS; + + *hostname = nfs_umount_hostname(options, *hostname); + if (!*hostname) { + nfs_error(_("%s: out of memory"), progname); + return EX_FAIL; + } + + 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) + /* nfs_advise_umount reports any errors */ + return EX_FAIL; + + return EX_SUCCESS; +} + +int nfs_is_inaddr_any(struct sockaddr *nfs_saddr) +{ + switch (nfs_saddr->sa_family) { + case AF_INET: { + if (((struct sockaddr_in *)nfs_saddr)->sin_addr.s_addr == + INADDR_ANY) + return 1; + break; + } + case AF_INET6: + if (!memcmp(&((struct sockaddr_in6 *)nfs_saddr)->sin6_addr, + &in6addr_any, sizeof(in6addr_any))) + return 1; + break; + } + return 0; +} + +int nfs_addr_matches_localips(struct sockaddr *nfs_saddr) +{ + struct ifaddrs *myaddrs, *ifa; + int found = 0; + + /* acquire exiting network interfaces */ + if (getifaddrs(&myaddrs) != 0) + return 0; + + /* interate over the available interfaces and check if we + * we find a match to the supplied clientaddr value + */ + for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (!(ifa->ifa_flags & IFF_UP)) + continue; + if (!memcmp(ifa->ifa_addr, nfs_saddr, + sizeof(struct sockaddr))) { + found = 1; + break; + } + } + freeifaddrs(myaddrs); + return found; +} |