
/* Based on code written by Tres Seaver / tseaver@palladion.com */

#include <sys/socket.h>
#include <iovec.h>

typedef union {
	struct cmsghdr cm;
	char   control[ CMSG_SPACE( sizeof( int ) ) ];
} CMsgHdrUnion;


/*
 *  Pass a descriptor across a Unix domain stream socket.  Return number of
 *  bytes of main payload passed (caller should pass at least one byte, to
 *  allow peer to detect EOF cleanly).
 *
 *  See W. R. Stevens, Unix Network Programming (2nd ed), vol 1, p. 389.
 *
 *  In "pre-forked child" use case, sockFD may be one end of a socketpair();
 *  for unrelated servers, should be created by bind()/connect().
 */

ssize_t
write_fd(int       sockFD,   /* AF_UNIX stream socket */
	 void     *payload,  /* Payload buffer */
	 size_t    nPL,      /* Size of payload */
	 int       sendFD)   /* Descriptor to be passed */
{
	struct msghdr   msg;    /* sendmsg() buffer */
	struct iovec    iov[1]; /* scatter-gather payload vector */
	CMsgHdrUnion    controlX;
	struct cmsghdr *cmptr;

	msg.msg_name = NULL;     /* name not used for stream sockets. */
	msg.msg_namelen = 0;

	iov[0].iov_base = payload;  /* set up scatter-gather. */
	iov[0].iov_len = nPL;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	/*
	 *  XXX:  Older Unixen may use msg_accrights instead of msg_control.
	 */
	msg.msg_control     = controlX.control;
	msg.msg_controllen  = sizeof( controlX.control );
	
	cmptr               = CMSG_FIRSTHDR( &msg );
	cmptr->cmsg_len     = CMSG_LEN( sizeof( int ) );
	cmptr->cmsg_level   = SOL_SOCKET;
	cmptr->cmsg_type    = SCM_RIGHTS;
	
	*( (int*) CMSG_DATA( cmptr ) ) = sendFD;

	return( sendmsg( sockFD, &msg, 0 ) );
}

/*
 *  Receive a descriptor across a Unix domain stream socket.  Return number of
 *  bytes of main payload passed (should be least one byte, to distinguish
 *  EOF).
 *
 *  See W. R. Stevens, Unix Network Programming (2nd ed), vol 1, p. 387.
 *
 *  In "pre-forked child" use case, sockFD may be one end of a socketpair();
 *  for unrelated servers, should be created by bind()/connect().
 */
ssize_t
read_fd(int        sockFD,  /* AF_UNIX socket over which to pass descriptor */
	void      *payload, /* Payload buffer */
	size_t     maxPL,   /* Size of payload buffer */
	int       *recvFD)  /* Address of descriptor being received */
{
	struct msghdr   msg;    /* recvmsg() message buffer */
	struct iovec    iov[1];  /* Scatter-gather payload vector */
	ssize_t         nPL;     /* Size of actual payload received */
	CMsgHdrUnion    controlX;
	strcut cmsghdr *cmptr;

	msg.msg_name = NULL; /* name not used for stream sockets */
	msg.msg_namelen = 0;

	iov[0].iov_base = payload;  /* set up scatter-gather */
	iov[0].iov_len = maxPL;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	/*
	 *  XXX:  Older Unixen may use msg_accrights instead of msg_control.
	 */
	msg.msg_control     = controlX.control;
	msg.msg_controllen  = sizeof( controlX.control );
	
	/*
	 *  XXX:  Stevens does'nt pre-initialize cm, but RH 5.2 acts flaky
	 *        without it.
	 */
	cmptr               = CMSG_FIRSTHDR( &msg );
	cmptr->cmsg_len     = CMSG_LEN( sizeof( int ) );
	cmptr->cmsg_level   = SOL_SOCKET;
	cmptr->cmsg_type    = SCM_RIGHTS;
	
	*( (int*) CMSG_DATA( cmptr ) ) = -1;

	nPL = recvmsg( sockFD, &msg, 0 );
	
	if ( nPL <= 0 )
		return( nPL );
	
	/*
	 *  XXX:    Might realloc, so reseat?
	 */
	cmptr = CMSG_FIRSTHDR( &msg );
	
	if ( cmptr != NULL
	     && cmptr->cmsg_len    == CMSG_LEN( sizeof( int ) )
	     && cmptr->cmsg_level  == SOL_SOCKET
	     && cmptr->cmsg_type   == SCM_RIGHTS
	     )
		*recvFD = *( (int*) CMSG_DATA( cmptr ) );
	else
		*recvFD = -1;       /* descriptor was not passed */
	
	return( nPL );
}

