root/usr/src/ucblib/librpcsoc/clnt_tcp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1990 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 1984, 1986, 1987, 1988, 1989, 1996 AT&T
 * All Rights Reserved
 */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

/*
 * clnt_tcp.c, Implements a TCP/IP based, client side RPC.
 *
 * TCP based RPC supports 'batched calls'.
 * A sequence of calls may be batched-up in a send buffer.  The rpc call
 * return immediately to the client even though the call was not necessarily
 * sent.  The batching occurs if the results' xdr routine is NULL (0) AND
 * the rpc timeout value is zero (see clnt.h, rpc).
 *
 * Clients should NOT casually batch calls that in fact return results; that is,
 * the server side should be aware that a call is batched and not produce any
 * return message.  Batched calls that produce many result messages can
 * deadlock (netlock) the client and the server....
 *
 * Now go hang yourself.
 */

#include <rpc/rpc.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>
#include <rpc/pmap_clnt.h>
#include <syslog.h>
#include <malloc.h>
#include <stdio.h>

#define MCALL_MSG_SIZE 24

extern int errno;

static int readtcp(void *, caddr_t, int);
static int writetcp(void *, caddr_t, int);
extern int _socket(int, int, int);
extern pid_t getpid(void);
extern int bindresvport(int, struct sockaddr_in *);
extern bool_t xdr_opaque_auth(XDR *, struct opaque_auth *);
static struct clnt_ops *clnttcp_ops(void);

struct ct_data {
        int             ct_sock;
        bool_t          ct_closeit;
        struct timeval  ct_wait;
        bool_t          ct_waitset;     /* wait set by clnt_control? */
        struct sockaddr_in ct_addr;
        struct rpc_err  ct_error;
        char            ct_mcall[MCALL_MSG_SIZE];       /* marshalled callmsg */
        uint_t          ct_mpos;                        /* pos after marshal */
        XDR             ct_xdrs;
};

/*
 * Create a client handle for a tcp/ip connection.
 * If *sockp<0, *sockp is set to a newly created TCP socket and it is
 * connected to raddr.  If *sockp non-negative then
 * raddr is ignored.  The rpc/tcp package does buffering
 * similar to stdio, so the client must pick send and receive buffer sizes
 * 0 => use the default.
 * If raddr->sin_port is 0, then a binder on the remote machine is
 * consulted for the right port number.
 * NB: *sockp is copied into a private area.
 * NB: It is the clients responsibility to close *sockp.
 * NB: The rpch->cl_auth is set null authentication.  Caller may wish to
 * set this something more useful.
 */
CLIENT *
clnttcp_create(struct sockaddr_in *raddr, rpcprog_t prog, rpcvers_t vers,
    int *sockp, uint_t sendsz, uint_t recvsz)
{
        CLIENT *h;
        struct ct_data *ct;
        struct timeval now;
        struct rpc_msg call_msg;

        h  = (CLIENT *)mem_alloc(sizeof (*h));
        if (h == NULL) {
                (void) syslog(LOG_ERR, "clnttcp_create: out of memory");
                rpc_createerr.cf_stat = RPC_SYSTEMERROR;
                rpc_createerr.cf_error.re_errno = errno;
                goto fooy;
        }
        ct = (struct ct_data *)mem_alloc(sizeof (*ct));
        if (ct == NULL) {
                (void) syslog(LOG_ERR, "clnttcp_create: out of memory");
                rpc_createerr.cf_stat = RPC_SYSTEMERROR;
                rpc_createerr.cf_error.re_errno = errno;
                goto fooy;
        }

        /*
         * If no port number given ask the pmap for one
         */
        if (raddr->sin_port == 0) {
                ushort_t port;
                if ((port = pmap_getport(raddr, prog, vers, IPPROTO_TCP))
                    == 0) {
                        mem_free((caddr_t)ct, sizeof (struct ct_data));
                        mem_free((caddr_t)h, sizeof (CLIENT));
                        return ((CLIENT *)NULL);
                }
                raddr->sin_port = htons(port);
        }

        /*
         * If no socket given, open one
         */
        if (*sockp < 0) {
                *sockp = _socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                (void) bindresvport(*sockp, (struct sockaddr_in *)0);
                if ((*sockp < 0)||
                    (connect(*sockp, (struct sockaddr *)raddr,
                    sizeof (*raddr)) < 0)) {
                        rpc_createerr.cf_stat = RPC_SYSTEMERROR;
                        rpc_createerr.cf_error.re_errno = errno;
                        (void) close(*sockp);
                        goto fooy;
                }
                ct->ct_closeit = TRUE;
        } else {
                ct->ct_closeit = FALSE;
        }

        /*
         * Set up private data struct
         */
        ct->ct_sock = *sockp;
        ct->ct_wait.tv_usec = 0;
        ct->ct_waitset = FALSE;
        ct->ct_addr = *raddr;

        /*
         * Initialize call message
         */
        (void) gettimeofday(&now, (struct timezone *)0);
        call_msg.rm_xid = getpid() ^ now.tv_sec ^ now.tv_usec;
        call_msg.rm_direction = CALL;
        call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
        call_msg.rm_call.cb_prog = prog;
        call_msg.rm_call.cb_vers = vers;

        /*
         * pre-serialize the staic part of the call msg and stash it away
         */
        xdrmem_create(&(ct->ct_xdrs), ct->ct_mcall, MCALL_MSG_SIZE,
            XDR_ENCODE);
        if (! xdr_callhdr(&(ct->ct_xdrs), &call_msg)) {
                if (ct->ct_closeit) {
                        (void) close(*sockp);
                }
                goto fooy;
        }
        ct->ct_mpos = XDR_GETPOS(&(ct->ct_xdrs));
        XDR_DESTROY(&(ct->ct_xdrs));

        /*
         * Create a client handle which uses xdrrec for serialization
         * and authnone for authentication.
         */
        xdrrec_create(&(ct->ct_xdrs), sendsz, recvsz,
            (caddr_t)ct, readtcp, writetcp);
        h->cl_ops = clnttcp_ops();
        h->cl_private = (caddr_t)ct;
        h->cl_auth = authnone_create();
        return (h);

fooy:
        /*
         * Something goofed, free stuff and barf
         */
        mem_free((caddr_t)ct, sizeof (struct ct_data));
        mem_free((caddr_t)h, sizeof (CLIENT));
        return (NULL);
}

