freebsd-skq/lib/libc/rpc/svc_vc.c
dwmalone 86cf053ae0 Hopefully improve control message passing over Unix domain sockets.
1) Allow the sending of more than one control message at a time
over a unix domain socket. This should cover the PR 29499.

2) This requires that unp_{ex,in}ternalize and unp_scan understand
mbufs with more than one control message at a time.

3) Internalize and externalize used to work on the mbuf in-place.
This made life quite complicated and the code for sizeof(int) <
sizeof(file *) could end up doing the wrong thing. The patch always
create a new mbuf/cluster now. This resulted in the change of the
prototype for the domain externalise function.

4) You can now send SCM_TIMESTAMP messages.

5) Always use CMSG_DATA(cm) to determine the start where the data
in unp_{ex,in}ternalize. It was using ((struct cmsghdr *)cm + 1)
in some places, which gives the wrong alignment on the alpha.
(NetBSD made this fix some time ago).

This results in an ABI change for discriptor passing and creds
passing on the alpha. (Probably on the IA64 and Spare ports too).

6) Fix userland programs to use CMSG_* macros too.

7) Be more careful about freeing mbufs containing (file *)s.
This is made possible by the prototype change of externalise.

PR:		29499
MFC after:	6 weeks
2001-10-04 13:11:48 +00:00

706 lines
16 KiB
C

