]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
mount command: use gethostbyname(3) when building on old systems
[nfs-utils.git] / utils / mount / network.c
index 0c68993c0e06a7e287483e6abcc53ee65b0b2ea9..a82c338a2cb06eb801e35110b211c02b184fef7b 100644 (file)
 #include "nfsrpc.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 */
-
 #define PMAP_TIMEOUT   (10)
 #define CONNECT_TIMEOUT        (20)
 #define MOUNT_TIMEOUT  (30)
@@ -160,6 +142,22 @@ static const unsigned long probe_mnt3_first[] = {
        0,
 };
 
+static void nfs_set_port(struct sockaddr *sap, const unsigned short port)
+{
+       switch (sap->sa_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)sap)->sin_port = htons(port);
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)sap)->sin6_port = htons(port);
+               break;
+       default:
+               nfs_error(_("%s: unrecognized address family in %s"),
+                       progname, __func__);
+       }
+}
+
+#ifdef HAVE_DECL_AI_ADDRCONFIG
 /**
  * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
  * @hostname: pointer to C string containing DNS hostname to resolve
@@ -213,6 +211,61 @@ int nfs_name_to_address(const char *hostname,
        freeaddrinfo(gai_results);
        return ret;
 }
+#else  /* HAVE_DECL_AI_ADDRCONFIG */
+/**
+ * nfs_name_to_address - resolve hostname to an IPv4 socket address
+ * @hostname: pointer to C string containing DNS hostname to resolve
+ * @af_hint: hint to restrict resolution to one address family
+ * @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.
+ *
+ * Some older getaddrinfo(3) implementations don't support
+ * AI_ADDRCONFIG or AI_V4MAPPED properly.  For those cases, a DNS
+ * resolver based on the traditional gethostbyname(3) is provided.
+ */
+int nfs_name_to_address(const char *hostname,
+                       const sa_family_t af_hint,
+                       struct sockaddr *sap, socklen_t *salen)
+{
+       struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+       socklen_t len = *salen;
+       struct hostent *hp;
+
+       *salen = 0;
+
+       if (af_hint != AF_INET) {
+               nfs_error(_("%s: address family not supported by DNS resolver\n"),
+                               progname, hostname);
+               return 0;
+       }
+
+       sin->sin_family = AF_INET;
+       if (inet_aton(hostname, &sin->sin_addr)) {
+               *salen = sizeof(*sin);
+               return 1;
+       }
+
+       hp = gethostbyname(hostname);
+       if (hp == NULL) {
+               nfs_error(_("%s: DNS resolution failed for %s: %s"),
+                               progname, hostname, hstrerror(h_errno));
+               return 0;
+       }
+
+       if (hp->h_length > len) {
+               nfs_error(_("%s: DNS resolution results too long for buffer\n"),
+                               progname);
+               return 0;
+       }
+
+       memcpy(&sin->sin_addr, hp->h_addr, hp->h_length);
+       *salen = hp->h_length;
+       return 1;
+}
+#endif /* HAVE_DECL_AI_ADDRCONFIG */
 
 /**
  * nfs_gethostbyname - resolve a hostname to an IPv4 address
@@ -474,27 +527,38 @@ static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen,
  * 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)
 {
-       const struct sockaddr *saddr = (struct sockaddr *)&server->saddr;
-       const socklen_t salen = sizeof(server->saddr);
-       struct pmap *pmap = &server->pmap;
+       struct sockaddr_storage address;
+       struct sockaddr *saddr = (struct sockaddr *)&address;
        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 = nfs_getport(saddr, salen, prog, *p_vers, *p_prot);
                if (p_port) {
                        if (!port || port == p_port) {
-                               server->saddr.sin_port = htons(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,
@@ -535,56 +599,114 @@ out_ok:
        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);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_nfs3_first, probe_tcp_first);
        else
-               return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
+               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));
@@ -593,9 +715,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));
                }
@@ -604,18 +726,35 @@ 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);
+/**
+ * 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)
+{
+       return nfs_probe_bothports((struct sockaddr *)&mnt_server->saddr,
+                                       sizeof(mnt_server->saddr),
+                                       &mnt_server->pmap,
+                                       (struct sockaddr *)&nfs_server->saddr,
+                                       sizeof(nfs_server->saddr),
+                                       &nfs_server->pmap);
 }
 
 static int nfs_probe_statd(void)
@@ -682,11 +821,14 @@ int start_statd(void)
  */
 int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
 {
+       struct sockaddr *sap = (struct 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)