OpenSolaris_b135/lib/libnsl/rpc/clnt_door.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * clnt_doors.c, Client side for doors IPC based RPC.
 */

#include "mt.h"
#include "rpc_mt.h"
#include <rpc/rpc.h>
#include <errno.h>
#include <sys/poll.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/kstat.h>
#include <sys/time.h>
#include <door.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <alloca.h>
#include <rpc/svc_mt.h>
#include <sys/mman.h>
#include <atomic.h>


extern bool_t xdr_opaque_auth(XDR *, struct opaque_auth *);

static struct clnt_ops *clnt_door_ops();

extern int __rpc_default_door_buf_size;
extern int __rpc_min_door_buf_size;

/*
 * Private data kept per client handle
 */
struct cu_data {
	int			cu_fd;		/* door fd */
	bool_t			cu_closeit;	/* close it on destroy */
	struct rpc_err		cu_error;
	uint_t			cu_xdrpos;
	uint_t			cu_sendsz;	/* send size */
	char			cu_header[32];	/* precreated header */
};

/*
 * Door IPC based client creation routine.
 *
 * NB: The rpch->cl_auth is initialized to null authentication.
 * 	Caller may wish to set this something more useful.
 *
 * sendsz is the maximum allowable packet size that can be sent.
 * 0 will cause default to be used.
 */
CLIENT *
clnt_door_create(const rpcprog_t program, const rpcvers_t version,
							const uint_t sendsz)
{
	CLIENT			*cl = NULL;	/* client handle */
	struct cu_data		*cu = NULL;	/* private data */
	struct rpc_msg		call_msg;
	char			rendezvous[64];
	int			did;
	struct door_info	info;
	XDR			xdrs;
	struct timeval		now;
	uint_t			ssz;

	(void) sprintf(rendezvous, RPC_DOOR_RENDEZVOUS, (int)program,
	    (int)version);
	if ((did = open(rendezvous, O_RDONLY, 0)) < 0) {
		rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
		rpc_createerr.cf_error.re_errno = errno;
		rpc_createerr.cf_error.re_terrno = 0;
		return (NULL);
	}

	if (door_info(did, &info) < 0 || (info.di_attributes & DOOR_REVOKED)) {
		(void) close(did);
		rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
		rpc_createerr.cf_error.re_errno = errno;
		rpc_createerr.cf_error.re_terrno = 0;
		return (NULL);
	}

	/*
	 * Determine send size
	 */
	if (sendsz < __rpc_min_door_buf_size)
		ssz = __rpc_default_door_buf_size;
	else
		ssz = RNDUP(sendsz);

	if ((cl = malloc(sizeof (CLIENT))) == NULL ||
	    (cu = malloc(sizeof (*cu))) == NULL) {
		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
		rpc_createerr.cf_error.re_errno = errno;
		goto err;
	}

	/*
	 * Precreate RPC header for performance reasons.
	 */
	(void) gettimeofday(&now, NULL);
	call_msg.rm_xid = getpid() ^ now.tv_sec ^ now.tv_usec;
	call_msg.rm_call.cb_prog = program;
	call_msg.rm_call.cb_vers = version;
	xdrmem_create(&xdrs, cu->cu_header, sizeof (cu->cu_header), XDR_ENCODE);
	if (!xdr_callhdr(&xdrs, &call_msg)) {
		rpc_createerr.cf_stat = RPC_CANTENCODEARGS;
		rpc_createerr.cf_error.re_errno = 0;
		goto err;
	}
	cu->cu_xdrpos = XDR_GETPOS(&xdrs);

	cu->cu_sendsz = ssz;
	cu->cu_fd = did;
	cu->cu_closeit = TRUE;
	cl->cl_ops = clnt_door_ops();
	cl->cl_private = (caddr_t)cu;
	cl->cl_auth = authnone_create();
	cl->cl_tp = strdup(rendezvous);
	if (cl->cl_tp == NULL) {
		syslog(LOG_ERR, "clnt_door_create: strdup failed");
		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
		rpc_createerr.cf_error.re_errno = errno;
		goto err;
	}
	cl->cl_netid = strdup("door");
	if (cl->cl_netid == NULL) {
		syslog(LOG_ERR, "clnt_door_create: strdup failed");
		if (cl->cl_tp)
			free(cl->cl_tp);
		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
		rpc_createerr.cf_error.re_errno = errno;
		goto err;
	}
	return (cl);
err:
	rpc_createerr.cf_error.re_terrno = 0;
	if (cl) {
		free(cl);
		if (cu)
			free(cu);
	}
	(void) close(did);
	return (NULL);
}