static enum clnt_stat
clnttcp_call(CLIENT *h, rpcproc_t proc, xdrproc_t xdr_args, caddr_t args_ptr,
    xdrproc_t xdr_results, caddr_t results_ptr, struct timeval timeout)
{
        struct ct_data *ct;
        XDR *xdrs;
        struct rpc_msg reply_msg;
        uint32_t x_id;
        uint32_t *msg_x_id;
        bool_t shipnow;
        int refreshes;

        ct = (struct ct_data *)h->cl_private;
        xdrs = &(ct->ct_xdrs);
        msg_x_id = (uint32_t *)(ct->ct_mcall);  /* yuk */
        refreshes = 2;

        if (!ct->ct_waitset) {
                ct->ct_wait = timeout;
        }

        shipnow =
            (xdr_results == (xdrproc_t)0 && timeout.tv_sec == 0 &&
            timeout.tv_usec == 0) ? FALSE : TRUE;

call_again:
        xdrs->x_op = XDR_ENCODE;
        ct->ct_error.re_status = RPC_SUCCESS;
        x_id = ntohl(--(*msg_x_id));
        if ((! XDR_PUTBYTES(xdrs, ct->ct_mcall, ct->ct_mpos)) ||
            (! XDR_PUTINT32(xdrs, (int32_t *)&proc)) ||
            (! AUTH_MARSHALL(h->cl_auth, xdrs)) ||
            (! (*xdr_args)(xdrs, args_ptr))) {
                if (ct->ct_error.re_status == RPC_SUCCESS)
                        ct->ct_error.re_status = RPC_CANTENCODEARGS;
                (void) xdrrec_endofrecord(xdrs, TRUE);
                return (ct->ct_error.re_status);
        }
        if (! xdrrec_endofrecord(xdrs, shipnow))
                return (ct->ct_error.re_status = RPC_CANTSEND);
        if (! shipnow)
                return (RPC_SUCCESS);
        /*
         * Hack to provide rpc-based message passing
         */
        if (timeout.tv_sec == 0 && timeout.tv_usec == 0) {
                return (ct->ct_error.re_status = RPC_TIMEDOUT);
        }


        /*
         * Keep receiving until we get a valid transaction id
         */
        xdrs->x_op = XDR_DECODE;
        while (TRUE) {
                reply_msg.acpted_rply.ar_verf = _null_auth;
                reply_msg.acpted_rply.ar_results.where = NULL;
                reply_msg.acpted_rply.ar_results.proc = xdr_void;
                if (! xdrrec_skiprecord(xdrs))
                        return (ct->ct_error.re_status);
                        /* now decode and validate the response header */
                if (! xdr_replymsg(xdrs, &reply_msg)) {
                        if (ct->ct_error.re_status == RPC_SUCCESS)
                                continue;
                        return (ct->ct_error.re_status);
                }
                if (reply_msg.rm_xid == x_id)
                        break;
        }

        /*
         * process header
         */
        __seterr_reply(&reply_msg, &(ct->ct_error));
        if (ct->ct_error.re_status == RPC_SUCCESS) {
                if (! AUTH_VALIDATE(h->cl_auth,
                    &reply_msg.acpted_rply.ar_verf)) {
                        ct->ct_error.re_status = RPC_AUTHERROR;
                        ct->ct_error.re_why = AUTH_INVALIDRESP;
                } else if (! (*xdr_results)(xdrs, results_ptr)) {
                        if (ct->ct_error.re_status == RPC_SUCCESS)
                                ct->ct_error.re_status = RPC_CANTDECODERES;
                }
                /* free verifier ... */
                if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
                        xdrs->x_op = XDR_FREE;
                        (void) xdr_opaque_auth(xdrs,
                            &(reply_msg.acpted_rply.ar_verf));
                }
        }  /* end successful completion */
        else {
                /* maybe our credentials need to be refreshed ... */
                if (refreshes-- && AUTH_REFRESH(h->cl_auth, &reply_msg))
                        goto call_again;
        }  /* end of unsuccessful completion */
        return (ct->ct_error.re_status);
}

