| /* |
| * 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); |
| } |