/* ARGSUSED */
static enum clnt_stat
clnt_door_call(CLIENT *cl, rpcproc_t proc, xdrproc_t xargs, caddr_t argsp,
	xdrproc_t xresults, caddr_t resultsp, struct timeval utimeout)
{
/* LINTED pointer alignment */
	struct cu_data	*cu = (struct cu_data *)cl->cl_private;
	XDR 		xdrs;
	door_arg_t	params;
	char		*outbuf_ref;
	struct rpc_msg	reply_msg;
	bool_t		need_to_unmap;
	uint32_t	xid;
	int		nrefreshes = 2;	/* number of times to refresh cred */

	rpc_callerr.re_errno = 0;
	rpc_callerr.re_terrno = 0;

	if ((params.rbuf = alloca(cu->cu_sendsz)) == NULL) {
		rpc_callerr.re_terrno = 0;
		rpc_callerr.re_errno = errno;
		return (rpc_callerr.re_status = RPC_SYSTEMERROR);
	}
	outbuf_ref = params.rbuf;
	params.rsize = cu->cu_sendsz;
	if ((params.data_ptr = alloca(cu->cu_sendsz)) == NULL) {
		rpc_callerr.re_terrno = 0;
		rpc_callerr.re_errno = errno;
		return (rpc_callerr.re_status = RPC_SYSTEMERROR);
	}

call_again:
	xdrmem_create(&xdrs, params.data_ptr, cu->cu_sendsz, XDR_ENCODE);
	/* Increment XID (not really needed for RPC over doors...) */
	/* LINTED pointer alignment */
	xid = atomic_inc_uint_nv((uint32_t *)cu->cu_header);
	(void) memcpy(params.data_ptr, cu->cu_header, cu->cu_xdrpos);
	/* LINTED pointer alignment */
	*(uint32_t *)params.data_ptr = xid;
	XDR_SETPOS(&xdrs, cu->cu_xdrpos);

	if ((!XDR_PUTINT32(&xdrs, (int32_t *)&proc)) ||
	    (!AUTH_MARSHALL(cl->cl_auth, &xdrs)) ||
	    (!(*xargs)(&xdrs, argsp))) {
		return (rpc_callerr.re_status = RPC_CANTENCODEARGS);
	}
	params.data_size = (int)XDR_GETPOS(&xdrs);

	params.desc_ptr = NULL;
	params.desc_num = 0;
	if (door_call(cu->cu_fd, &params) < 0) {
		rpc_callerr.re_errno = errno;
		return (rpc_callerr.re_status = RPC_CANTSEND);
	}

	if (params.rbuf == NULL || params.rsize == 0) {
		return (rpc_callerr.re_status = RPC_FAILED);
	}
	need_to_unmap = (params.rbuf != outbuf_ref);

/* LINTED pointer alignment */
	if (*(uint32_t *)params.rbuf != xid) {
		rpc_callerr.re_status = RPC_CANTDECODERES;
		goto done;
	}

	xdrmem_create(&xdrs, params.rbuf, params.rsize, XDR_DECODE);
	reply_msg.acpted_rply.ar_verf = _null_auth;
	reply_msg.acpted_rply.ar_results.where = resultsp;
	reply_msg.acpted_rply.ar_results.proc = xresults;

	if (xdr_replymsg(&xdrs, &reply_msg)) {
		if (reply_msg.rm_reply.rp_stat == MSG_ACCEPTED &&
		    reply_msg.acpted_rply.ar_stat == SUCCESS)
			rpc_callerr.re_status = RPC_SUCCESS;
		else
			__seterr_reply(&reply_msg, &rpc_callerr);

		if (rpc_callerr.re_status == RPC_SUCCESS) {
			if (!AUTH_VALIDATE(cl->cl_auth,
			    &reply_msg.acpted_rply.ar_verf)) {
				rpc_callerr.re_status = RPC_AUTHERROR;
				rpc_callerr.re_why = AUTH_INVALIDRESP;
			}
			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));
			}
		}
		/*
		 * If unsuccesful AND error is an authentication error
		 * then refresh credentials and try again, else break
		 */
		else if (rpc_callerr.re_status == RPC_AUTHERROR) {
			/*
			 * maybe our credentials need to be refreshed ...
			 */
			if (nrefreshes-- &&
			    AUTH_REFRESH(cl->cl_auth, &reply_msg)) {
				if (need_to_unmap)
					(void) munmap(params.rbuf,
					    params.rsize);
				goto call_again;
			} else
				/*
				 * We are setting rpc_callerr here given that
				 * libnsl is not reentrant thereby
				 * reinitializing the TSD.  If not set here then
				 * success could be returned even though refresh
				 * failed.
				 */
				rpc_callerr.re_status = RPC_AUTHERROR;
		}
	} else
		rpc_callerr.re_status = RPC_CANTDECODERES;

