diff options
Diffstat (limited to 'support/nfs/rpc_socket.c')
-rw-r--r-- | support/nfs/rpc_socket.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/support/nfs/rpc_socket.c b/support/nfs/rpc_socket.c new file mode 100644 index 0000000..5fabf5a --- /dev/null +++ b/support/nfs/rpc_socket.c @@ -0,0 +1,560 @@ +/* + * Generic RPC client socket-level APIs for nfs-utils + * + * Copyright (C) 2008 Oracle Corporation. All rights reserved. + * + * 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 <sys/types.h> +#include <sys/time.h> + +#include <stdbool.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> + +#include <rpc/rpc.h> +#include <rpc/pmap_prot.h> + +#include "sockaddr.h" +#include "nfsrpc.h" + +#ifdef HAVE_LIBTIRPC +#include <netconfig.h> +#include <rpc/rpcb_prot.h> +#endif /* HAVE_LIBTIRPC */ + +/* + * If "-1" is specified in the tv_sec field, use these defaults instead. + */ +#define NFSRPC_TIMEOUT_UDP (3) +#define NFSRPC_TIMEOUT_TCP (10) + + +/* + * Set up an RPC client for communicating via a AF_LOCAL socket. + * + * @timeout is initialized upon return + * + * Returns a pointer to a prepared RPC client if successful; caller + * must destroy a non-NULL returned RPC client. Otherwise NULL, and + * rpc_createerr.cf_stat is set to reflect the error. + */ +static CLIENT *nfs_get_localclient(const struct sockaddr *sap, + const socklen_t salen, + const rpcprog_t program, + const rpcvers_t version, + struct timeval *timeout) +{ +#ifdef HAVE_LIBTIRPC + struct sockaddr_storage address; + const struct netbuf nbuf = { + .maxlen = sizeof(struct sockaddr_un), + .len = (size_t)salen, + .buf = &address, + }; +#else + (void) salen; +#endif /* HAVE_LIBTIRPC */ + CLIENT *client; + int sock; + + sock = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sock == -1) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + return NULL; + } + + if (timeout->tv_sec == -1) + timeout->tv_sec = NFSRPC_TIMEOUT_TCP; + +#ifdef HAVE_LIBTIRPC + memcpy(nbuf.buf, sap, (size_t)salen); + client = clnt_vc_create(sock, &nbuf, program, version, 0, 0); +#else /* !HAVE_LIBTIRPC */ + client = clntunix_create((struct sockaddr_un *)sap, + program, version, &sock, 0, 0); +#endif /* !HAVE_LIBTIRPC */ + if (client != NULL) + CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL); + else + (void)close(sock); + + return client; +} + +#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. + * + * @timeout is modified to contain the time remaining (i.e. time provided + * minus time elasped). + * + * Returns zero on success, or returns -1 on error. errno is + * set to reflect the nature of the error. + */ +static int nfs_connect_nb(const int fd, const struct sockaddr *sap, + const socklen_t salen, struct timeval *timeout) +{ + int flags, ret; + fd_set rset; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return -1; + + ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (ret < 0) + return -1; + + /* + * From here on subsequent sys calls could change errno so + * we set ret = -errno to capture it in case we decide to + * use it later. + */ + ret = connect(fd, sap, salen); + if (ret < 0 && errno != EINPROGRESS && errno != EINTR) { + ret = -1; + goto done; + } + + if (ret == 0) + goto done; + + /* now wait */ + FD_ZERO(&rset); + FD_SET(fd, &rset); + + while ((ret = select(fd + 1, NULL, &rset, NULL, timeout)) < 0) { + if (errno != EINTR) { + ret = -1; + goto done; + } else { + continue; + } + } + if (ret == 0) { + errno = ETIMEDOUT; + ret = -1; + goto done; + } + + if (FD_ISSET(fd, &rset)) { + int status; + socklen_t len = (socklen_t)sizeof(ret); + + status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len); + if (status < 0) { + ret = -1; + goto done; + } + + /* Oops - something wrong with connect */ + if (ret != 0) { + errno = ret; + ret = -1; + } + } + +done: + (void)fcntl(fd, F_SETFL, flags); + return ret; +} + +/* + * Set up an RPC client for communicating via a datagram socket. + * A connected UDP socket is used to detect a missing remote + * listener as quickly as possible. + * + * @timeout is initialized upon return + * + * Returns a pointer to a prepared RPC client if successful; caller + * must destroy a non-NULL returned RPC client. Otherwise NULL, and + * rpc_createerr.cf_stat is set to reflect the error. + */ +static CLIENT *nfs_get_udpclient(const struct sockaddr *sap, + const socklen_t salen, + const rpcprog_t program, + const rpcvers_t version, + struct timeval *timeout, + const int resvport) +{ + CLIENT *client; + int ret = 0; + int sock = 0; +#ifdef HAVE_LIBTIRPC + struct sockaddr_storage address; + const struct netbuf nbuf = { + .maxlen = salen, + .len = salen, + .buf = &address, + }; + +#else /* !HAVE_LIBTIRPC */ + + if (sap->sa_family != AF_INET) { + rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; + return NULL; + } +#endif /* !HAVE_LIBTIRPC */ + + sock = socket((int)sap->sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (sock == -1) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + return NULL; + } + + if (resvport) { + ret = nfs_bindresvport(sock, sap->sa_family); + + if (ret < 0) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + (void)close(sock); + return NULL; + } + } + + if (timeout->tv_sec == -1) + timeout->tv_sec = NFSRPC_TIMEOUT_UDP; + + ret = nfs_connect_nb(sock, sap, salen, timeout); + if (ret != 0) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + (void)close(sock); + return NULL; + } + +#ifdef HAVE_LIBTIRPC + memcpy(nbuf.buf, sap, (size_t)salen); + client = clnt_dg_create(sock, &nbuf, program, version, 0, 0); +#else /* !HAVE_LIBTIRPC */ + client = clntudp_create((struct sockaddr_in *)sap, program, + version, *timeout, &sock); +#endif /* !HAVE_LIBTIRPC */ + if (client != NULL) { + 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); + + return client; +} + +/* + * Set up and connect an RPC client for communicating via a stream socket. + * + * @timeout is initialized upon return + * + * Returns a pointer to a prepared and connected RPC client if + * successful; caller must destroy a non-NULL returned RPC client. + * Otherwise NULL, and rpc_createerr.cf_stat is set to reflect the + * error. + */ +static CLIENT *nfs_get_tcpclient(const struct sockaddr *sap, + const socklen_t salen, + const rpcprog_t program, + const rpcvers_t version, + struct timeval *timeout, + const int resvport) +{ + CLIENT *client; + int ret = 0; + int sock = 0; +#ifdef HAVE_LIBTIRPC + struct sockaddr_storage address; + const struct netbuf nbuf = { + .maxlen = salen, + .len = salen, + .buf = &address, + }; + +#else /* !HAVE_LIBTIRPC */ + + if (sap->sa_family != AF_INET) { + rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; + return NULL; + } +#endif /* !HAVE_LIBTIRPC */ + + sock = socket((int)sap->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + return NULL; + } + + if (resvport) { + ret = nfs_bindresvport(sock, sap->sa_family); + + if (ret < 0) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + (void)close(sock); + return NULL; + } + } + + if (timeout->tv_sec == -1) + timeout->tv_sec = NFSRPC_TIMEOUT_TCP; + + ret = nfs_connect_nb(sock, sap, salen, timeout); + if (ret != 0) { + rpc_createerr.cf_stat = RPC_SYSTEMERROR; + rpc_createerr.cf_error.re_errno = errno; + (void)close(sock); + return NULL; + } + +#ifdef HAVE_LIBTIRPC + memcpy(nbuf.buf, sap, (size_t)salen); + client = clnt_vc_create(sock, &nbuf, program, version, 0, 0); +#else /* !HAVE_LIBTIRPC */ + client = clnttcp_create((struct sockaddr_in *)sap, + program, version, &sock, 0, 0); +#endif /* !HAVE_LIBTIRPC */ + if (client != NULL) + CLNT_CONTROL(client, CLSET_FD_CLOSE, NULL); + else + (void)close(sock); + + return client; +} + +/** + * nfs_get_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. 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 + * client. Otherwise returns NULL, and rpc_createerr.cf_stat is set to + * reflect the error. + */ +CLIENT *nfs_get_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 (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 (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, 1); + case 0: + case IPPROTO_UDP: + return nfs_get_udpclient(sap, salen, program, version, + timeout, 1); + } + + rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; + return NULL; +} + +/** + * nfs_getrpcbyname - convert an RPC program name to a rpcprog_t + * @program: default program number to use if names not found in db + * @table: pointer to table of 'char *' names to try to find + * + * Returns program number of first name to be successfully looked + * up, or the default program number if all lookups fail. + */ +rpcprog_t nfs_getrpcbyname(const rpcprog_t program, const char *table[]) +{ +#ifdef HAVE_GETRPCBYNAME + struct rpcent *entry; + unsigned int i; + + if (table != NULL) + for (i = 0; table[i] != NULL; i++) { + entry = getrpcbyname(table[i]); + if (entry) + return (rpcprog_t)entry->r_number; + } +#endif /* HAVE_GETRPCBYNAME */ + + 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 authunix_create(machname, uid, gid, 1, &gid); +} |