
The getnucred() function was used by UDS to obtain credentials of user processes in a form used in the UDS API, namely the ucred structure. Since the NetBSD merge, this structure has changed drastically (aside from being renamed to "uucred"), and it is no longer in UDS's best interest to use this structure internally. Therefore, getnucred() is no longer a useful API either, and instead we directly use the previously private getepinfo() function to obtain credentials. Change-Id: I80bc809de716ec0a9b7497cb109d2f2708a629d5
1115 lines
28 KiB
C
1115 lines
28 KiB
C
/*
|
|
* Unix Domain Sockets Implementation (PF_UNIX, PF_LOCAL)
|
|
* This code handles ioctl(2) commands to implement the socket API.
|
|
* Some helper functions are also present.
|
|
*/
|
|
|
|
#include "uds.h"
|
|
|
|
static int
|
|
perform_connection(devminor_t minorx, devminor_t minory,
|
|
struct sockaddr_un *addr)
|
|
{
|
|
/*
|
|
* There are several places were a connection is established, the
|
|
* initiating call being one of accept(2), connect(2), socketpair(2).
|
|
*/
|
|
dprintf(("UDS: perform_connection(%d, %d)\n", minorx, minory));
|
|
|
|
/*
|
|
* Only connection-oriented types are acceptable and only equal
|
|
* types can connect to each other.
|
|
*/
|
|
if ((uds_fd_table[minorx].type != SOCK_SEQPACKET &&
|
|
uds_fd_table[minorx].type != SOCK_STREAM) ||
|
|
uds_fd_table[minorx].type != uds_fd_table[minory].type)
|
|
return EINVAL;
|
|
|
|
/* Connect the pair of sockets. */
|
|
uds_fd_table[minorx].peer = minory;
|
|
uds_fd_table[minory].peer = minorx;
|
|
|
|
/* Set the address of both sockets */
|
|
memcpy(&uds_fd_table[minorx].addr, addr, sizeof(struct sockaddr_un));
|
|
memcpy(&uds_fd_table[minory].addr, addr, sizeof(struct sockaddr_un));
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_accept(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
devminor_t minorparent; /* minor number of parent (server) */
|
|
devminor_t minorpeer;
|
|
int rc, i;
|
|
struct sockaddr_un addr;
|
|
|
|
dprintf(("UDS: do_accept(%d)\n", minor));
|
|
|
|
/*
|
|
* Somewhat weird logic is used in this function, so here's an
|
|
* overview... The minor number is the server's client socket
|
|
* (the socket to be returned by accept()). The data waiting
|
|
* for us in the IO Grant is the address that the server is
|
|
* listening on. This function uses the address to find the
|
|
* server's descriptor. From there we can perform the
|
|
* connection or suspend and wait for a connect().
|
|
*/
|
|
|
|
/* This IOCTL must be called on a 'fresh' socket. */
|
|
if (uds_fd_table[minor].type != -1)
|
|
return EINVAL;
|
|
|
|
/* Get the server's address */
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
|
|
sizeof(struct sockaddr_un))) != OK)
|
|
return rc;
|
|
|
|
/* Locate the server socket. */
|
|
for (i = 0; i < NR_FDS; i++) {
|
|
if (uds_fd_table[i].stale == FALSE &&
|
|
uds_fd_table[i].listening == TRUE &&
|
|
uds_fd_table[i].addr.sun_family == AF_UNIX &&
|
|
!strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
|
|
sizeof(uds_fd_table[i].addr.sun_path)))
|
|
break;
|
|
}
|
|
|
|
if (i == NR_FDS)
|
|
return EINVAL;
|
|
|
|
minorparent = i; /* parent */
|
|
|
|
/* We are the parent's child. */
|
|
uds_fd_table[minorparent].child = minor;
|
|
|
|
/*
|
|
* The peer has the same type as the parent. we need to be that
|
|
* type too.
|
|
*/
|
|
uds_fd_table[minor].type = uds_fd_table[minorparent].type;
|
|
|
|
/* Locate the peer to accept in the parent's backlog. */
|
|
minorpeer = -1;
|
|
for (i = 0; i < uds_fd_table[minorparent].backlog_size; i++) {
|
|
if (uds_fd_table[minorparent].backlog[i] != -1) {
|
|
minorpeer = uds_fd_table[minorparent].backlog[i];
|
|
uds_fd_table[minorparent].backlog[i] = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (minorpeer == -1) {
|
|
dprintf(("UDS: do_accept(%d): suspend\n", minor));
|
|
|
|
/*
|
|
* There are no peers in the backlog, suspend and wait for one
|
|
* to show up.
|
|
*/
|
|
uds_fd_table[minor].suspended = UDS_SUSPENDED_ACCEPT;
|
|
|
|
return EDONTREPLY;
|
|
}
|
|
|
|
dprintf(("UDS: connecting %d to %d -- parent is %d\n", minor,
|
|
minorpeer, minorparent));
|
|
|
|
if ((rc = perform_connection(minor, minorpeer, &addr)) != OK) {
|
|
dprintf(("UDS: do_accept(%d): connection failed\n", minor));
|
|
|
|
return rc;
|
|
}
|
|
|
|
uds_fd_table[minorparent].child = -1;
|
|
|
|
/* If the peer is blocked on connect() or write(), revive the peer. */
|
|
if (uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_CONNECT ||
|
|
uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_WRITE) {
|
|
dprintf(("UDS: do_accept(%d): revive %d\n", minor, minorpeer));
|
|
uds_unsuspend(minorpeer);
|
|
}
|
|
|
|
/* See if we can satisfy an ongoing select. */
|
|
if ((uds_fd_table[minorpeer].sel_ops & CDEV_OP_WR) &&
|
|
uds_fd_table[minorpeer].size < UDS_BUF) {
|
|
/* A write on the peer is possible now. */
|
|
chardriver_reply_select(uds_fd_table[minorpeer].sel_endpt,
|
|
minorpeer, CDEV_OP_WR);
|
|
uds_fd_table[minorpeer].sel_ops &= ~CDEV_OP_WR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int child, peer;
|
|
struct sockaddr_un addr;
|
|
int rc, i, j;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
|
|
dprintf(("UDS: do_connect(%d)\n", minor));
|
|
|
|
/* Only connection oriented sockets can connect. */
|
|
if (uds_fd_table[minor].type != SOCK_STREAM &&
|
|
uds_fd_table[minor].type != SOCK_SEQPACKET)
|
|
return EINVAL;
|
|
|
|
/* The socket must not be connecting or connected already. */
|
|
peer = uds_fd_table[minor].peer;
|
|
if (peer != -1) {
|
|
if (uds_fd_table[peer].peer == -1)
|
|
return EALREADY; /* connecting */
|
|
else
|
|
return EISCONN; /* connected */
|
|
}
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
|
|
sizeof(struct sockaddr_un))) != OK)
|
|
return rc;
|
|
|
|
if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path,
|
|
sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK)
|
|
return rc;
|
|
|
|
/*
|
|
* Look for a socket of the same type that is listening on the
|
|
* address we want to connect to.
|
|
*/
|
|
for (i = 0; i < NR_FDS; i++) {
|
|
if (uds_fd_table[minor].type != uds_fd_table[i].type)
|
|
continue;
|
|
if (uds_fd_table[i].listening == FALSE)
|
|
continue;
|
|
if (uds_fd_table[i].stale == TRUE)
|
|
continue;
|
|
if (uds_fd_table[i].addr.sun_family != AF_UNIX)
|
|
continue;
|
|
if (strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
|
|
sizeof(uds_fd_table[i].addr.sun_path)))
|
|
continue;
|
|
|
|
/* Found a matching socket. */
|
|
break;
|
|
}
|
|
|
|
if (i == NR_FDS)
|
|
return ECONNREFUSED;
|
|
|
|
/* If the server is blocked on an accept, perform the connection. */
|
|
if ((child = uds_fd_table[i].child) != -1) {
|
|
rc = perform_connection(minor, child, &addr);
|
|
|
|
if (rc != OK)
|
|
return rc;
|
|
|
|
uds_fd_table[i].child = -1;
|
|
|
|
dprintf(("UDS: do_connect(%d): revive %d\n", minor, child));
|
|
|
|
/* Wake up the accepting party. */
|
|
uds_unsuspend(child);
|
|
|
|
return OK;
|
|
}
|
|
|
|
dprintf(("UDS: adding %d to %d's backlog\n", minor, i));
|
|
|
|
/* Look for a free slot in the backlog. */
|
|
rc = -1;
|
|
for (j = 0; j < uds_fd_table[i].backlog_size; j++) {
|
|
if (uds_fd_table[i].backlog[j] == -1) {
|
|
uds_fd_table[i].backlog[j] = minor;
|
|
|
|
rc = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rc == -1)
|
|
return ECONNREFUSED; /* backlog is full */
|
|
|
|
/* See if the server is blocked on select(). */
|
|
if (uds_fd_table[i].sel_ops & CDEV_OP_RD) {
|
|
/* Satisfy a read-type select on the server. */
|
|
chardriver_reply_select(uds_fd_table[i].sel_endpt, i,
|
|
CDEV_OP_RD);
|
|
|
|
uds_fd_table[i].sel_ops &= ~CDEV_OP_RD;
|
|
}
|
|
|
|
/* We found our server. */
|
|
uds_fd_table[minor].peer = i;
|
|
|
|
memcpy(&uds_fd_table[minor].addr, &addr, sizeof(struct sockaddr_un));
|
|
|
|
dprintf(("UDS: do_connect(%d): suspend\n", minor));
|
|
|
|
/* Suspend until the server side accepts the connection. */
|
|
uds_fd_table[minor].suspended = UDS_SUSPENDED_CONNECT;
|
|
|
|
return EDONTREPLY;
|
|
}
|
|
|
|
static int
|
|
do_listen(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
int backlog_size;
|
|
|
|
dprintf(("UDS: do_listen(%d)\n", minor));
|
|
|
|
/* Ensure the socket has a type and is bound. */
|
|
if (uds_fd_table[minor].type == -1 ||
|
|
uds_fd_table[minor].addr.sun_family != AF_UNIX)
|
|
return EINVAL;
|
|
|
|
/* listen(2) supports only two socket types. */
|
|
if (uds_fd_table[minor].type != SOCK_STREAM &&
|
|
uds_fd_table[minor].type != SOCK_SEQPACKET)
|
|
return EOPNOTSUPP;
|
|
|
|
/*
|
|
* The POSIX standard doesn't say what to do if listen() has
|
|
* already been called. Well, there isn't an errno. We silently
|
|
* let it happen, but if listen() has already been called, we
|
|
* don't allow the backlog to shrink.
|
|
*/
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &backlog_size,
|
|
sizeof(backlog_size))) != OK)
|
|
return rc;
|
|
|
|
if (uds_fd_table[minor].listening == FALSE) {
|
|
/* Set the backlog size to a reasonable value. */
|
|
if (backlog_size <= 0 || backlog_size > UDS_SOMAXCONN)
|
|
backlog_size = UDS_SOMAXCONN;
|
|
|
|
uds_fd_table[minor].backlog_size = backlog_size;
|
|
} else {
|
|
/* Allow the user to expand the backlog size. */
|
|
if (backlog_size > uds_fd_table[minor].backlog_size &&
|
|
backlog_size < UDS_SOMAXCONN)
|
|
uds_fd_table[minor].backlog_size = backlog_size;
|
|
|
|
/*
|
|
* Don't let the user shrink the backlog_size, as we might
|
|
* have clients waiting in those slots.
|
|
*/
|
|
}
|
|
|
|
/* This socket is now listening. */
|
|
uds_fd_table[minor].listening = TRUE;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_socket(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc, type;
|
|
|
|
dprintf(("UDS: do_socket(%d)\n", minor));
|
|
|
|
/* The socket type can only be set once. */
|
|
if (uds_fd_table[minor].type != -1)
|
|
return EINVAL;
|
|
|
|
/* Get the requested type. */
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &type,
|
|
sizeof(type))) != OK)
|
|
return rc;
|
|
|
|
/* Assign the type if it is valid only. */
|
|
switch (type) {
|
|
case SOCK_STREAM:
|
|
case SOCK_DGRAM:
|
|
case SOCK_SEQPACKET:
|
|
uds_fd_table[minor].type = type;
|
|
return OK;
|
|
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
do_bind(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
struct sockaddr_un addr;
|
|
int rc, i;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
|
|
dprintf(("UDS: do_bind(%d)\n", minor));
|
|
|
|
/* If the type hasn't been set by do_socket() yet, OR an attempt
|
|
* to re-bind() a non-SOCK_DGRAM socket is made, fail the call.
|
|
*/
|
|
if ((uds_fd_table[minor].type == -1) ||
|
|
(uds_fd_table[minor].addr.sun_family == AF_UNIX &&
|
|
uds_fd_table[minor].type != SOCK_DGRAM))
|
|
return EINVAL;
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
|
|
sizeof(struct sockaddr_un))) != OK)
|
|
return rc;
|
|
|
|
/* Do some basic sanity checks on the address. */
|
|
if (addr.sun_family != AF_UNIX)
|
|
return EAFNOSUPPORT;
|
|
|
|
if (addr.sun_path[0] == '\0')
|
|
return ENOENT;
|
|
|
|
/* Attempt to create the socket file. */
|
|
if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path,
|
|
#if NOT_YET
|
|
sizeof(addr.sun_path), SPATH_CREATE, &dev, &ino)) != OK)
|
|
#else
|
|
sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK)
|
|
#endif
|
|
return rc;
|
|
|
|
/*
|
|
* It is possible that the socket path name was already in use as
|
|
* address by another socket. This means that the socket file was
|
|
* prematurely unlinked. In that case, mark the old socket as stale,
|
|
* so that its path name will not be matched and only the newly bound
|
|
* socket will be found in address-based searches. For now, we leave
|
|
* the old socket marked as stale for as long as it is bound to the
|
|
* same address. A more advanced implementation could establish an
|
|
* order between the sockets so that the most recently bound socket is
|
|
* found at any time, but it is doubtful whether that would be useful.
|
|
*/
|
|
for (i = 0; i < NR_FDS; i++) {
|
|
if (uds_fd_table[i].stale == FALSE &&
|
|
uds_fd_table[i].addr.sun_family == AF_UNIX &&
|
|
!strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
|
|
sizeof(uds_fd_table[i].addr.sun_path))) {
|
|
#if NOT_YET
|
|
uds_fd_table[i].stale = TRUE;
|
|
#else
|
|
return EADDRINUSE;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Looks good, perform the bind(). */
|
|
uds_fd_table[minor].stale = FALSE;
|
|
memcpy(&uds_fd_table[minor].addr, &addr, sizeof(struct sockaddr_un));
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_getsockname(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
dprintf(("UDS: do_getsockname(%d)\n", minor));
|
|
|
|
/*
|
|
* Unconditionally send the address we have assigned to this socket.
|
|
* The POSIX standard doesn't say what to do if the address hasn't been
|
|
* set. If the address isn't currently set, then the user will get
|
|
* NULL bytes. Note: libc depends on this behavior.
|
|
*/
|
|
return sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes) &uds_fd_table[minor].addr, sizeof(struct sockaddr_un));
|
|
}
|
|
|
|
static int
|
|
do_getpeername(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int peer_minor;
|
|
|
|
dprintf(("UDS: do_getpeername(%d)\n", minor));
|
|
|
|
/* Check that the socket is connected with a valid peer. */
|
|
if (uds_fd_table[minor].peer != -1) {
|
|
peer_minor = uds_fd_table[minor].peer;
|
|
|
|
/* Copy the address from the peer. */
|
|
return sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes) &uds_fd_table[peer_minor].addr,
|
|
sizeof(struct sockaddr_un));
|
|
} else if (uds_fd_table[minor].err == ECONNRESET) {
|
|
uds_fd_table[minor].err = 0;
|
|
|
|
return ECONNRESET;
|
|
} else
|
|
return ENOTCONN;
|
|
}
|
|
|
|
static int
|
|
do_shutdown(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc, how;
|
|
|
|
dprintf(("UDS: do_shutdown(%d)\n", minor));
|
|
|
|
/* The socket must be connection oriented. */
|
|
if (uds_fd_table[minor].type != SOCK_STREAM &&
|
|
uds_fd_table[minor].type != SOCK_SEQPACKET)
|
|
return EINVAL;
|
|
|
|
if (uds_fd_table[minor].peer == -1) {
|
|
/* shutdown(2) is only valid for connected sockets. */
|
|
if (uds_fd_table[minor].err == ECONNRESET)
|
|
return ECONNRESET;
|
|
else
|
|
return ENOTCONN;
|
|
}
|
|
|
|
/* Get the 'how' parameter from the caller. */
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &how,
|
|
sizeof(how))) != OK)
|
|
return rc;
|
|
|
|
switch (how) {
|
|
case SHUT_RD: /* Take away read permission. */
|
|
uds_fd_table[minor].mode &= ~UDS_R;
|
|
break;
|
|
|
|
case SHUT_WR: /* Take away write permission. */
|
|
uds_fd_table[minor].mode &= ~UDS_W;
|
|
break;
|
|
|
|
case SHUT_RDWR: /* Shut down completely. */
|
|
uds_fd_table[minor].mode = 0;
|
|
break;
|
|
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_socketpair(devminor_t minorx, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
dev_t minorin;
|
|
devminor_t minory;
|
|
struct sockaddr_un addr;
|
|
|
|
dprintf(("UDS: do_socketpair(%d)\n", minorx));
|
|
|
|
/* The ioctl argument is the minor number of the second socket. */
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &minorin,
|
|
sizeof(minorin))) != OK)
|
|
return rc;
|
|
|
|
minory = minor(minorin);
|
|
|
|
dprintf(("UDS: socketpair(%d, %d,)\n", minorx, minory));
|
|
|
|
/* Security check: both sockets must have the same owner endpoint. */
|
|
if (uds_fd_table[minorx].owner != uds_fd_table[minory].owner)
|
|
return EPERM;
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
addr.sun_path[0] = 'X';
|
|
addr.sun_path[1] = '\0';
|
|
|
|
return perform_connection(minorx, minory, &addr);
|
|
}
|
|
|
|
static int
|
|
do_getsockopt_sotype(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
dprintf(("UDS: do_getsockopt_sotype(%d)\n", minor));
|
|
|
|
/* If the type hasn't been set yet, we fail the call. */
|
|
if (uds_fd_table[minor].type == -1)
|
|
return EINVAL;
|
|
|
|
return sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes) &uds_fd_table[minor].type, sizeof(int));
|
|
}
|
|
|
|
static int
|
|
do_getsockopt_peercred(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int peer_minor;
|
|
int rc;
|
|
struct uucred cred;
|
|
|
|
dprintf(("UDS: do_getsockopt_peercred(%d)\n", minor));
|
|
|
|
if (uds_fd_table[minor].peer == -1) {
|
|
if (uds_fd_table[minor].err == ECONNRESET) {
|
|
uds_fd_table[minor].err = 0;
|
|
|
|
return ECONNRESET;
|
|
} else
|
|
return ENOTCONN;
|
|
}
|
|
|
|
peer_minor = uds_fd_table[minor].peer;
|
|
|
|
/*
|
|
* Obtain the peer's credentials and copy them out. Ignore failures;
|
|
* in that case, the caller will simply get no credentials.
|
|
*/
|
|
memset(&cred, 0, sizeof(cred));
|
|
cred.cr_uid = -1;
|
|
cred.cr_gid = -1;
|
|
(void)getepinfo(uds_fd_table[peer_minor].owner, &cred.cr_uid,
|
|
&cred.cr_gid);
|
|
|
|
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &cred,
|
|
sizeof(struct uucred));
|
|
}
|
|
|
|
static int
|
|
do_getsockopt_sndbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
size_t sndbuf = UDS_BUF;
|
|
|
|
dprintf(("UDS: do_getsockopt_sndbuf(%d)\n", minor));
|
|
|
|
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &sndbuf,
|
|
sizeof(sndbuf));
|
|
}
|
|
|
|
static int
|
|
do_setsockopt_sndbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
size_t sndbuf;
|
|
|
|
dprintf(("UDS: do_setsockopt_sndbuf(%d)\n", minor));
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &sndbuf,
|
|
sizeof(sndbuf))) != OK)
|
|
return rc;
|
|
|
|
/* The send buffer is limited to 32KB at the moment. */
|
|
if (sndbuf > UDS_BUF)
|
|
return ENOSYS;
|
|
|
|
/* FIXME: actually shrink the buffer. */
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_getsockopt_rcvbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
size_t rcvbuf = UDS_BUF;
|
|
|
|
dprintf(("UDS: do_getsockopt_rcvbuf(%d)\n", minor));
|
|
|
|
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rcvbuf,
|
|
sizeof(rcvbuf));
|
|
}
|
|
|
|
static int
|
|
do_setsockopt_rcvbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
size_t rcvbuf;
|
|
|
|
dprintf(("UDS: do_setsockopt_rcvbuf(%d)\n", minor));
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &rcvbuf,
|
|
sizeof(rcvbuf))) != OK)
|
|
return rc;
|
|
|
|
/* The receive buffer is limited to 32KB at the moment. */
|
|
if (rcvbuf > UDS_BUF)
|
|
return ENOSYS;
|
|
|
|
/* FIXME: actually shrink the buffer. */
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_sendto(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
struct sockaddr_un addr;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
|
|
dprintf(("UDS: do_sendto(%d)\n", minor));
|
|
|
|
/* This IOCTL is only for SOCK_DGRAM sockets. */
|
|
if (uds_fd_table[minor].type != SOCK_DGRAM)
|
|
return EINVAL;
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
|
|
sizeof(struct sockaddr_un))) != OK)
|
|
return rc;
|
|
|
|
/* Do some basic sanity checks on the address. */
|
|
if (addr.sun_family != AF_UNIX || addr.sun_path[0] == '\0')
|
|
return EINVAL;
|
|
|
|
if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path,
|
|
sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK)
|
|
return rc;
|
|
|
|
memcpy(&uds_fd_table[minor].target, &addr, sizeof(struct sockaddr_un));
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_recvfrom(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
dprintf(("UDS: do_recvfrom(%d)\n", minor));
|
|
|
|
return sys_safecopyto(endpt, grant, 0,
|
|
(vir_bytes) &uds_fd_table[minor].source,
|
|
sizeof(struct sockaddr_un));
|
|
}
|
|
|
|
static int
|
|
send_fds(devminor_t minor, struct msg_control *msg_ctrl,
|
|
struct ancillary *data)
|
|
{
|
|
int i, rc, nfds, totalfds;
|
|
endpoint_t from_ep;
|
|
struct msghdr msghdr;
|
|
struct cmsghdr *cmsg = NULL;
|
|
|
|
dprintf(("UDS: send_fds(%d)\n", minor));
|
|
|
|
from_ep = uds_fd_table[minor].owner;
|
|
|
|
/* Obtain this socket's credentials. */
|
|
if ((rc = getepinfo(from_ep, &data->cred.uid, &data->cred.gid)) < 0)
|
|
return rc;
|
|
|
|
dprintf(("UDS: minor=%d cred={%d,%d}\n", minor,
|
|
data->cred.uid, data->cred.gid));
|
|
|
|
totalfds = data->nfiledes;
|
|
|
|
memset(&msghdr, '\0', sizeof(struct msghdr));
|
|
msghdr.msg_control = msg_ctrl->msg_control;
|
|
msghdr.msg_controllen = msg_ctrl->msg_controllen;
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL;
|
|
cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
|
|
if (cmsg->cmsg_level != SOL_SOCKET ||
|
|
cmsg->cmsg_type != SCM_RIGHTS)
|
|
continue;
|
|
|
|
nfds = MIN((cmsg->cmsg_len-CMSG_LEN(0))/sizeof(int), OPEN_MAX);
|
|
|
|
for (i = 0; i < nfds; i++) {
|
|
if (totalfds == OPEN_MAX)
|
|
return EOVERFLOW;
|
|
|
|
data->fds[totalfds] = ((int *) CMSG_DATA(cmsg))[i];
|
|
dprintf(("UDS: minor=%d fd[%d]=%d\n", minor, totalfds,
|
|
data->fds[totalfds]));
|
|
totalfds++;
|
|
}
|
|
}
|
|
|
|
for (i = data->nfiledes; i < totalfds; i++) {
|
|
if ((rc = copyfd(from_ep, data->fds[i], COPYFD_FROM)) < 0) {
|
|
printf("UDS: copyfd(COPYFD_FROM) failed: %d\n", rc);
|
|
|
|
/* Revert the successful copyfd() calls made so far. */
|
|
for (i--; i >= data->nfiledes; i--)
|
|
close(data->fds[i]);
|
|
|
|
return rc;
|
|
}
|
|
|
|
dprintf(("UDS: send_fds(): %d -> %d\n", data->fds[i], rc));
|
|
|
|
data->fds[i] = rc; /* this is now the local FD */
|
|
}
|
|
|
|
data->nfiledes = totalfds;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* This function calls close() for all of the FDs in flight. This is used
|
|
* when a Unix Domain Socket is closed and there exists references to file
|
|
* descriptors that haven't been received with recvmsg().
|
|
*/
|
|
int
|
|
uds_clear_fds(devminor_t minor, struct ancillary *data)
|
|
{
|
|
int i;
|
|
|
|
dprintf(("UDS: uds_clear_fds(%d)\n", minor));
|
|
|
|
for (i = 0; i < data->nfiledes; i++) {
|
|
dprintf(("UDS: uds_clear_fds() => %d\n", data->fds[i]));
|
|
|
|
close(data->fds[i]);
|
|
|
|
data->fds[i] = -1;
|
|
}
|
|
|
|
data->nfiledes = 0;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
recv_fds(devminor_t minor, struct ancillary *data,
|
|
struct msg_control *msg_ctrl)
|
|
{
|
|
int rc, i, j, fds[OPEN_MAX];
|
|
struct msghdr msghdr;
|
|
struct cmsghdr *cmsg;
|
|
endpoint_t to_ep;
|
|
|
|
dprintf(("UDS: recv_fds(%d)\n", minor));
|
|
|
|
msghdr.msg_control = msg_ctrl->msg_control;
|
|
msghdr.msg_controllen = msg_ctrl->msg_controllen;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msghdr);
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * data->nfiledes);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
|
|
to_ep = uds_fd_table[minor].owner;
|
|
|
|
/* Copy to the target endpoint. */
|
|
for (i = 0; i < data->nfiledes; i++) {
|
|
if ((rc = copyfd(to_ep, data->fds[i], COPYFD_TO)) < 0) {
|
|
printf("UDS: copyfd(COPYFD_TO) failed: %d\n", rc);
|
|
|
|
/* Revert the successful copyfd() calls made so far. */
|
|
for (i--; i >= 0; i--)
|
|
(void) copyfd(to_ep, fds[i], COPYFD_CLOSE);
|
|
|
|
return rc;
|
|
}
|
|
|
|
fds[i] = rc; /* this is now the remote FD */
|
|
}
|
|
|
|
/* Close the local copies only once the entire procedure succeeded. */
|
|
for (i = 0; i < data->nfiledes; i++) {
|
|
dprintf(("UDS: recv_fds(): %d -> %d\n", data->fds[i], fds[i]));
|
|
|
|
((int *)CMSG_DATA(cmsg))[i] = fds[i];
|
|
|
|
close(data->fds[i]);
|
|
|
|
data->fds[i] = -1;
|
|
}
|
|
|
|
data->nfiledes = 0;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
recv_cred(devminor_t minor, struct ancillary *data,
|
|
struct msg_control *msg_ctrl)
|
|
{
|
|
struct msghdr msghdr;
|
|
struct cmsghdr *cmsg;
|
|
struct uucred *cred;
|
|
|
|
dprintf(("UDS: recv_cred(%d)\n", minor));
|
|
|
|
msghdr.msg_control = msg_ctrl->msg_control;
|
|
msghdr.msg_controllen = msg_ctrl->msg_controllen;
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msghdr);
|
|
if (cmsg->cmsg_len > 0)
|
|
cmsg = CMSG_NXTHDR(&msghdr, cmsg);
|
|
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct uucred));
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_CREDS;
|
|
cred = (struct uucred *)CMSG_DATA(cmsg);
|
|
memset(cred, 0, sizeof(*cred));
|
|
cred->cr_uid = data->cred.uid;
|
|
cred->cr_gid = data->cred.gid;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
do_sendmsg(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int peer, rc, i;
|
|
struct msg_control msg_ctrl;
|
|
|
|
dprintf(("UDS: do_sendmsg(%d)\n", minor));
|
|
|
|
memset(&msg_ctrl, '\0', sizeof(struct msg_control));
|
|
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &msg_ctrl,
|
|
sizeof(struct msg_control))) != OK)
|
|
return rc;
|
|
|
|
/* Locate the peer. */
|
|
peer = -1;
|
|
if (uds_fd_table[minor].type == SOCK_DGRAM) {
|
|
if (uds_fd_table[minor].target.sun_path[0] == '\0' ||
|
|
uds_fd_table[minor].target.sun_family != AF_UNIX)
|
|
return EDESTADDRREQ;
|
|
|
|
for (i = 0; i < NR_FDS; i++) {
|
|
/*
|
|
* Look for a SOCK_DGRAM socket that is bound on the
|
|
* target address.
|
|
*/
|
|
if (uds_fd_table[i].type == SOCK_DGRAM &&
|
|
uds_fd_table[i].stale == FALSE &&
|
|
uds_fd_table[i].addr.sun_family == AF_UNIX &&
|
|
!strncmp(uds_fd_table[minor].target.sun_path,
|
|
uds_fd_table[i].addr.sun_path,
|
|
sizeof(uds_fd_table[i].addr.sun_path))) {
|
|
peer = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (peer == -1)
|
|
return ENOENT;
|
|
} else {
|
|
peer = uds_fd_table[minor].peer;
|
|
if (peer == -1)
|
|
return ENOTCONN;
|
|
}
|
|
|
|
dprintf(("UDS: sendmsg(%d) -- peer=%d\n", minor, peer));
|
|
|
|
/*
|
|
* Note: it's possible that there is already some file descriptors in
|
|
* ancillary_data if the peer didn't call recvmsg() yet. That's okay.
|
|
* The receiver will get the current file descriptors plus the new
|
|
* ones.
|
|
*/
|
|
return send_fds(minor, &msg_ctrl, &uds_fd_table[peer].ancillary_data);
|
|
}
|
|
|
|
static int
|
|
do_recvmsg(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
struct msg_control msg_ctrl;
|
|
socklen_t clen_avail = 0;
|
|
socklen_t clen_needed = 0;
|
|
socklen_t clen_desired = 0;
|
|
|
|
dprintf(("UDS: do_recvmsg(%d)\n", minor));
|
|
dprintf(("UDS: minor=%d credentials={uid:%d,gid:%d}\n", minor,
|
|
uds_fd_table[minor].ancillary_data.cred.uid,
|
|
uds_fd_table[minor].ancillary_data.cred.gid));
|
|
|
|
memset(&msg_ctrl, '\0', sizeof(struct msg_control));
|
|
|
|
/*
|
|
* Get the msg_control from the user. It will include the
|
|
* amount of space the user has allocated for control data.
|
|
*/
|
|
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &msg_ctrl,
|
|
sizeof(struct msg_control))) != OK)
|
|
return rc;
|
|
|
|
clen_avail = MIN(msg_ctrl.msg_controllen, MSG_CONTROL_MAX);
|
|
|
|
if (uds_fd_table[minor].ancillary_data.nfiledes > 0) {
|
|
clen_needed = CMSG_SPACE(sizeof(int) *
|
|
uds_fd_table[minor].ancillary_data.nfiledes);
|
|
}
|
|
|
|
/* if there is room we also include credentials */
|
|
clen_desired = clen_needed + CMSG_SPACE(sizeof(struct uucred));
|
|
|
|
if (clen_needed > clen_avail)
|
|
return EOVERFLOW;
|
|
|
|
if (uds_fd_table[minor].ancillary_data.nfiledes > 0) {
|
|
if ((rc = recv_fds(minor, &uds_fd_table[minor].ancillary_data,
|
|
&msg_ctrl)) != OK)
|
|
return rc;
|
|
}
|
|
|
|
if (clen_desired <= clen_avail) {
|
|
rc = recv_cred(minor, &uds_fd_table[minor].ancillary_data,
|
|
&msg_ctrl);
|
|
if (rc != OK)
|
|
return rc;
|
|
msg_ctrl.msg_controllen = clen_desired;
|
|
} else
|
|
msg_ctrl.msg_controllen = clen_needed;
|
|
|
|
/* Send the control data to the user. */
|
|
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &msg_ctrl,
|
|
sizeof(struct msg_control));
|
|
}
|
|
|
|
static int
|
|
do_fionread(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
|
|
rc = uds_perform_read(minor, NONE, GRANT_INVALID, UDS_BUF, 1);
|
|
|
|
/* What should we do on error? Just set to zero for now. */
|
|
if (rc < 0)
|
|
rc = 0;
|
|
|
|
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rc, sizeof(rc));
|
|
}
|
|
|
|
int
|
|
uds_do_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt,
|
|
cp_grant_id_t grant)
|
|
{
|
|
int rc;
|
|
|
|
switch (request) {
|
|
case NWIOSUDSCONN:
|
|
/* Connect to a listening socket -- connect(). */
|
|
rc = do_connect(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSACCEPT:
|
|
/* Accept an incoming connection -- accept(). */
|
|
rc = do_accept(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSBLOG:
|
|
/*
|
|
* Set the backlog_size and put the socket into the listening
|
|
* state -- listen().
|
|
*/
|
|
rc = do_listen(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSTYPE:
|
|
/* Set the SOCK_ type for this socket -- socket(). */
|
|
rc = do_socket(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSADDR:
|
|
/* Set the address for this socket -- bind(). */
|
|
rc = do_bind(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSADDR:
|
|
/* Get the address for this socket -- getsockname(). */
|
|
rc = do_getsockname(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSPADDR:
|
|
/* Get the address for the peer -- getpeername(). */
|
|
rc = do_getpeername(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSSHUT:
|
|
/*
|
|
* Shut down a socket for reading, writing, or both --
|
|
* shutdown().
|
|
*/
|
|
rc = do_shutdown(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSPAIR:
|
|
/* Connect two sockets -- socketpair(). */
|
|
rc = do_socketpair(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSSOTYPE:
|
|
/* Get socket type -- getsockopt(SO_TYPE). */
|
|
rc = do_getsockopt_sotype(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSPEERCRED:
|
|
/* Get peer endpoint -- getsockopt(SO_PEERCRED). */
|
|
rc = do_getsockopt_peercred(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSTADDR:
|
|
/* Set target address -- sendto(). */
|
|
rc = do_sendto(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSFADDR:
|
|
/* Get from address -- recvfrom(). */
|
|
rc = do_recvfrom(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSSNDBUF:
|
|
/* Get the send buffer size -- getsockopt(SO_SNDBUF). */
|
|
rc = do_getsockopt_sndbuf(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSSNDBUF:
|
|
/* Set the send buffer size -- setsockopt(SO_SNDBUF). */
|
|
rc = do_setsockopt_sndbuf(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSRCVBUF:
|
|
/* Get the send buffer size -- getsockopt(SO_SNDBUF). */
|
|
rc = do_getsockopt_rcvbuf(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSRCVBUF:
|
|
/* Set the send buffer size -- setsockopt(SO_SNDBUF). */
|
|
rc = do_setsockopt_rcvbuf(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOSUDSCTRL:
|
|
/* Set the control data -- sendmsg(). */
|
|
rc = do_sendmsg(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case NWIOGUDSCTRL:
|
|
/* Set the control data -- recvmsg(). */
|
|
rc = do_recvmsg(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
case FIONREAD:
|
|
/*
|
|
* Get the number of bytes immediately available for reading.
|
|
*/
|
|
rc = do_fionread(minor, endpt, grant);
|
|
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* The IOCTL command is not valid for /dev/uds -- this happens
|
|
* a lot and is normal. A lot of libc functions determine the
|
|
* socket type with IOCTLs. Any unrecognized requests simply
|
|
* get an ENOTTY response.
|
|
*/
|
|
|
|
rc = ENOTTY;
|
|
}
|
|
|
|
return rc;
|
|
}
|