]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - support/nfs/rpc_socket.c
nfs-utils: add and use nfs_authsys_create
[nfs-utils.git] / support / nfs / rpc_socket.c
index 2b11e350669ab53729bc41d92f7378eadb26b268..aa6a2055772a2fe2f178be8340fe2f6b1683b2c8 100644 (file)
@@ -26,6 +26,8 @@
 
 #include <sys/types.h>
 #include <sys/time.h>
+
+#include <stdbool.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -38,6 +40,7 @@
 #include <rpc/rpc.h>
 #include <rpc/pmap_prot.h>
 
+#include "sockaddr.h"
 #include "nfsrpc.h"
 
 #ifdef HAVE_LIBTIRPC
@@ -51,6 +54,7 @@
 #define NFSRPC_TIMEOUT_UDP     (3)
 #define NFSRPC_TIMEOUT_TCP     (10)
 
+
 /*
  * Set up an RPC client for communicating via a AF_LOCAL socket.
  *
@@ -121,10 +125,10 @@ static int nfs_bind(const int sock, const sa_family_t family)
 
        switch (family) {
        case AF_INET:
-               return bind(sock, (struct sockaddr *)&sin,
+               return bind(sock, (struct sockaddr *)(char *)&sin,
                                        (socklen_t)sizeof(sin));
        case AF_INET6:
-               return bind(sock, (struct sockaddr *)&sin6,
+               return bind(sock, (struct sockaddr *)(char *)&sin6,
                                        (socklen_t)sizeof(sin6));
        }
 
@@ -132,6 +136,56 @@ static int nfs_bind(const int sock, const sa_family_t family)
        return -1;
 }
 
+#ifdef HAVE_LIBTIRPC
+
+/*
+ * Bind a socket using an unused privileged source port.
+ *
+ * Returns zero on success, or returns -1 on error.  errno is
+ * set to reflect the nature of the error.
+ */
+static int nfs_bindresvport(const int sock, const sa_family_t family)
+{
+       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,
+       };
+
+       switch (family) {
+       case AF_INET:
+               return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin);
+       case AF_INET6:
+               return bindresvport_sa(sock, (struct sockaddr *)(char *)&sin6);
+       }
+
+       errno = EAFNOSUPPORT;
+       return -1;
+}
+
+#else  /* !HAVE_LIBTIRPC */
+
+/*
+ * Bind a socket using an unused privileged source port.
+ *
+ * Returns zero on success, or returns -1 on error.  errno is
+ * set to reflect the nature of the error.
+ */
+static int nfs_bindresvport(const int sock, const sa_family_t family)
+{
+       if (family != AF_INET) {
+               errno = EAFNOSUPPORT;
+               return -1;
+       }
+
+       return bindresvport(sock, NULL);
+}
+
+#endif /* !HAVE_LIBTIRPC */
+
 /*
  * Perform a non-blocking connect on the socket fd.
  *
@@ -218,7 +272,8 @@ static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
                                 const socklen_t salen,
                                 const rpcprog_t program,
                                 const rpcvers_t version,
-                                struct timeval *timeout)
+                                struct timeval *timeout,
+                                const int resvport)
 {
        CLIENT *client;
        int ret, sock;
@@ -245,7 +300,10 @@ static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
                return NULL;
        }
 
-       ret = nfs_bind(sock, sap->sa_family);
+       if (resvport)
+               ret = nfs_bindresvport(sock, sap->sa_family);
+       else
+               ret = nfs_bind(sock, sap->sa_family);
        if (ret < 0) {
                rpc_createerr.cf_stat = RPC_SYSTEMERROR;
                rpc_createerr.cf_error.re_errno = errno;
@@ -272,7 +330,9 @@ static CLIENT *nfs_get_udpclient(const struct sockaddr *sap,
                                        version, *timeout, &sock);
 #endif /* !HAVE_LIBTIRPC */
        if (client != NULL) {
-               CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)timeout);
+               struct timeval retry_timeout = { 1, 0 };
+               CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT,
+                                               (char *)&retry_timeout);
                CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL);
        } else
                (void)close(sock);
@@ -294,7 +354,8 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
                                 const socklen_t salen,
                                 const rpcprog_t program,
                                 const rpcvers_t version,
-                                struct timeval *timeout)
+                                struct timeval *timeout,
+                                const int resvport)
 {
        CLIENT *client;
        int ret, sock;
@@ -321,7 +382,10 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
                return NULL;
        }
 
