blob: 35261171a6150510a68534ae79f8ffff15aeb3a3 [file] [log] [blame]
/*
* 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",
"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()
{
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_bind:
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
rpc_createerr.cf_error.re_errno = errno;
if (verbose) {
nfs_error(_("%s: Unable to bind to %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 ) {
/* v2, 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;
}