static void
clnttcp_geterr(CLIENT *h, struct rpc_err *errp)
{
        struct ct_data *ct;

        ct = (struct ct_data *)h->cl_private;
        *errp = ct->ct_error;
}

static bool_t
clnttcp_freeres(CLIENT *cl, xdrproc_t xdr_res, caddr_t res_ptr)
{
        struct ct_data *ct;
        XDR *xdrs;

        ct = (struct ct_data *)cl->cl_private;
        xdrs = &(ct->ct_xdrs);
        xdrs->x_op = XDR_FREE;
        return ((*xdr_res)(xdrs, res_ptr));
}

static void
clnttcp_abort(void)
{
}

static bool_t
clnttcp_control(CLIENT *cl, int request, char *info)
{
        struct ct_data *ct;

        ct = (struct ct_data *)cl->cl_private;
        switch (request) {
        case CLSET_TIMEOUT:
                ct->ct_wait = *(struct timeval *)info;
                ct->ct_waitset = TRUE;
                break;
        case CLGET_TIMEOUT:
                *(struct timeval *)info = ct->ct_wait;
                break;
        case CLGET_SERVER_ADDR:
                *(struct sockaddr_in *)info = ct->ct_addr;
                break;
        case CLGET_FD:
                *(int *)info = ct->ct_sock;
                break;
        case CLSET_FD_CLOSE:
                ct->ct_closeit = TRUE;
                break;
        case CLSET_FD_NCLOSE:
                ct->ct_closeit = FALSE;
                break;
        default:
                return (FALSE);
        }
        return (TRUE);
}


static void
clnttcp_destroy(CLIENT *h)
{
        struct ct_data *ct;

        ct = (struct ct_data *)h->cl_private;
        if (ct->ct_closeit) {
                (void) close(ct->ct_sock);
        }
        XDR_DESTROY(&(ct->ct_xdrs));
        mem_free((caddr_t)ct, sizeof (struct ct_data));
        mem_free((caddr_t)h, sizeof (CLIENT));
}

/*
 * Interface between xdr serializer and tcp connection.
 * Behaves like the system calls, read & write, but keeps some error state
 * around for the rpc level.
 */
static int
readtcp(void *data, caddr_t buf, int len)
{
        fd_set mask;
        fd_set readfds;
        struct ct_data *ct;

        if (len == 0)
                return (0);

        ct = data;
        FD_ZERO(&mask);
        FD_SET(ct->ct_sock, &mask);
        while (TRUE) {
                readfds = mask;
                switch (select(__rpc_dtbsize(),
                    &readfds, NULL, NULL, &(ct->ct_wait))) {
                case 0:
                        ct->ct_error.re_status = RPC_TIMEDOUT;
                        return (-1);

                case -1:
                        if (errno == EINTR)
                                continue;
                        ct->ct_error.re_status = RPC_CANTRECV;
                        ct->ct_error.re_errno = errno;
                        return (-1);
                }
                break;
        }
        switch (len = read(ct->ct_sock, buf, len)) {

        case 0:
                /* premature eof */
                ct->ct_error.re_errno = ECONNRESET;
                ct->ct_error.re_status = RPC_CANTRECV;
                len = -1;  /* it's really an error */
                break;

        case -1:
                ct->ct_error.re_errno = errno;
                ct->ct_error.re_status = RPC_CANTRECV;
                break;
        }
        return (len);
}

static int
writetcp(void *data, caddr_t buf, int len)
{
        struct ct_data *ct;
        int i, cnt;

        ct = data;
        for (cnt = len; cnt > 0; cnt -= i, buf += i) {
                if ((i = write(ct->ct_sock, buf, cnt)) == -1) {
                        ct->ct_error.re_errno = errno;
                        ct->ct_error.re_status = RPC_CANTSEND;
                        return (-1);
                }
        }
        return (len);
}

static struct clnt_ops *
clnttcp_ops(void)
{
        static struct clnt_ops ops;

        if (ops.cl_call == NULL) {
                ops.cl_call = clnttcp_call;
                ops.cl_abort = clnttcp_abort;
                ops.cl_geterr = clnttcp_geterr;
                ops.cl_freeres = clnttcp_freeres;
                ops.cl_destroy = clnttcp_destroy;
                ops.cl_control = clnttcp_control;
        }
        return (&ops);
}