X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fmount%2Fnetwork.c;h=5b515c30d76d26a79839331d5db7dd2b7840f6e3;hp=806344c399dd92d4f3b5ebe8d8f67ea9099c6061;hb=17962b82afb68ca8e6b0d3f432d36c6c7c4980ea;hpb=33bbeabb40d11a59266e0702adaa6a2e0acb6382 diff --git a/utils/mount/network.c b/utils/mount/network.c index 806344c..5b515c3 100644 --- a/utils/mount/network.c +++ b/utils/mount/network.c @@ -37,48 +37,68 @@ #include #include #include +#include #include #include #include #include +#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" - -/* - * Earlier versions of glibc's /usr/include/netdb.h exclude these - * definitions because it was thought they were not part of a stable - * POSIX standard. However, they are defined by RFC 2553 and 3493 - * and in POSIX 1003.1-2001, so these definitions were added in later - * versions of netdb.h. - */ -#ifndef AI_V4MAPPED -#define AI_V4MAPPED 0x0008 /* IPv4-mapped addresses are acceptable. */ -#endif /* AI_V4MAPPED */ -#ifndef AI_ALL -#define AI_ALL 0x0010 /* Return both IPv4 and IPv6 addresses. */ -#endif /* AI_ALL */ -#ifndef AI_ADDRCONFIG -#define AI_ADDRCONFIG 0x0020 /* Use configuration of this host to choose \ - returned address type. */ -#endif /* AI_ADDRCONFIG */ +#include "conffile.h" +#include "nfslib.h" #define PMAP_TIMEOUT (10) #define CONNECT_TIMEOUT (20) #define MOUNT_TIMEOUT (30) -#if SIZEOF_SOCKLEN_T - 0 == 0 -#define socklen_t unsigned int -#endif +#define SAFE_SOCKADDR(x) (struct sockaddr *)(char *)(x) extern int nfs_mount_data_version; extern char *progname; extern int verbose; +static const char *nfs_ns_pgmtbl[] = { + "status", + NULL, +}; + +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, @@ -154,37 +174,63 @@ static const unsigned long probe_mnt3_first[] = { 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_name_to_address - resolve hostname to an IPv4 or IPv6 socket address + * 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_name_to_address(const char *hostname, - const sa_family_t af_hint, - struct sockaddr *sap, socklen_t *salen) +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 = af_hint, +#ifdef HAVE_DECL_AI_ADDRCONFIG .ai_flags = AI_ADDRCONFIG, +#endif /* HAVE_DECL_AI_ADDRCONFIG */ + .ai_family = family, }; socklen_t len = *salen; int error, ret = 0; - if (af_hint == AF_INET6) - gai_hint.ai_flags |= AI_V4MAPPED|AI_ALL; - *salen = 0; error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results); - if (error) { + 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, (error == EAI_SYSTEM ? - strerror(errno) : gai_strerror(error))); + progname, hostname, gai_strerror(error)); return ret; } @@ -211,7 +257,7 @@ int nfs_name_to_address(const char *hostname, /** * nfs_gethostbyname - resolve a hostname to an IPv4 address * @hostname: pointer to a C string containing a DNS hostname - * @saddr: returns an IPv4 address + * @sin: returns an IPv4 address * * Returns 1 if successful, otherwise zero. */ @@ -219,29 +265,27 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin) { socklen_t len = sizeof(*sin); - return nfs_name_to_address(hostname, AF_INET, - (struct sockaddr *)sin, &len); + 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 - * @addrlen: length of presentation address * @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_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. * * 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, const size_t addrlen, - struct sockaddr *sap, socklen_t *salen) +int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap, + socklen_t *salen) { struct addrinfo *gai_results; struct addrinfo gai_hint = { @@ -386,12 +430,12 @@ static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot, if (bindresvport(so, &laddr) < 0) goto err_bindresvport; } else { - cc = bind(so, (struct sockaddr *)&laddr, namelen); + cc = bind(so, SAFE_SOCKADDR(&laddr), namelen); if (cc < 0) goto err_bind; } if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) { - cc = connect_to(so, (struct sockaddr *)saddr, namelen, + cc = connect_to(so, SAFE_SOCKADDR(saddr), namelen, timeout); if (cc < 0) goto err_connect; @@ -443,136 +487,111 @@ err_connect: return RPC_ANYSOCK; } -/* - * getport() is very similar to pmap_getport() with the exception that - * this version tries to use an ephemeral port, since reserved ports are - * not needed for GETPORT queries. This conserves the very limited - * reserved port space, which helps reduce failed socket binds - * during mount storms. - * - * A side effect of calling this function is that rpccreateerr is set. - */ -static unsigned short getport(struct sockaddr_in *saddr, - unsigned long program, - unsigned long version, - unsigned int proto) +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) { - struct sockaddr_in bind_saddr; - unsigned short port = 0; - int socket; - CLIENT *clnt = NULL; - enum clnt_stat stat; - - bind_saddr = *saddr; - bind_saddr.sin_port = htons(PMAPPORT); - - socket = get_socket(&bind_saddr, proto, PMAP_TIMEOUT, FALSE, TRUE); - if (socket == RPC_ANYSOCK) { - if (proto == IPPROTO_TCP && - rpc_createerr.cf_error.re_errno == ETIMEDOUT) - rpc_createerr.cf_stat = RPC_TIMEDOUT; - return 0; - } + char buf[NI_MAXHOST]; - switch (proto) { - case IPPROTO_UDP: - clnt = clntudp_bufcreate(&bind_saddr, - PMAPPROG, PMAPVERS, - RETRY_TIMEOUT, &socket, - RPCSMALLMSGSIZE, - RPCSMALLMSGSIZE); - break; - case IPPROTO_TCP: - clnt = clnttcp_create(&bind_saddr, - PMAPPROG, PMAPVERS, - &socket, - RPCSMALLMSGSIZE, RPCSMALLMSGSIZE); - break; - } - if (clnt != NULL) { - struct pmap parms = { - .pm_prog = program, - .pm_vers = version, - .pm_prot = proto, - }; - - stat = clnt_call(clnt, PMAPPROC_GETPORT, - (xdrproc_t)xdr_pmap, (caddr_t)&parms, - (xdrproc_t)xdr_u_short, (caddr_t)&port, - TIMEOUT); - if (stat) { - clnt_geterr(clnt, &rpc_createerr.cf_error); - rpc_createerr.cf_stat = stat; - } - clnt_destroy(clnt); - if (stat != RPC_SUCCESS) - port = 0; - else if (port == 0) - rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED; + if (!verbose) + return; + + if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) { + buf[0] = '\0'; + strcat(buf, "unknown host"); } - close(socket); - return port; + 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 probe_port(clnt_addr_t *server, const unsigned long *versions, - const unsigned int *protos) +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_in *saddr = &server->saddr; - struct pmap *pmap = &server->pmap; + 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; - rpc_createerr.cf_stat = 0; + for (;;) { - p_port = getport(saddr, prog, *p_vers, *p_prot); + 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) { - saddr->sin_port = htons(p_port); - if (verbose) { - printf(_("%s: trying %s prog %ld vers " - "%ld prot %s port %d\n"), - progname, - inet_ntoa(saddr->sin_addr), - prog, *p_vers, - *p_prot == IPPROTO_UDP ? - _("UDP") : _("TCP"), - p_port); - } - if (clnt_ping(saddr, prog, *p_vers, *p_prot, NULL)) + 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) - goto out_bad; + break; if (!prot) { - if (*++p_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) - goto out_bad; + break; if (vers || !*++p_vers) break; } -out_bad: + nfs_pp_debug2("failed"); return 0; out_ok: @@ -582,60 +601,121 @@ out_ok: pmap->pm_prot = *p_prot; if (!port) pmap->pm_port = p_port; - rpc_createerr.cf_stat = 0; + nfs_clear_rpc_createerr(); return 1; } - -static int probe_nfsport(clnt_addr_t *nfs_server) +/* + * 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) { - struct pmap *pmap = &nfs_server->pmap; - if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) return 1; - if (nfs_mount_data_version >= 4) - return probe_port(nfs_server, probe_nfs3_first, probe_tcp_first); - else - return probe_port(nfs_server, probe_nfs2_only, probe_udp_only); + if (nfs_mount_data_version >= 4) { + const unsigned int *probe_proto; + + probe_proto = nfs_default_proto(); + + return nfs_probe_port(sap, salen, pmap, + probe_nfs3_first, probe_proto); + } else + return nfs_probe_port(sap, salen, pmap, + probe_nfs2_only, probe_udp_only); } -static int probe_mntport(clnt_addr_t *mnt_server) +/* + * 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) { - struct pmap *pmap = &mnt_server->pmap; - if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port) return 1; if (nfs_mount_data_version >= 4) - return probe_port(mnt_server, probe_mnt3_first, probe_udp_first); + return nfs_probe_port(sap, salen, pmap, + probe_mnt3_first, probe_udp_first); else - return probe_port(mnt_server, probe_mnt1_first, probe_udp_only); + return nfs_probe_port(sap, salen, pmap, + probe_mnt1_first, probe_udp_only); } -/** - * 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 +/* + * 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 if successful, otherwise zero if some error occurred. - * Note that the arguments are both input and output arguments. + * 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)) + 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 * - * A side effect of calling this function is that rpccreateerr is set. + * 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 probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server) +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) { - struct pmap *nfs_pmap = &nfs_server->pmap; - struct pmap *mnt_pmap = &mnt_server->pmap; struct pmap save_nfs, save_mnt; - int res; 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) - goto version_fixed; + 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)); @@ -644,9 +724,9 @@ int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server) for (; *probe_vers; probe_vers++) { nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers); - if ((res = probe_nfsport(nfs_server) != 0)) { + if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap) != 0) { mnt_pmap->pm_vers = *probe_vers; - if ((res = probe_mntport(mnt_server)) != 0) + if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0) return 1; memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap)); } @@ -655,38 +735,48 @@ int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server) case RPC_PROGNOTREGISTERED: break; default: - goto out_bad; + return 0; } memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap)); } -out_bad: return 0; - -version_fixed: - if (!probe_nfsport(nfs_server)) - goto out_bad; - return probe_mntport(mnt_server); } -static int probe_statd(void) +/** + * 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_in addr; - unsigned short port; + struct sockaddr *mnt_addr = SAFE_SOCKADDR(&mnt_server->saddr); + struct sockaddr *nfs_addr = SAFE_SOCKADDR(&nfs_server->saddr); - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - port = getport(&addr, 100024, 1, IPPROTO_UDP); - - if (port == 0) - return 0; - addr.sin_port = htons(port); + return nfs_probe_bothports(mnt_addr, sizeof(mnt_server->saddr), + &mnt_server->pmap, + nfs_addr, sizeof(nfs_server->saddr), + &nfs_server->pmap); +} - if (clnt_ping(&addr, 100024, 1, IPPROTO_UDP, NULL) <= 0) - return 0; +static int nfs_probe_statd(void) +{ + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + rpcprog_t program = nfs_getrpcbyname(NSMPROG, nfs_ns_pgmtbl); - return 1; + return nfs_getport_ping(SAFE_SOCKADDR(&addr), sizeof(addr), + program, (rpcvers_t)1, IPPROTO_UDP); } /** @@ -700,7 +790,7 @@ int start_statd(void) struct stat stb; #endif - if (probe_statd()) + if (nfs_probe_statd()) return 1; #ifdef START_STATD @@ -712,13 +802,14 @@ int start_statd(void) execl(START_STATD, START_STATD, NULL); exit(1); case -1: /* error */ - perror("Fork failed"); + nfs_error(_("%s: fork failed: %s"), + progname, strerror(errno)); break; default: /* parent */ waitpid(pid, NULL,0); break; } - if (probe_statd()) + if (nfs_probe_statd()) return 1; } } @@ -727,6 +818,79 @@ int start_statd(void) 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 @@ -740,11 +904,14 @@ int start_statd(void) */ 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 (!probe_mntport(mnt_server)) + if (!nfs_probe_mntport(sap, salen, pmap)) return 0; clnt = mnt_openclnt(mnt_server, &msock); if (!clnt) @@ -802,8 +969,10 @@ CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock) } if (clnt) { /* try to mount hostname:dirname */ - clnt->cl_auth = authunix_create_default(); - return clnt; + clnt->cl_auth = nfs_authsys_create(); + if (clnt->cl_auth) + return clnt; + CLNT_DESTROY(clnt); } return NULL; } @@ -829,9 +998,9 @@ void mnt_closeclnt(CLIENT *clnt, int msock) * @prot: target RPC protocol * @caddr: filled in with our network address * - * Sigh... getport() doesn't actually check the version number. + * 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 and RPC client, and fire off a NULL + * 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. @@ -845,11 +1014,11 @@ int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog, struct sockaddr_in *caddr) { CLIENT *clnt = NULL; - int sock, stat; + int sock, status; static char clnt_res; struct sockaddr dissolve; - rpc_createerr.cf_stat = stat = 0; + 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) { @@ -892,18 +1061,18 @@ int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog, return 0; } memset(&clnt_res, 0, sizeof(clnt_res)); - stat = clnt_call(clnt, NULLPROC, + status = clnt_call(clnt, NULLPROC, (xdrproc_t)xdr_void, (caddr_t)NULL, (xdrproc_t)xdr_void, (caddr_t)&clnt_res, TIMEOUT); - if (stat) { + if (status) { clnt_geterr(clnt, &rpc_createerr.cf_error); - rpc_createerr.cf_stat = stat; + rpc_createerr.cf_stat = status; } clnt_destroy(clnt); close(sock); - if (stat == RPC_SUCCESS) + if (status == RPC_SUCCESS) return 1; else return 0; @@ -937,13 +1106,13 @@ static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen, switch (sap->sa_family) { case AF_INET: - if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + if (bind(sock, SAFE_SOCKADDR(&sin), sizeof(sin)) < 0) { close(sock); return 0; } break; case AF_INET6: - if (bind(sock, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) { + if (bind(sock, SAFE_SOCKADDR(&sin6), sizeof(sin6)) < 0) { close(sock); return 0; } @@ -966,7 +1135,7 @@ static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen, * * Returns 1 and fills in @buf if successful; otherwise, zero. */ -static int nfs_ca_gai(const struct sockaddr *sap, const socklen_t salen, +static int nfs_ca_gai(const struct sockaddr *sap, struct sockaddr *buf, socklen_t *buflen) { struct addrinfo *gai_results; @@ -1007,7 +1176,7 @@ int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen, struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf; if (nfs_ca_sockname(sap, salen, buf, buflen) == 0) - if (nfs_ca_gai(sap, salen, buf, buflen) == 0) + if (nfs_ca_gai(sap, buf, buflen) == 0) goto out_failed; /* @@ -1022,7 +1191,441 @@ int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen, out_failed: *buflen = 0; if (verbose) - nfs_error(_("%s: failed to construct callback address")); + 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; + } + 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(struct mount_options *options, unsigned long *version) +{ + long tmp; + + switch (po_rightmost(options, nfs_version_opttbl)) { + case 0: /* v2 */ + *version = 2; + return 1; + case 1: /* v3 */ + *version = 3; + return 1; + case 2: /* v4 */ + *version = 4; + return 1; + case 3: /* vers */ + switch (po_get_numeric(options, "vers", &tmp)) { + case PO_FOUND: + if (tmp >= 2 && tmp <= 4) { + *version = tmp; + return 1; + } + return 0; + case PO_NOT_FOUND: + nfs_error(_("%s: parsing error on 'vers=' option\n"), + progname); + return 0; + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'vers=' option"), + progname); + return 0; + } + case 4: /* nfsvers */ + switch (po_get_numeric(options, "nfsvers", &tmp)) { + case PO_FOUND: + if (tmp >= 2 && tmp <= 4) { + *version = tmp; + return 1; + } + return 0; + case PO_NOT_FOUND: + nfs_error(_("%s: parsing error on 'nfsvers=' option\n"), + progname); + return 0; + case PO_BAD_VALUE: + nfs_error(_("%s: invalid value for 'nfsvers=' option"), + progname); + return 0; + } + } + + /* + * NFS 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. + */ +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 >= 1 && tmp <= 65535) { + *port = tmp; + return 1; + } + 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; + } + 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; + } + 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 >= 1 && tmp <= 65535) { + *port = tmp; + return 1; + } + 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) +{ + if (!nfs_nfs_program(options, &nfs_pmap->pm_prog)) + return 0; + if (!nfs_nfs_version(options, &nfs_pmap->pm_vers)) + return 0; + 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; }