done:
	if (need_to_unmap)
		(void) munmap(params.rbuf, params.rsize);
	return (rpc_callerr.re_status);
}

/* ARGSUSED */
static enum clnt_stat
clnt_door_send(CLIENT *cl, rpcproc_t proc, xdrproc_t xargs, caddr_t argsp)
{
	/* send() call not supported on doors */

	rpc_callerr.re_errno = ENOTSUP;
	rpc_callerr.re_terrno = 0;

	return (rpc_callerr.re_status = RPC_FAILED);
}

static void
clnt_door_geterr(CLIENT *cl, struct rpc_err *errp)
{
/* LINTED pointer alignment */
	struct cu_data	*cu = (struct cu_data *)cl->cl_private;

	*errp = rpc_callerr;
}

/* ARGSUSED */
static bool_t
clnt_door_freeres(CLIENT *cl, xdrproc_t xdr_res, caddr_t res_ptr)
{
	XDR		xdrs;

	(void) memset(&xdrs, 0, sizeof (xdrs));
	xdrs.x_op = XDR_FREE;
	return ((*xdr_res)(&xdrs, res_ptr));
}

static void
clnt_door_abort(CLIENT *cl)
{
	cl = cl;
}

static bool_t
clnt_door_control(CLIENT *cl, int request, char *info)
{
/* LINTED pointer alignment */
	struct cu_data	*cu = (struct cu_data *)cl->cl_private;

	switch (request) {
	case CLSET_FD_CLOSE:
		cu->cu_closeit = TRUE;
		return (TRUE);

	case CLSET_FD_NCLOSE:
		cu->cu_closeit = FALSE;
		return (TRUE);
	}

	/* for other requests which use info */
	if (info == NULL)
		return (FALSE);

	switch (request) {
	case CLGET_FD:
/* LINTED pointer alignment */
		*(int *)info = cu->cu_fd;
		break;

	case CLGET_XID:
		/*
		 * use the knowledge that xid is the
		 * first element in the call structure *.
		 * This will get the xid of the PREVIOUS call
		 */
/* LINTED pointer alignment */
		*(uint32_t *)info = ntohl(*(uint32_t *)cu->cu_header);
		break;

	case CLSET_XID:
		/* This will set the xid of the NEXT call */
/* LINTED pointer alignment */
		*(uint32_t *)cu->cu_header =  htonl(*(uint32_t *)info - 1);
		/* decrement by 1 as clnt_door_call() increments once */
		break;

	case CLGET_VERS:
		/*
		 * This RELIES on the information that, in the call body,
		 * the version number field is the fifth field from the
		 * begining of the RPC header. MUST be changed if the
		 * call_struct is changed
		 */
/* LINTED pointer alignment */
		*(uint32_t *)info = ntohl(*(uint32_t *)(cu->cu_header +
		    4 * BYTES_PER_XDR_UNIT));
		break;

	case CLSET_VERS:
/* LINTED pointer alignment */
		*(uint32_t *)(cu->cu_header + 4 * BYTES_PER_XDR_UNIT) =
/* LINTED pointer alignment */
		    htonl(*(uint32_t *)info);
		break;

	case CLGET_PROG:
		/*
		 * This RELIES on the information that, in the call body,
		 * the program number field is the fourth field from the
		 * begining of the RPC header. MUST be changed if the
		 * call_struct is changed
		 */
/* LINTED pointer alignment */
		*(uint32_t *)info = ntohl(*(uint32_t *)(cu->cu_header +
		    3 * BYTES_PER_XDR_UNIT));
		break;

	case CLSET_PROG:
/* LINTED pointer alignment */
		*(uint32_t *)(cu->cu_header + 3 * BYTES_PER_XDR_UNIT) =
/* LINTED pointer alignment */
		    htonl(*(uint32_t *)info);
		break;

	default:
		return (FALSE);
	}
	return (TRUE);
}