/* $NetBSD: svc_vc.c,v 1.7 2000/08/03 00:01:53 fvdl Exp $ */
/* $FreeBSD$ */
/*
* Sun RPC is a product of Sun Microsystems, Inc. and is provided for
* unrestricted use provided that this legend is included on all tape
* media and as a part of the software program in whole or part. Users
* may copy or modify Sun RPC without charge, but are not authorized
* to license or distribute it to anyone else except as part of a product or
* program developed by the user.
*
* SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
* WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
* PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
*
* Sun RPC is provided with no support and without any obligation on the
* part of Sun Microsystems, Inc. to assist in its use, correction,
* modification or enhancement.
*
* SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
* INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
* OR ANY PART THEREOF.
*
* In no event will Sun Microsystems, Inc. be liable for any lost revenue
* or profits or other special, indirect and consequential damages, even if
* Sun has been advised of the possibility of such damages.
*
* Sun Microsystems, Inc.
* 2550 Garcia Avenue
* Mountain View, California 94043
*/
#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
static char *sccsid = "@(#)svc_tcp.c 1.21 87/08/11 Copyr 1984 Sun Micro";
static char *sccsid = "@(#)svc_tcp.c 2.2 88/08/01 4.0 RPCSRC";
#endif
/*
* svc_vc.c, Server side for Connection Oriented based RPC.
*
* Actually implements two flavors of transporter -
* a tcp rendezvouser (a listner and connection establisher)
* and a record/tcp stream.
*/
#include "namespace.h"
#include "reentrant.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <rpc/rpc.h>
#include "rpc_com.h"
#include "un-namespace.h"
struct cmessage {
struct cmsghdr cmsg;
struct cmsgcred cmcred;
};
static SVCXPRT *makefd_xprt __P((int, u_int, u_int));
static bool_t rendezvous_request __P((SVCXPRT *, struct rpc_msg *));
static enum xprt_stat rendezvous_stat __P((SVCXPRT *));
static void svc_vc_destroy __P((SVCXPRT *));
static int read_vc __P((caddr_t, caddr_t, int));
static int write_vc __P((caddr_t, caddr_t, int));
static enum xprt_stat svc_vc_stat __P((SVCXPRT *));
static bool_t svc_vc_recv __P((SVCXPRT *, struct rpc_msg *));
static bool_t svc_vc_getargs __P((SVCXPRT *, xdrproc_t, caddr_t));
static bool_t svc_vc_freeargs __P((SVCXPRT *, xdrproc_t, caddr_t));
static bool_t svc_vc_reply __P((SVCXPRT *, struct rpc_msg *));
static void svc_vc_rendezvous_ops __P((SVCXPRT *));
static void svc_vc_ops __P((SVCXPRT *));
static bool_t svc_vc_control __P((SVCXPRT *xprt, const u_int rq, void *in));
static int __msgread_withcred(int, void *, size_t, struct cmessage *);
static int __msgwrite(int, void *, size_t);
struct cf_rendezvous { /* kept in xprt->xp_p1 for rendezvouser */
u_int sendsize;
u_int recvsize;
};
struct cf_conn { /* kept in xprt->xp_p1 for actual connection */
enum xprt_stat strm_stat;
u_int32_t x_id;
XDR xdrs;
char verf_body[MAX_AUTH_BYTES];
};
/*
* Usage:
* xprt = svc_vc_create(sock, send_buf_size, recv_buf_size);
*
* Creates, registers, and returns a (rpc) tcp based transporter.
* Once *xprt is initialized, it is registered as a transporter
* see (svc.h, xprt_register). This routine returns
* a NULL if a problem occurred.
*
* The filedescriptor passed in is expected to refer to a bound, but
* not yet connected socket.
*
* Since streams do buffered io similar to stdio, the caller can specify
* how big the send and receive buffers are via the second and third parms;
* 0 => use the system default.
*/
SVCXPRT *
svc_vc_create(fd, sendsize, recvsize)
int fd;
u_int sendsize;
u_int recvsize;
{
SVCXPRT *xprt;
struct cf_rendezvous *r = NULL;
struct __rpc_sockinfo si;
struct sockaddr_storage sslocal;
socklen_t slen;
int one = 1;
r = mem_alloc(sizeof(*r));
if (r == NULL) {
warnx("svc_vc_create: out of memory");
goto cleanup_svc_vc_create;
}
if (!__rpc_fd2sockinfo(fd, &si))
return NULL;
r->sendsize = __rpc_get_t_size(si.si_af, si.si_proto, (int)sendsize);
r->recvsize = __rpc_get_t_size(si.si_af, si.si_proto, (int)recvsize);
xprt = mem_alloc(sizeof(SVCXPRT));
if (xprt == NULL) {
warnx("svc_vc_create: out of memory");
goto cleanup_svc_vc_create;
}
xprt->xp_tp = NULL;
xprt->xp_p1 = (caddr_t)(void *)r;
xprt->xp_p2 = NULL;
xprt->xp_p3 = NULL;
xprt->xp_verf = _null_auth;
svc_vc_rendezvous_ops(xprt);
xprt->xp_port = (u_short)-1; /* It is the rendezvouser */
xprt->xp_fd = fd;
slen = sizeof (struct sockaddr_storage);
if (_getsockname(fd, (struct sockaddr *)(void *)&sslocal, &slen) < 0) {
warnx("svc_vc_create: could not retrieve local addr");
goto cleanup_svc_vc_create;
}
xprt->xp_ltaddr.maxlen = xprt->xp_ltaddr.len = sslocal.ss_len;
xprt->xp_ltaddr.buf = mem_alloc((size_t)sslocal.ss_len);
if (xprt->xp_ltaddr.buf == NULL) {
warnx("svc_vc_create: no mem for local addr");
goto cleanup_svc_vc_create;
}
memcpy(xprt->xp_ltaddr.buf, &sslocal, (size_t)sslocal.ss_len);
xprt->xp_rtaddr.maxlen = sizeof (struct sockaddr_storage);
xprt_register(xprt);
return (xprt);
cleanup_svc_vc_create:
if (r != NULL)
mem_free(r, sizeof(*r));
return (NULL);
}
/*
* Like svtcp_create(), except the routine takes any *open* UNIX file
* descriptor as its first input.
*/
SVCXPRT *
svc_fd_create(fd, sendsize, recvsize)
int fd;
u_int sendsize;
u_int recvsize;
{
struct sockaddr_storage ss;
socklen_t slen;
SVCXPRT *ret;
assert(fd != -1);
ret = makefd_xprt(fd, sendsize, recvsize);
if (ret == NULL)
return NULL;
slen = sizeof (struct sockaddr_storage);
if (_getsockname(fd, (struct sockaddr *)(void *)&ss, &slen) < 0) {
warnx("svc_fd_create: could not retrieve local addr");
goto freedata;
}
ret->xp_ltaddr.maxlen = ret->xp_ltaddr.len = ss.ss_len;
ret->xp_ltaddr.buf = mem_alloc((size_t)ss.ss_len);
if (ret->xp_ltaddr.buf == NULL) {
warnx("svc_fd_create: no mem for local addr");
goto freedata;
}
memcpy(ret->xp_ltaddr.buf, &ss, (size_t)ss.ss_len);
slen = sizeof (struct sockaddr_storage);
if (_getpeername(fd, (struct sockaddr *)(void *)&ss, &slen) < 0) {
warnx("svc_fd_create: could not retrieve remote addr");
goto freedata;
}
ret->xp_rtaddr.maxlen = ret->xp_rtaddr.len = ss.ss_len;
ret->xp_rtaddr.buf = mem_alloc((size_t)ss.ss_len);
if (ret->xp_rtaddr.buf == NULL) {
warnx("svc_fd_create: no mem for local addr");
goto freedata;
}
memcpy(ret->xp_rtaddr.buf, &ss, (size_t)ss.ss_len);
#ifdef PORTMAP
if (ss.ss_family == AF_INET) {
ret->xp_raddr = *(struct sockaddr_in *)ret->xp_rtaddr.buf;
ret->xp_addrlen = sizeof (struct sockaddr_in);
}
#endif /* PORTMAP */
return ret;
freedata:
if (ret->xp_ltaddr.buf != NULL)
mem_free(ret->xp_ltaddr.buf, rep->xp_ltaddr.maxlen);
return NULL;
}
static SVCXPRT *
makefd_xprt(fd, sendsize, recvsize)
int fd;
u_int sendsize;
u_int recvsize;
{
SVCXPRT *xprt;
struct cf_conn *cd;
const char *netid;
struct __rpc_sockinfo si;
assert(fd != -1);
xprt = mem_alloc(sizeof(SVCXPRT));
if (xprt == NULL) {
warnx("svc_vc: makefd_xprt: out of memory");
goto done;
}
memset(xprt, 0, sizeof *xprt);
cd = mem_alloc(sizeof(struct cf_conn));
if (cd == NULL) {
warnx("svc_tcp: makefd_xprt: out of memory");
mem_free(xprt, sizeof(SVCXPRT));
xprt = NULL;
goto done;
}
cd->strm_stat = XPRT_IDLE;
xdrrec_create(&(cd->xdrs), sendsize, recvsize,
(caddr_t)(void *)xprt, read_vc, write_vc);
xprt->xp_p1 = (caddr_t)(void *)cd;
xprt->xp_verf.oa_base = cd->verf_body;
svc_vc_ops(xprt); /* truely deals with calls */
xprt->xp_port = 0; /* this is a connection, not a rendezvouser */
xprt->xp_fd = fd;
if (__rpc_fd2sockinfo(fd, &si) && __rpc_sockinfo2netid(&si, &netid))
xprt->xp_netid = strdup(netid);
xprt_register(xprt);
done:
return (xprt);
}
/*ARGSUSED*/
static bool_t
rendezvous_request(xprt, msg)
SVCXPRT *xprt;
struct rpc_msg *msg;
{
int sock;
struct cf_rendezvous *r;
struct sockaddr_storage addr;
socklen_t len;
struct __rpc_sockinfo si;
assert(xprt != NULL);
assert(msg != NULL);
r = (struct cf_rendezvous *)xprt->xp_p1;
again:
len = sizeof addr;
if ((sock = _accept(xprt->xp_fd, (struct sockaddr *)(void *)&addr,
&len)) < 0) {
if (errno == EINTR)
goto again;
return (FALSE);
}
/*
* make a new transporter (re-uses xprt)
*/
xprt = makefd_xprt(sock, r->sendsize, r->recvsize);
xprt->xp_rtaddr.buf = mem_alloc(len);
if (xprt->xp_rtaddr.buf == NULL)
return (FALSE);
memcpy(xprt->xp_rtaddr.buf, &addr, len);
xprt->xp_rtaddr.len = len;
#ifdef PORTMAP
if (addr.ss_family == AF_INET) {
xprt->xp_raddr = *(struct sockaddr_in *)xprt->xp_rtaddr.buf;
xprt->xp_addrlen = sizeof (struct sockaddr_in);
}
#endif /* PORTMAP */
if (__rpc_fd2sockinfo(sock, &si) && si.si_proto == IPPROTO_TCP) {
len = 1;
/* XXX fvdl - is this useful? */
_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &len, sizeof (len));
}
return (FALSE); /* there is never an rpc msg to be processed */
}
/*ARGSUSED*/
static enum xprt_stat
rendezvous_stat(xprt)
SVCXPRT *xprt;
{
return (XPRT_IDLE);
}
static void
svc_vc_destroy(xprt)
SVCXPRT *xprt;
{
struct cf_conn *cd;
struct cf_rendezvous *r;
assert(xprt != NULL);
cd = (struct cf_conn *)xprt->xp_p1;
xprt_unregister(xprt);
if (xprt->xp_fd != RPC_ANYFD)
(void)_close(xprt->xp_fd);
if (xprt->xp_port != 0) {
/* a rendezvouser socket */
r = (struct cf_rendezvous *)xprt->xp_p1;
mem_free(r, sizeof (struct cf_rendezvous));
xprt->xp_port = 0;
} else {
/* an actual connection socket */
XDR_DESTROY(&(cd->xdrs));
mem_free(cd, sizeof(struct cf_conn));
}
if (xprt->xp_rtaddr.buf)
mem_free(xprt->xp_rtaddr.buf, xprt->xp_rtaddr.maxlen);
if (xprt->xp_ltaddr.buf)
mem_free(xprt->xp_ltaddr.buf, xprt->xp_ltaddr.maxlen);
if (xprt->xp_tp)
free(xprt->xp_tp);
if (xprt->xp_netid)
free(xprt->xp_netid);
mem_free(xprt, sizeof(SVCXPRT));
}
/*ARGSUSED*/
static bool_t
svc_vc_control(xprt, rq, in)
SVCXPRT *xprt;
const u_int rq;
void *in;
{
return (FALSE);
}
/*
* reads data from the tcp or uip connection.
* any error is fatal and the connection is closed.
* (And a read of zero bytes is a half closed stream => error.)
* All read operations timeout after 35 seconds. A timeout is
* fatal for the connection.
*/
static int
read_vc(xprtp, buf, len)
caddr_t xprtp;
caddr_t buf;
int len;
{
SVCXPRT *xprt;
int sock;
int milliseconds = 35 * 1000;
struct pollfd pollfd;
struct sockaddr *sa;
struct cmessage *cm;
xprt = (SVCXPRT *)(void *)xprtp;
assert(xprt != NULL);
sock = xprt->xp_fd;
do {
pollfd.fd = sock;
pollfd.events = POLLIN;
pollfd.revents = 0;
switch (_poll(&pollfd, 1, milliseconds)) {
case -1:
if (errno == EINTR)
continue;
/*FALLTHROUGH*/
case 0:
goto fatal_err;
default:
break;
}
} while ((pollfd.revents & POLLIN) == 0);
sa = (struct sockaddr *)xprt->xp_rtaddr.buf;
if (sa->sa_family == AF_LOCAL) {
cm = (struct cmessage *)xprt->xp_verf.oa_base;
if ((len = __msgread_withcred(sock, buf, len, cm)) > 0) {
xprt->xp_p2 = &cm->cmcred;
return (len);
}
} else {
if ((len = _read(sock, buf, (size_t)len)) > 0)
return (len);
}
fatal_err:
((struct cf_conn *)(xprt->xp_p1))->strm_stat = XPRT_DIED;
return (-1);
}
/*
* writes data to the tcp connection.
* Any error is fatal and the connection is closed.
*/
static int
write_vc(xprtp, buf, len)
caddr_t xprtp;
caddr_t buf;
int len;
{
SVCXPRT *xprt;
int i, cnt;
struct sockaddr *sa;
xprt = (SVCXPRT *)(void *)xprtp;
assert(xprt != NULL);
sa = (struct sockaddr *)xprt->xp_rtaddr.buf;
if (sa->sa_family == AF_LOCAL) {
for (cnt = len; cnt > 0; cnt -= i, buf += i) {
if ((i = __msgwrite(xprt->xp_fd, buf,
(size_t)cnt)) < 0) {
((struct cf_conn *)(xprt->xp_p1))->strm_stat =
XPRT_DIED;
return (-1);
}
}
} else {
for (cnt = len; cnt > 0; cnt -= i, buf += i) {
if ((i = _write(xprt->xp_fd, buf,
(size_t)cnt)) < 0) {
((struct cf_conn *)(xprt->xp_p1))->strm_stat =
XPRT_DIED;
return (-1);
}
}
}
return (len);
}
static enum xprt_stat
svc_vc_stat(xprt)
SVCXPRT *xprt;
{
struct cf_conn *cd;
assert(xprt != NULL);
cd = (struct cf_conn *)(xprt->xp_p1);
if (cd->strm_stat == XPRT_DIED)
return (XPRT_DIED);
if (! xdrrec_eof(&(cd->xdrs)))
return (XPRT_MOREREQS);
return (XPRT_IDLE);
}
static bool_t
svc_vc_recv(xprt, msg)
SVCXPRT *xprt;
struct rpc_msg *msg;
{
struct cf_conn *cd;
XDR *xdrs;
assert(xprt != NULL);
assert(msg != NULL);
cd = (struct cf_conn *)(xprt->xp_p1);
xdrs = &(cd->xdrs);
xdrs->x_op = XDR_DECODE;
(void)xdrrec_skiprecord(xdrs);
if (xdr_callmsg(xdrs, msg)) {
cd->x_id = msg->rm_xid;
return (TRUE);
}
cd->strm_stat = XPRT_DIED;
return (FALSE);
}
static bool_t
svc_vc_getargs(xprt, xdr_args, args_ptr)
SVCXPRT *xprt;
xdrproc_t xdr_args;
caddr_t args_ptr;
{
assert(xprt != NULL);
/* args_ptr may be NULL */
return ((*xdr_args)(&(((struct cf_conn *)(xprt->xp_p1))->xdrs),
args_ptr));
}
static bool_t
svc_vc_freeargs(xprt, xdr_args, args_ptr)
SVCXPRT *xprt;
xdrproc_t xdr_args;
caddr_t args_ptr;
{
XDR *xdrs;
assert(xprt != NULL);
/* args_ptr may be NULL */
xdrs = &(((struct cf_conn *)(xprt->xp_p1))->xdrs);
xdrs->x_op = XDR_FREE;
return ((*xdr_args)(xdrs, args_ptr));
}
static bool_t
svc_vc_reply(xprt, msg)
SVCXPRT *xprt;
struct rpc_msg *msg;
{
struct cf_conn *cd;
XDR *xdrs;
bool_t stat;
assert(xprt != NULL);
assert(msg != NULL);
cd = (struct cf_conn *)(xprt->xp_p1);
xdrs = &(cd->xdrs);
xdrs->x_op = XDR_ENCODE;
msg->rm_xid = cd->x_id;
stat = xdr_replymsg(xdrs, msg);
(void)xdrrec_endofrecord(xdrs, TRUE);
return (stat);
}
static void
svc_vc_ops(xprt)
SVCXPRT *xprt;
{
static struct xp_ops ops;
static struct xp_ops2 ops2;
extern mutex_t ops_lock;
/* VARIABLES PROTECTED BY ops_lock: ops, ops2 */
mutex_lock(&ops_lock);
if (ops.xp_recv == NULL) {
ops.xp_recv = svc_vc_recv;
ops.xp_stat = svc_vc_stat;
ops.xp_getargs = svc_vc_getargs;
ops.xp_reply = svc_vc_reply;
ops.xp_freeargs = svc_vc_freeargs;
ops.xp_destroy = svc_vc_destroy;
ops2.xp_control = svc_vc_control;
}
xprt->xp_ops = &ops;
xprt->xp_ops2 = &ops2;
mutex_unlock(&ops_lock);
}
static void
svc_vc_rendezvous_ops(xprt)
SVCXPRT *xprt;
{
static struct xp_ops ops;
static struct xp_ops2 ops2;
extern mutex_t ops_lock;
mutex_lock(&ops_lock);
if (ops.xp_recv == NULL) {
ops.xp_recv = rendezvous_request;
ops.xp_stat = rendezvous_stat;
ops.xp_getargs =
(bool_t (*) __P((SVCXPRT *, xdrproc_t, caddr_t)))abort;
ops.xp_reply =
(bool_t (*) __P((SVCXPRT *, struct rpc_msg *)))abort;
ops.xp_freeargs =
(bool_t (*) __P((SVCXPRT *, xdrproc_t, caddr_t)))abort,
ops.xp_destroy = svc_vc_destroy;
ops2.xp_control = svc_vc_control;
}
xprt->xp_ops = &ops;
xprt->xp_ops2 = &ops2;
mutex_unlock(&ops_lock);
}
int
__msgread_withcred(sock, buf, cnt, cmp)
int sock;
void *buf;
size_t cnt;
struct cmessage *cmp;
{
struct iovec iov[1];
struct msghdr msg;
union {
struct cmsghdr cmsg;
char control[CMSG_SPACE(sizeof(struct cmsgcred))];
} cm;
int ret;
bzero(&cm, sizeof(cm));
iov[0].iov_base = buf;
iov[0].iov_len = cnt;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = &cm;
msg.msg_controllen = CMSG_SPACE(sizeof(struct cmsgcred));
msg.msg_flags = 0;
ret = _recvmsg(sock, &msg, 0);
bcopy(&cm.cmsg, &cmp->cmsg, sizeof(cmp->cmsg));
bcopy(CMSG_DATA(&cm), &cmp->cmcred, sizeof(cmp->cmcred));
return ret;
}
static int
__msgwrite(sock, buf, cnt)
int sock;
void *buf;
size_t cnt;
{
struct iovec iov[1];
struct msghdr msg;
struct cmessage cm;
bzero((char *)&cm, sizeof(cm));
iov[0].iov_base = buf;
iov[0].iov_len = cnt;
cm.cmsg.cmsg_type = SCM_CREDS;
cm.cmsg.cmsg_level = SOL_SOCKET;
cm.cmsg.cmsg_len = sizeof(struct cmessage);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = (caddr_t)&cm;
msg.msg_controllen = sizeof(struct cmessage);
msg.msg_flags = 0;
return(_sendmsg(sock, &msg, 0));
}
/*
* Get the effective UID of the sending process. Used by rpcbind and keyserv
* (AF_LOCAL).
*/
int
__rpc_get_local_uid(SVCXPRT *transp, uid_t *uid)
{
struct cmsgcred *cmcred;
cmcred = __svc_getcallercreds(transp);
if (cmcred == NULL)
return(-1);
*uid = cmcred->cmcred_euid;
return(0);
}