-       ret = nfs_bind(sock, sap->sa_family);
+       if (resvport)
+               ret = nfs_bindresvport(sock, sap->sa_family);
+       else
+               ret = nfs_bind(sock, sap->sa_family);
        if (ret < 0) {
                rpc_createerr.cf_stat = RPC_SYSTEMERROR;
                rpc_createerr.cf_error.re_errno = errno;
@@ -365,7 +429,8 @@ static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap,
  * @timeout: pointer to request timeout (must not be NULL)
  *
  * Set up an RPC client for communicating with an RPC program @program
- * and @version on the server @sap over @transport.
+ * and @version on the server @sap over @transport.  An unprivileged
+ * source port is used.
  *
  * Returns a pointer to a prepared RPC client if successful, and
  * @timeout is initialized; caller must destroy a non-NULL returned RPC
@@ -379,36 +444,89 @@ CLIENT *nfs_get_rpcclient(const struct sockaddr *sap,
                          const rpcvers_t version,
                          struct timeval *timeout)
 {
-       struct sockaddr_in *sin = (struct sockaddr_in *)sap;
-       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+       nfs_clear_rpc_createerr();
 
        switch (sap->sa_family) {
        case AF_LOCAL:
                return nfs_get_localclient(sap, salen, program,
                                                version, timeout);
        case AF_INET:
-               if (sin->sin_port == 0) {
+       case AF_INET6:
+               if (nfs_get_port(sap) == 0) {
                        rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
                        return NULL;
                }
                break;
+       default:
+               rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
+               return NULL;
+       }
+
+       switch (transport) {
+       case IPPROTO_TCP:
+               return nfs_get_tcpclient(sap, salen, program, version,
+                                               timeout, 0);
+       case 0:
+       case IPPROTO_UDP:
+               return nfs_get_udpclient(sap, salen, program, version,
+                                               timeout, 0);
+       }
+
+       rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
+       return NULL;
+}
+
+/**
+ * nfs_get_priv_rpcclient - acquire an RPC client
+ * @sap: pointer to socket address of RPC server
+ * @salen: length of socket address
+ * @transport: IPPROTO_ value of transport protocol to use
+ * @program: RPC program number
+ * @version: RPC version number
+ * @timeout: pointer to request timeout (must not be NULL)
+ *
+ * Set up an RPC client for communicating with an RPC program @program
+ * and @version on the server @sap over @transport.  A privileged
+ * source port is used.
+ *
+ * Returns a pointer to a prepared RPC client if successful, and
+ * @timeout is initialized; caller must destroy a non-NULL returned RPC
+ * client.  Otherwise returns NULL, and rpc_createerr.cf_stat is set to
+ * reflect the error.
+ */
+CLIENT *nfs_get_priv_rpcclient(const struct sockaddr *sap,
+                              const socklen_t salen,
+                              const unsigned short transport,
+                              const rpcprog_t program,
+                              const rpcvers_t version,
+                              struct timeval *timeout)
+{
+       nfs_clear_rpc_createerr();
+
+       switch (sap->sa_family) {
+       case AF_LOCAL:
+               return nfs_get_localclient(sap, salen, program,
+                                               version, timeout);
+       case AF_INET:
        case AF_INET6:
-               if (sin6->sin6_port == 0) {
+               if (nfs_get_port(sap) == 0) {
                        rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
                        return NULL;
                }
                break;
        default:
-               rpc_createerr.cf_stat = RPC_UNKNOWNHOST;
+               rpc_createerr.cf_stat = RPC_UNKNOWNADDR;
                return NULL;
        }
 
        switch (transport) {
        case IPPROTO_TCP:
-               return nfs_get_tcpclient(sap, salen, program, version, timeout);
+               return nfs_get_tcpclient(sap, salen, program, version,
+                                               timeout, 1);
        case 0:
        case IPPROTO_UDP:
-               return nfs_get_udpclient(sap, salen, program, version, timeout);
+               return nfs_get_udpclient(sap, salen, program, version,
+                                               timeout, 1);
        }
 
        rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
@@ -439,3 +557,24 @@ rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[])
 
        return program;
 }
+
+/*
+ * AUTH_SYS doesn't allow more than 16 gids in the supplemental group list.
+ * If there are more than that, trying to determine which ones to include
+ * in the list is problematic. This function creates an auth handle that
+ * only has the primary gid in the supplemental gids list. It's intended to
+ * be used for protocols where credentials really don't matter much (the MNT
+ * protocol, for instance).
+ */
+AUTH *
+nfs_authsys_create(void)
+{
+       char machname[MAXHOSTNAMELEN + 1];
+       uid_t   uid = geteuid();
+       gid_t   gid = getegid();
+
+       if (gethostname(machname, sizeof(machname)) == -1)
+               return NULL;
+
+       return authsys_create(machname, uid, gid, 1, &gid);
+}