static void
clnt_door_destroy(CLIENT *cl)
{
/* LINTED pointer alignment */
	struct cu_data	*cu = (struct cu_data *)cl->cl_private;
	int		cu_fd = cu->cu_fd;

	if (cu->cu_closeit)
		(void) close(cu_fd);
	free(cu);
	if (cl->cl_netid && cl->cl_netid[0])
		free(cl->cl_netid);
	if (cl->cl_tp && cl->cl_tp[0])
		free(cl->cl_tp);
	free(cl);
}

static struct clnt_ops *
clnt_door_ops(void)
{
	static struct clnt_ops	ops;
	extern mutex_t		ops_lock;

	sig_mutex_lock(&ops_lock);
	if (ops.cl_call == NULL) {
		ops.cl_call = clnt_door_call;
		ops.cl_send = clnt_door_send;
		ops.cl_abort = clnt_door_abort;
		ops.cl_geterr = clnt_door_geterr;
		ops.cl_freeres = clnt_door_freeres;
		ops.cl_destroy = clnt_door_destroy;
		ops.cl_control = clnt_door_control;
	}
	sig_mutex_unlock(&ops_lock);
	return (&ops);
}

int
_update_did(CLIENT *cl, int vers)
{
/* LINTED pointer alignment */
	struct cu_data	*cu = (struct cu_data *)cl->cl_private;
	rpcprog_t prog;
	char rendezvous[64];

	if (cu->cu_fd >= 0)
		(void) close(cu->cu_fd);
/* Make sure that the right door id is used in door_call. */
	clnt_control(cl, CLGET_PROG, (void *)&prog);
	(void) sprintf(rendezvous, RPC_DOOR_RENDEZVOUS, (int)prog, vers);
	if ((cu->cu_fd = open(rendezvous, O_RDONLY, 0)) < 0) {
		rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
		rpc_createerr.cf_error.re_errno = errno;
		rpc_createerr.cf_error.re_terrno = 0;
		return (0);
	}
	free(cl->cl_tp);
	cl->cl_tp = strdup(rendezvous);
	if (cl->cl_tp == NULL) {
		syslog(LOG_ERR, "_update_did: strdup failed");
		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
		rpc_createerr.cf_error.re_errno = errno;
		rpc_createerr.cf_error.re_terrno = 0;
		return (0);
	}
	return (1);
}