--- /dev/null
+/*
+ * compat: getpeereid
+ * SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (c) 2006-2026 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifdef __sun
+#include <ucred.h>
+#endif
+
+#include "getpeereid.h"
+
+int
+getpeereid(int fd, uid_t *uid, gid_t *gid)
+{
+#if defined(SO_PEERCRED)
+ struct ucred creds;
+ socklen_t creds_len = sizeof(creds);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_len) == -1)
+ return -1;
+
+ *uid = creds.uid;
+ *gid = creds.gid;
+#elif defined(__sun)
+ ucred_t *ucred = NULL;
+
+ if (getpeerucred(fd, &ucred) == -1)
+ return -1;
+
+ *uid = ucred_geteuid(ucred);
+ *gid = ucred_getegid(ucred);
+ ucred_free(ucred);
+#else
+#error OS has no getpeereid support!
+#endif
+
+ return 0;
+}
--- /dev/null
+/*
+ * compat: getpeereid
+ * SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (c) 2006-2026 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef GETPEEREID_H
+#define GETPEEREID_H
+
+#include <sys/types.h>
+
+int getpeereid(int, uid_t *, gid_t *);
+
+#endif
echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H
fi
+if [ -z "$GETPEEREID" ]; then
+ printf "Testing for getpeereid ... "
+ cat <<EOF >_getpeereid.c
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+int main(void) {
+ return getpeereid(0, NULL, NULL);
+}
+EOF
+ if $XCC _getpeereid.c -o _getpeereid 2>&3; then
+ GETPEEREID=yes
+ else
+ GETPEEREID=no
+ fi
+ echo "$GETPEEREID"
+ rm -rf _getpeereid.* _getpeereid
+fi
+if [ "$GETPEEREID" = no ]; then
+ echo "COMPAT_SRCS+= compat/getpeereid.c" >>$CONFIG_MK
+ echo "#include \"compat/getpeereid.h\"" >>$CONFIG_H
+fi
+
if [ -z "$MEMSET_EXPLICIT" ]; then
printf "Testing for memset_explicit ... "
cat <<EOF >_memset_explicit.c
#include <errno.h>
#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#endif
+#define SUN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+#define LISTEN_BACKLOG 5
+
static void control_handle_data(void *, unsigned short);
static void
void
control_free(struct fd_list *fd)
{
-#ifdef PRIVSEP
- if (fd->ctx->ps_control_client == fd)
- fd->ctx->ps_control_client = NULL;
-#endif
-
eloop_event_delete(fd->ctx->eloop, fd->fd);
close(fd->fd);
TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
static void
control_hangup(struct fd_list *fd)
{
-#ifdef PRIVSEP
- if (IN_PRIVSEP(fd->ctx)) {
- if (ps_ctl_sendeof(fd) == -1)
- logerr(__func__);
- }
-#endif
control_free(fd);
}
-static int
+static ssize_t
control_handle_read(struct fd_list *fd)
{
- char buffer[1024];
- ssize_t bytes;
-
- bytes = read(fd->fd, buffer, sizeof(buffer) - 1);
+ uid_t uid;
+ gid_t gid, in_gid;
+ char buf[BUFSIZ];
+ struct iovec iov[] = { {
+ .iov_base = buf,
+ .iov_len = sizeof(buf),
+ } };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = __arraycount(iov),
+ };
+ ssize_t bytes, err;
+
+ bytes = recvmsg(fd->fd, &msg, 0);
if (bytes == -1)
logerr(__func__);
if (bytes == -1 || bytes == 0)
- return (int)bytes;
+ return bytes;
+
+ if (getpeereid(fd->fd, &uid, &gid) == -1) {
+ logerr("%s: getpeereid", __func__);
+ return -1;
+ }
#ifdef PRIVSEP
if (IN_PRIVSEP(fd->ctx)) {
- ssize_t err;
-
- fd->flags |= FD_SENDLEN;
- err = ps_ctl_handleargs(fd, buffer, (size_t)bytes);
- fd->flags &= ~FD_SENDLEN;
+ in_gid = fd->ctx->control_group;
+ if (uid == 0)
+ err = 1;
+ else
+ err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid);
+ if (err == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ if (err == 0) {
+ fd->flags &= ~FD_CONTROL;
+ in_gid = fd->ctx->read_group;
+ err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid);
+ if (err == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ if (err == 0)
+ fd->flags &= ~FD_READ;
+ else
+ fd->flags |= FD_READ;
+ } else
+ fd->flags |= FD_CONTROL | FD_READ;
+
+ err = ps_ctl_handleargs(fd, buf, (size_t)bytes);
if (err == -1) {
logerr(__func__);
return 0;
}
- if (err == 1 &&
- ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) {
+
+ if (fd->flags & FD_LISTEN)
+ fd->flags &= ~FD_COMMAND;
+ else
+ fd->flags |= FD_COMMAND;
+
+ iov[0].iov_len = (size_t)bytes;
+ /* If ps_ctl_handleargs returns 0 that means it didn't
+ * do anything with the command, so pass it to the manager. */
+ if (err == 0 && ps_ctl_sendmsg(fd, &msg) == -1) {
logerr(__func__);
return -1;
}
}
#endif
- return control_recvdata(fd, buffer, (size_t)bytes);
+ in_gid = fd->ctx->control_group;
+ err = control_user_ingroup(uid, gid, in_gid);
+ if (err == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ if (err == 0) {
+ fd->flags &= ~FD_CONTROL;
+ in_gid = fd->ctx->read_group;
+ err = control_user_ingroup(uid, gid, in_gid);
+ if (err == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ if (err == 0)
+ fd->flags &= ~FD_READ;
+ else
+ fd->flags |= FD_READ;
+ } else
+ fd->flags |= FD_CONTROL | FD_READ;
+ return control_recvmsg(fd, &msg, (size_t)bytes);
}
-static int
+static ssize_t
control_handle_write(struct fd_list *fd)
{
- struct iovec iov[2];
- int iov_len;
+ struct iovec iov[4];
+ struct msghdr msg = { .msg_iov = iov };
struct fd_data *data;
+ ssize_t len;
+#ifdef PRIVSEP
+ size_t peer_len;
+#endif
data = TAILQ_FIRST(&fd->queue);
- if (data->data_flags & FD_SENDLEN) {
- iov[0].iov_base = &data->data_len;
- iov[0].iov_len = sizeof(size_t);
- iov[1].iov_base = data->data;
- iov[1].iov_len = data->data_len;
- iov_len = 2;
- } else {
- iov[0].iov_base = data->data;
- iov[0].iov_len = data->data_len;
- iov_len = 1;
+#ifdef PRIVSEP
+ if (data->data_peer_id != 0) {
+ /* Control messages for a peer are prefixed with it's fd
+ * fd and message length. */
+ iov[msg.msg_iovlen].iov_base = &data->data_peer_id;
+ iov[msg.msg_iovlen].iov_len = sizeof(data->data_peer_id);
+ msg.msg_iovlen++;
+
+ peer_len = data->data_len;
+ if (data->data_flags & FD_DATA_SENDLEN)
+ peer_len += sizeof(data->data_len);
+ iov[msg.msg_iovlen].iov_base = &peer_len;
+ iov[msg.msg_iovlen].iov_len = sizeof(peer_len);
+ msg.msg_iovlen++;
+ }
+#endif
+
+ if (data->data_flags & FD_DATA_SENDLEN) {
+ iov[msg.msg_iovlen].iov_base = &data->data_len;
+ iov[msg.msg_iovlen].iov_len = sizeof(data->data_len);
+ msg.msg_iovlen++;
}
- if (writev(fd->fd, iov, iov_len) == -1) {
+ iov[msg.msg_iovlen].iov_base = data->data;
+ iov[msg.msg_iovlen].iov_len = data->data_len;
+ msg.msg_iovlen++;
+
+ len = sendmsg(fd->fd, &msg, 0);
+ if (len == -1) {
if (errno != EPIPE && errno != ENOTCONN) {
// We don't get ELE_HANGUP for some reason
logerr("%s: write", __func__);
#endif
if (TAILQ_FIRST(&fd->queue) != NULL)
- return 0;
-
-#ifdef PRIVSEP
- if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) {
- if (ps_ctl_sendeof(fd) == -1)
- logerr(__func__);
- }
-#endif
+ return len;
/* Done sending data, stop watching write to fd */
if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ,
control_handle_data, fd) == -1)
logerr("%s: eloop_event_add", __func__);
- return 0;
+ return len;
}
static void
control_handle_data(void *arg, unsigned short events)
{
struct fd_list *fd = arg;
- int err;
+ ssize_t err;
+
+ if (events & ELE_HANGUP)
+ goto hangup;
if (!(events & (ELE_READ | ELE_WRITE | ELE_HANGUP)))
logerrx("%s: unexpected event 0x%04x", __func__, events);
}
if (events & ELE_READ) {
err = control_handle_read(fd);
- if (err == -1 || err == 0)
+ if ((err == -1 && errno != EPERM) || err == 0)
goto hangup;
}
- if (events & ELE_HANGUP)
- goto hangup;
return;
}
int
-control_recvdata(struct fd_list *fd, char *data, size_t len)
+control_recvmsg(struct fd_list *fd, struct msghdr *msg, size_t len)
{
- char *p = data, *e;
+ struct iovec *iov;
+ char *p, *e;
char *argvp[255], **ap;
int argc;
+ if (msg->msg_iovlen == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ iov = msg->msg_iov;
+ p = (char *)iov->iov_base;
+
/* Each command is \n terminated
* Each argument is NULL separated */
while (len != 0) {
}
*ap = NULL;
if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) {
- logerr(__func__);
- if (errno != EINTR && errno != EAGAIN)
+ logerr("%s: dhcpcd_handleargs", __func__);
+ if (errno != EINTR && errno != EAGAIN && errno != EPERM)
return -1;
}
+ if (!(fd->flags & FD_LISTEN))
+ fd->flags |= FD_COMMAND;
}
return 1;
}
+struct fd_list *
+control_find(struct dhcpcd_ctx *ctx, int fd)
+{
+ struct fd_list *l;
+
+ TAILQ_FOREACH(l, &ctx->control_fds, next) {
+ if (l->fd == fd)
+ return l;
+ }
+
+ errno = ESRCH;
+ return NULL;
+}
+
struct fd_list *
control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags)
{
struct fd_list *l;
+ size_t cnt;
+#ifdef PRIVSEP
+ struct fd_list *n;
+ unsigned int id;
+#endif
+
+ l = control_find(ctx, fd);
+ if (l != NULL) {
+ l->flags = flags;
+ return l;
+ }
+
+#ifdef PRIVSEP
+again:
+ id = ++ctx->ps_control_id;
+ if (id == 0) /* wrapped */
+ id = ++ctx->ps_control_id;
+#endif
+ cnt = 0;
+ TAILQ_FOREACH(n, &ctx->control_fds, next) {
+ if (++cnt >= CONTROL_PEER_MAX) {
+ errno = ENOBUFS;
+ return NULL;
+ }
+#ifdef PRIVSEP
+ if (n->id == id)
+ goto again;
+#endif
+ }
l = malloc(sizeof(*l));
if (l == NULL)
TAILQ_INIT(&l->queue);
#ifdef CTL_FREE_LIST
TAILQ_INIT(&l->free_queue);
+#endif
+#ifdef PRIVSEP
+ l->peer_id = 0;
+ l->id = id;
#endif
TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
return l;
struct sockaddr_un run;
socklen_t len;
struct fd_list *l;
- int fd, flags;
+ int fd, flags = 1;
if (events != ELE_READ)
logerrx("%s: unexpected event 0x%04x", __func__, events);
fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
goto error;
-#ifdef PRIVSEP
- if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx))
- ;
- else
-#endif
- fd_flags |= FD_SENDLEN;
-
l = control_new(ctx, fd, fd_flags);
if (l == NULL)
goto error;
if (eloop_event_add(ctx->eloop, l->fd, ELE_READ, control_handle_data,
l) == -1)
logerr("%s: eloop_event_add", __func__);
+
return;
error:
control_handle1(ctx, ctx->control_fd, 0, events);
}
-static void
-control_handle_unpriv(void *arg, unsigned short events)
-{
- struct dhcpcd_ctx *ctx = arg;
-
- control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV, events);
-}
-
static int
-make_path(char *path, size_t len, const char *ifname, sa_family_t family,
- bool unpriv)
+make_path(char *path, size_t len, const char *ifname, sa_family_t family)
{
const char *per;
- const char *sunpriv;
switch (family) {
case AF_INET:
per = "";
break;
}
- if (unpriv)
- sunpriv = ifname ? ".unpriv" : "unpriv.";
- else
- sunpriv = "";
return snprintf(path, len, CONTROLSOCKET, ifname ? ifname : "",
- ifname ? per : "", sunpriv, ifname ? "." : "");
+ ifname ? per : "", "", ifname ? "." : "");
}
static int
-make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family,
- bool unpriv)
+make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family)
{
int fd;
- if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1)
+ if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1)
return -1;
memset(sa, 0, sizeof(*sa));
sa->sun_family = AF_UNIX;
- make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family, unpriv);
+ make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family);
return fd;
}
-#define S_PRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
-#define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
-
static int
-control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family,
- mode_t fmode)
+control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family)
{
struct sockaddr_un sa;
int fd;
socklen_t len;
- fd = make_sock(&sa, ifname, family, (fmode & S_UNPRIV) == S_UNPRIV);
+ fd = make_sock(&sa, ifname, family);
if (fd == -1)
return -1;
len = (socklen_t)SUN_LEN(&sa);
unlink(sa.sun_path);
if (bind(fd, (struct sockaddr *)&sa, len) == -1 ||
- chmod(sa.sun_path, fmode) == -1 ||
- (ctx->control_group &&
- chown(sa.sun_path, geteuid(), ctx->control_group) == -1) ||
- listen(fd, sizeof(ctx->control_fds)) == -1) {
- close(fd);
- unlink(sa.sun_path);
- return -1;
- }
+ chmod(sa.sun_path, SUN_MODE) == -1 ||
+ listen(fd, LISTEN_BACKLOG) == -1)
+ goto err;
#ifdef PRIVSEP_RIGHTS
- if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) {
- close(fd);
- unlink(sa.sun_path);
- return -1;
- }
+ if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1)
+ goto err;
#endif
- if ((fmode & S_UNPRIV) == S_UNPRIV)
- strlcpy(ctx->control_sock_unpriv, sa.sun_path,
- sizeof(ctx->control_sock_unpriv));
- else
- strlcpy(ctx->control_sock, sa.sun_path,
- sizeof(ctx->control_sock));
+ strlcpy(ctx->control_sock, sa.sun_path, sizeof(ctx->control_sock));
return fd;
+
+err:
+ close(fd);
+ unlink(sa.sun_path);
+ return -1;
}
int
#ifdef PRIVSEP
if (IN_PRIVSEP_SE(ctx)) {
make_path(ctx->control_sock, sizeof(ctx->control_sock), ifname,
- family, false);
- make_path(ctx->control_sock_unpriv,
- sizeof(ctx->control_sock_unpriv), ifname, family, true);
+ family);
return 0;
}
#endif
- if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1)
+ if ((fd = control_start1(ctx, ifname, family)) == -1)
return -1;
ctx->control_fd = fd;
-1)
logerr("%s: eloop_event_add", __func__);
- if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) {
- ctx->control_unpriv_fd = fd;
- if (eloop_event_add(ctx->eloop, fd, ELE_READ,
- control_handle_unpriv, ctx) == -1)
- logerr("%s: eloop_event_add", __func__);
- }
return ctx->control_fd;
}
if (ctx->control_sock[0] != '\0' &&
ps_root_unlink(ctx, ctx->control_sock) == -1)
retval = -1;
- if (ctx->control_sock_unpriv[0] != '\0' &&
- ps_root_unlink(ctx, ctx->control_sock_unpriv) == -1)
- retval = -1;
return retval;
} else if (ctx->options & DHCPCD_FORKED)
return retval;
retval = -1;
}
- if (ctx->control_unpriv_fd != -1) {
- eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd);
- close(ctx->control_unpriv_fd);
- ctx->control_unpriv_fd = -1;
- if (control_unlink(ctx, ctx->control_sock_unpriv) == -1)
- retval = -1;
- }
-
return retval;
}
int
-control_open(const char *ifname, sa_family_t family, bool unpriv)
+control_open(const char *ifname, sa_family_t family)
{
struct sockaddr_un sa;
int fd;
- if ((fd = make_sock(&sa, ifname, family, unpriv)) != -1) {
+ if ((fd = make_sock(&sa, ifname, family)) != -1) {
socklen_t len;
len = (socklen_t)SUN_LEN(&sa);
return write(ctx->control_fd, buffer, len);
}
-int
-control_queue(struct fd_list *fd, void *data, size_t data_len)
+ssize_t
+control_queuef(struct fd_list *fd, const void *data, size_t data_len,
+ unsigned int flags)
{
struct fd_data *d;
unsigned short events;
}
memcpy(d->data, data, data_len);
d->data_len = data_len;
- d->data_flags = fd->flags & FD_SENDLEN;
+ d->data_flags = flags;
+#ifdef PRIVSEP
+ d->data_peer_id = fd->peer_id;
+#endif
TAILQ_INSERT_TAIL(&fd->queue, d, next);
events = ELE_WRITE;
if (fd->flags & FD_LISTEN)
events |= ELE_READ;
- return eloop_event_add(fd->ctx->eloop, fd->fd, events,
- control_handle_data, fd);
+ if (eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data,
+ fd) == -1)
+ return -1;
+ return (ssize_t)d->data_len;
+}
+
+ssize_t
+control_queue(struct fd_list *fd, const void *data, size_t data_len)
+{
+ return control_queuef(fd, data, data_len, FD_DATA_SENDLEN);
+}
+
+int
+control_user_ingroup(uid_t uid, gid_t gid, gid_t grpid)
+{
+#ifdef __APPLE__
+ /* why is getgrouplist API is different ..... */
+ int *groups = NULL, *gp;
+#define GID (int)
+#else
+ gid_t *groups = NULL, *gp;
+#define GID
+#endif
+ struct passwd *pw;
+ int ngroups = 10, err = -1;
+
+ pw = getpwuid(uid);
+ if (pw == NULL)
+ return 0; /* unknown user is not privileged */
+
+ groups = reallocarray(groups, (size_t)ngroups, sizeof(*groups));
+ if (groups == NULL)
+ return -1;
+
+ if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1) {
+ gp = reallocarray(groups, (size_t)ngroups, sizeof(*groups));
+ if (gp == NULL)
+ goto out;
+ groups = gp;
+ if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1)
+ goto out;
+ }
+
+ for (gp = groups; ngroups != 0; ngroups--, gp++) {
+ if (*gp == GID grpid) {
+ err = 1;
+ goto out;
+ }
+ }
+ err = 0;
+
+out:
+ free(groups);
+ return err;
+}
+
+ssize_t
+control_handle_listen(struct fd_list *fd)
+{
+ int err;
+
+ if (fd->flags & FD_READ) {
+ err = 0;
+ if (!(fd->flags & FD_COMMAND))
+ fd->flags |= FD_LISTEN;
+ } else {
+ err = errno = EPERM;
+ logwarn(__func__);
+ }
+ return control_queuef(fd, &err, sizeof(err), 0);
}
#undef CTL_FREE_LIST
#endif
+/* Maximum number of control socket users */
+#define CONTROL_PEER_MAX 10
+
/* Limit queue size per fd */
#define CONTROL_QUEUE_MAX 100
size_t data_size;
size_t data_len;
unsigned int data_flags;
+#define FD_DATA_SENDLEN 0x01
+#ifdef PRIVSEP
+ unsigned int data_peer_id;
+#endif
};
TAILQ_HEAD(fd_data_head, fd_data);
#ifdef CTL_FREE_LIST
struct fd_data_head free_queue;
#endif
+#ifdef PRIVSEP
+ unsigned int id;
+ unsigned int peer_id;
+#endif
};
TAILQ_HEAD(fd_list_head, fd_list);
-#define FD_LISTEN 0x01U
-#define FD_UNPRIV 0x02U
-#define FD_SENDLEN 0x04U
+#define FD_READ 0x01U
+#define FD_CONTROL 0x02U
+#define FD_COMMAND 0x04U
+#define FD_LISTEN 0x08U
int control_start(struct dhcpcd_ctx *, const char *, sa_family_t);
int control_stop(struct dhcpcd_ctx *);
-int control_open(const char *, sa_family_t, bool);
+int control_open(const char *, sa_family_t);
ssize_t control_send(struct dhcpcd_ctx *, int, char *const *);
+struct fd_list *control_find(struct dhcpcd_ctx *, int);
struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int);
void control_free(struct fd_list *);
void control_delete(struct fd_list *);
-int control_queue(struct fd_list *, void *, size_t);
-int control_recvdata(struct fd_list *fd, char *, size_t);
+ssize_t control_queuef(struct fd_list *, const void *, size_t, unsigned int);
+ssize_t control_queue(struct fd_list *, const void *, size_t);
+int control_recvmsg(struct fd_list *, struct msghdr *, size_t);
+int control_user_ingroup(uid_t, gid_t, gid_t);
+ssize_t control_handle_listen(struct fd_list *);
#endif
#define DEFS_H
#define PACKAGE "dhcpcd"
-#define VERSION "10.3.2"
+#define VERSION "10.5.0"
#ifndef PRIVSEP_USER
#define PRIVSEP_USER "_" PACKAGE
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 8, 2025
+.Dd July 5, 2026
.Dt DHCPCD 8
.Os
.Sh NAME
.Ar interface .
.It Pa @RUNDIR@/sock
Control socket to the manager daemon.
-.It Pa @RUNDIR@/unpriv.sock
-Unprivileged socket to the manager daemon, only allows state retrieval.
.It Pa @RUNDIR@/ Ns Ar interface Ns .sock
Control socket to per interface daemon.
-.It Pa @RUNDIR@/ Ns Ar interface Ns .unpriv.sock
-Unprivileged socket to per interface daemon, only allows state retrieval.
.El
.Sh SEE ALSO
.Xr fnmatch 3 ,
}
#endif
-int
+ssize_t
dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc,
char **argv)
{
struct interface *ifp;
struct if_options *ifo;
- unsigned long long opts, orig_opts;
- int opt, oi, oifind, af = AF_UNSPEC;
- bool do_reboot, do_renew;
+ unsigned long long opts = 0, orig_opts;
+ int opt, oi, oifind, af = AF_UNSPEC, error;
+ bool do_getifs = false, do_reboot = false, do_renew = false;
size_t len, l, nifaces;
char *tmp, *p;
+ optind = 0;
+
/* Special commands for our control socket
* as the other end should be blocking until it gets the
* expected reply we should be safely able just to change the
* write callback on the fd */
/* Make any change here in privsep-control.c as well. */
if (strcmp(*argv, "--version") == 0) {
- return control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1);
+ return control_queue(fd, VERSION, strlen(VERSION) + 1);
} else if (strcmp(*argv, "--getconfigfile") == 0) {
- return control_queue(fd, UNCONST(fd->ctx->cffile),
+ return control_queue(fd, fd->ctx->cffile,
strlen(fd->ctx->cffile) + 1);
} else if (strcmp(*argv, "--getinterfaces") == 0) {
oifind = argc = 0;
- goto dumplease;
+ opts |= DHCPCD_DUMPLEASE;
+ do_getifs = true;
+ goto doauth;
+ } else if (strcmp(*argv, "--isprivileged") == 0) {
+ const char *ret = fd->flags & FD_CONTROL ? "true" : "false";
+ return control_queue(fd, ret, strlen(ret) + 1);
} else if (strcmp(*argv, "--listen") == 0) {
- fd->flags |= FD_LISTEN;
- return 0;
+ return control_handle_listen(fd);
}
/* Log the command */
loginfox("control command: %s", tmp);
free(tmp);
- optind = 0;
oi = 0;
- opts = 0;
do_reboot = do_renew = false;
while (
(opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) {
}
}
- /* store the index; the optind will change when a getopt get called */
+ /* If we are not dumping the lease we must be able to control
+ * dhcpcd */
+doauth:
+ if (opts & DHCPCD_DUMPLEASE) {
+ if (fd->flags & FD_READ)
+ error = 0;
+ else
+ error = EPERM;
+ } else {
+ if (fd->flags & FD_CONTROL)
+ error = 0;
+ else
+ error = EPERM;
+ }
+
+ /* store the index; the optind will change when a getopt get
+ * called */
oifind = optind;
if (opts & DHCPCD_DUMPLEASE) {
- ctx->options |= DHCPCD_DUMPLEASE;
- dumplease:
+ if (control_queuef(fd, &error, sizeof(error), 0) == -1) {
+ logerr("%s: control_queuef", __func__);
+ return -1;
+ }
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+ if (!do_getifs)
+ ctx->options |= DHCPCD_DUMPLEASE;
nifaces = 0;
TAILQ_FOREACH(ifp, ctx->ifaces, next) {
if (!ifp->active)
}
if (oifind == argc || oi < argc) {
opt = send_interface(NULL, ifp, af);
- if (opt == -1)
- goto dumperr;
+ if (opt == -1) {
+ nifaces = 0;
+ error = errno;
+ goto send_nifs;
+ }
nifaces += (size_t)opt;
}
}
- if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces))
+ send_nifs:
+ if (control_queuef(fd, &nifaces, sizeof(nifaces), 0) == -1)
+ goto dumperr;
+ if (error != 0) {
+ errno = error;
goto dumperr;
+ }
TAILQ_FOREACH(ifp, ctx->ifaces, next) {
if (!ifp->active)
continue;
return 0;
dumperr:
ctx->options &= ~DHCPCD_DUMPLEASE;
- return -1;
+ error = errno;
+ goto out;
}
- /* Only privileged users can control dhcpcd via the socket. */
- if (fd->flags & FD_UNPRIV) {
- errno = EPERM;
- return -1;
- }
+ if (error != 0)
+ goto out;
if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) {
if (oifind == argc && af == AF_UNSPEC) {
ctx->options |= DHCPCD_EXITING;
if (stop_all_interfaces(ctx, opts) == false)
eloop_exit(ctx->eloop, EXIT_SUCCESS);
- /* We did stop an interface, it will notify us once
- * dropped so we can exit. */
- return 0;
+ /* We did stop an interface, it will notify us
+ * once dropped so we can exit. */
+ goto out;
}
TAILQ_FOREACH(ifp, ctx->ifaces, next) {
stop_interface(ifp);
ifo->options = orig_opts;
}
- return 0;
+ goto out;
}
if (do_renew) {
if (oifind == argc) {
dhcpcd_renew(ctx);
- return 0;
+ goto out;
}
for (oi = oifind; oi < argc; oi++) {
if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL)
continue;
dhcpcd_ifrenew(ifp);
}
- return 0;
+ goto out;
}
reload_config(ctx);
reconf_reboot(ctx, do_reboot, argc, argv, oifind);
+
+out:
+ if (control_queuef(fd, &error, sizeof(error), 0) == -1) {
+ logerr("%s: control_queuef", __func__);
+ error = errno;
+ }
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
return 0;
}
eloop_exit(ctx->eloop, EXIT_FAILURE);
}
+static int
+dhcpcd_readerror(struct dhcpcd_ctx *ctx)
+{
+ int error = 0;
+ ssize_t len;
+
+ len = read(ctx->control_fd, &error, sizeof(error));
+ if (len == -1)
+ return -1;
+ if (len != sizeof(error)) {
+ errno = EINVAL;
+ return -1;
+ }
+ errno = error;
+ return error;
+}
+
static void
dhcpcd_readdump0(void *arg, unsigned short events)
{
ifo = NULL;
ctx.cffile = CONFIG;
ctx.script = UNCONST(dhcpcd_default_script);
- ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1;
+ ctx.control_fd = ctx.link_fd = -1;
ctx.pf_inet_fd = -1;
#ifdef PF_LINK
ctx.pf_link_fd = -1;
if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) {
printpidfile:
- /* If we have any other args, we should run as a single dhcpcd
- * instance for that interface. */
+ /* If we have any other args, we should run as a single
+ * dhcpcd instance for that interface. */
if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) {
const char *per;
const char *ifname;
ctx.options |= DHCPCD_MANAGER;
/*
- * If we are given any interfaces or a family, we
- * cannot send a signal as that would impact
+ * If we are given any interfaces or a family,
+ * we cannot send a signal as that would impact
* other interfaces.
*/
if (optind != argc || family != AF_UNSPEC)
if (!(ctx.options & DHCPCD_TEST)) {
ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */
if (!(ctx.options & DHCPCD_MANAGER))
- ctx.control_fd = control_open(argv[optind], family,
- ctx.options & DHCPCD_DUMPLEASE);
+ ctx.control_fd = control_open(argv[optind], family);
if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1)
- ctx.control_fd = control_open(argv[optind], AF_UNSPEC,
- ctx.options & DHCPCD_DUMPLEASE);
+ ctx.control_fd = control_open(argv[optind], AF_UNSPEC);
if (ctx.control_fd == -1)
- ctx.control_fd = control_open(NULL, AF_UNSPEC,
- ctx.options & DHCPCD_DUMPLEASE);
+ ctx.control_fd = control_open(NULL, AF_UNSPEC);
if (ctx.control_fd != -1) {
-#ifdef PRIVSEP
- if (IN_PRIVSEP(&ctx) &&
- ps_managersandbox(&ctx, NULL) == -1)
- goto exit_failure;
-#endif
if (!(ctx.options & DHCPCD_DUMPLEASE))
loginfox("sending commands to dhcpcd process");
len = control_send(&ctx, argc, argv);
logerr("%s: control_send", __func__);
goto exit_failure;
}
+ if (dhcpcd_readerror(&ctx) != 0) {
+ logerr("dhcpcd_control_read");
+ goto exit_failure;
+ }
if (ctx.options & DHCPCD_DUMPLEASE) {
if (dhcpcd_readdump(&ctx) == -1) {
logerr("%s: dhcpcd_readdump", __func__);
loginfox(PACKAGE "-" VERSION " starting");
- // We don't need stdin past this point
+ /* We don't need stdin past this point */
dup_null(STDIN_FILENO);
#if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK)
logdebugx("spawned manager process on PID %d", (int)getpid());
start_manager:
+
+ logdebugx("spawned manager process on PID %d", (int)getpid());
ctx.options |= DHCPCD_STARTED;
if ((pid = pidfile_lock(ctx.pidfile)) != 0) {
logerr("%s: pidfile_lock %d", __func__, (int)pid);
goto exit_failure;
#endif
- /* When running dhcpcd against a single interface, we need to retain
- * the old behaviour of waiting for an IP address */
+ /* When running dhcpcd against a single interface, we need to
+ * retain the old behaviour of waiting for an IP address */
if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND))
ctx.options |= DHCPCD_WAITIP;
logmessage(loglevel, "no interfaces have a carrier");
dhcpcd_daemonise(&ctx);
} else if (t > 0 &&
- /* Test mode removes the daemonise bit, so check for both */
+ /* Test mode removes the daemonise bit, so check for
+ both */
ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) {
eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout,
&ctx);
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd June 29, 2026
+.Dd July 29, 2026
.Dt DHCPCD.CONF 5
.Os
.Sh NAME
.Nm dhcpcd
will set this automatically.
.It Ic controlgroup Ar group
-Sets the group ownership of
-.Pa @RUNDIR@/sock
-so that users other than root can connect to
-.Nm dhcpcd .
+Members of this user group can control dhcpcd, including but not limited to
+stopping and starting interfaces.
+.It Ic readgroup Ar group
+Members of this user group can read dhcpcd interface configuration.
.It Ic debug
Echo debug messages to the stderr and syslog.
.It Ic dev Ar value
size_t io_buflen;
int control_fd;
- int control_unpriv_fd;
struct fd_list_head control_fds;
char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE];
- char control_sock_unpriv[sizeof(CONTROLSOCKET) + IF_NAMESIZE + 7];
+ gid_t read_group;
gid_t control_group;
/* DHCP Enterprise options, RFC3925 */
struct ps_process *ps_root;
struct ps_process *ps_inet;
struct ps_process *ps_ctl;
- int ps_data_fd; /* data returned from processes */
- int ps_log_fd; /* chroot logging */
- int ps_log_root_fd; /* outside chroot log reader */
- struct fd_list *ps_control; /* Queue for the above */
- struct fd_list *ps_control_client; /* Queue for the above */
- void *ps_buf; /* IPC buffer */
- size_t ps_buflen; /* IPC buffer length */
+ int ps_data_fd; /* data returned from processes */
+ int ps_log_fd; /* chroot logging */
+ int ps_log_root_fd; /* outside chroot log reader */
+ struct fd_list *ps_control; /* Queue for the above */
+ void *ps_buf; /* IPC buffer */
+ size_t ps_buflen; /* IPC buffer length */
+ unsigned int ps_control_id; /* Unique monotonic control ID */
#endif
#ifdef INET
void dhcpcd_daemonise(struct dhcpcd_ctx *);
void dhcpcd_linkoverflow(struct dhcpcd_ctx *);
-int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **);
+ssize_t dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **);
void dhcpcd_handlecarrier(struct interface *, int, unsigned int);
int dhcpcd_handleinterface(void *, int, const char *);
void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t);
static int
eloop_signal_kqueue(struct eloop *eloop, const int *signals, size_t nsignals)
{
- unsigned int cmd = nsignals == 0 ? EV_DELETE : EV_ADD;
+ unsigned short cmd = nsignals == 0 ? EV_DELETE : EV_ADD;
struct kevent *ke, *kep;
size_t i;
int err;
{ "dhcp6", no_argument, NULL, O_DHCP6 },
{ "nodhcp6", no_argument, NULL, O_NODHCP6 },
{ "controlgroup", required_argument, NULL, O_CONTROLGRP },
+ { "readgroup", required_argument, NULL, O_READGRP },
{ "slaac", required_argument, NULL, O_SLAAC },
{ "gateway", no_argument, NULL, O_GATEWAY },
{ "reject", required_argument, NULL, O_REJECT },
ifo->options &= ~DHCPCD_DHCP6;
break;
case O_CONTROLGRP:
+ case O_READGRP:
ARG_REQUIRED;
#ifdef PRIVSEP
/* Control group is already set by this point.
return -1;
}
if (grp == NULL) {
- if (!ctx->control_group)
- logerrx("controlgroup: %s: not found", arg);
+ logerrx("group: %s: not found", arg);
free(p);
return -1;
}
- ctx->control_group = grp->gr_gid;
+ switch (opt) {
+ case O_CONTROLGRP:
+ ctx->control_group = grp->gr_gid;
+ break;
+ case O_READGRP:
+ ctx->read_group = grp->gr_gid;
+ break;
+ }
free(p);
#else
grp = getgrnam(arg);
if (grp == NULL) {
- if (!ctx->control_group)
- logerrx("controlgroup: %s: not found", arg);
+ logerrx("group: %s: not found", arg);
return -1;
}
- ctx->control_group = grp->gr_gid;
+ switch (opt) {
+ case O_CONTROLGRP:
+ ctx->control_group = grp->gr_gid;
+ break;
+ case O_READGRP:
+ ctx->read_group = grp->gr_gid;
+ break;
+ }
#endif
break;
case O_GATEWAY:
#define O_BACKOFF_CUTOFF O_BASE + 61
#define O_BACKOFF_JITTER O_BASE + 62
#define O_ALLOW O_BASE + 63
+#define O_READGRP O_BASE + 64
extern const struct option cf_options[];
#include "logerr.h"
#include "privsep.h"
+#define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd
+
/* We expect to have open 2 privsep STREAM, 2 STREAM and 2 file STREAM fds */
static int
}
ssize_t
-ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len)
+ps_ctl_handleargs(struct fd_list *fd, const char *data, size_t len)
{
- /* Make any change here in dhcpcd.c as well. */
- if (strncmp(data, "--version", MIN(strlen("--version"), len)) == 0) {
- return control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1);
- } else if (strncmp(data, "--getconfigfile",
- MIN(strlen("--getconfigfile"), len)) == 0) {
- return control_queue(fd, UNCONST(fd->ctx->cffile),
+#define strclcmp(d, l, c) \
+ ((l) == (__arraycount((c))) ? strncmp((d), (c), (l)) : -1)
+
+ /* Make any change here in dhcpcd.c as well.
+ * --version is NOT terminated with \n. */
+ if (strclcmp(data, len, "--version") == 0)
+ return control_queue(fd, VERSION, strlen(VERSION) + 1);
+ else if (strclcmp(data, len, "--getconfigfile\n") == 0)
+ return control_queue(fd, fd->ctx->cffile,
strlen(fd->ctx->cffile) + 1);
- } else if (strncmp(data, "--listen", MIN(strlen("--listen"), len)) ==
- 0) {
- fd->flags |= FD_LISTEN;
- return 0;
- }
+ else if (strclcmp(data, len, "--isprivileged\n") == 0) {
+ const char *ret = fd->flags & FD_CONTROL ? "true" : "false";
+ return control_queue(fd, ret, strlen(ret) + 1);
+ } else if (strclcmp(data, len, "--listen\n") == 0)
+ return control_handle_listen(fd);
- if (fd->ctx->ps_control_client != NULL &&
- fd->ctx->ps_control_client != fd) {
- logerrx("%s: cannot handle another client", __func__);
- return 0;
- }
- return 1;
+ fd->flags |= FD_COMMAND;
+ return 0;
}
static ssize_t
ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
{
- struct dhcpcd_ctx *ctx = arg;
- struct iovec *iov = msg->msg_iov;
+ struct ps_process *psp = arg;
+ struct dhcpcd_ctx *ctx = psp->psp_ctx;
struct fd_list *fd;
- unsigned int fd_flags = FD_SENDLEN;
+ unsigned int fd_flags = 0;
int err;
- switch (psm->ps_flags) {
- case PS_CTL_PRIV:
- break;
- case PS_CTL_UNPRIV:
- fd_flags |= FD_UNPRIV;
- break;
- }
-
switch (psm->ps_cmd) {
+ case PS_CTL_CONTROL:
+ fd_flags |= FD_CONTROL; /* FALLTHROUGH */
+ case PS_CTL_READ:
+ fd_flags |= FD_READ; /* FALLTHROUGH */
case PS_CTL:
if (msg->msg_iovlen != 1) {
errno = EINVAL;
return -1;
}
- if (ctx->ps_control_client != NULL) {
- logerrx("%s: cannot handle another client", __func__);
- return 0;
- }
fd = control_new(ctx, ctx->ps_ctl->psp_work_fd, fd_flags);
if (fd == NULL)
return -1;
- ctx->ps_control_client = fd;
- err = control_recvdata(fd, iov->iov_base, iov->iov_len);
- if (err == -1 || err == 0) {
+ fd->peer_id = (unsigned int)psm->ps_flags;
+ err = control_recvmsg(fd, msg, psm->ps_datalen);
+ if (err == -1 || err == 0)
control_free(fd);
- ctx->ps_control_client = NULL;
- }
- break;
- case PS_CTL_EOF:
- ctx->ps_control_client = NULL;
break;
default:
errno = ENOTSUP;
struct ps_process *psp = arg;
if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_ctl_dispatch,
- psp->psp_ctx) == -1)
+ psp) == -1)
logerr(__func__);
}
ps_ctl_recv(void *arg, unsigned short events)
{
struct dhcpcd_ctx *ctx = arg;
- char buf[BUFSIZ];
- ssize_t len;
+ int fd;
+ unsigned int peer_id;
+ size_t msglen;
+ /* Control messages for a peer are prefixed with fd and message len */
+ struct iovec iov[] = {
+ {
+ .iov_base = &peer_id,
+ .iov_len = sizeof(peer_id),
+ },
+ {
+ .iov_base = &msglen,
+ .iov_len = sizeof(msglen),
+ },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = __arraycount(iov),
+ };
+ ssize_t rlen;
+ struct fd_list *fdl;
+
+ if (events & ELE_HANGUP) {
+ hangup:
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
- if (!(events & (ELE_READ | ELE_HANGUP)))
+ if (!(events & ELE_READ))
logerrx("%s: unexpected event 0x%04x", __func__, events);
- if (events & ELE_READ) {
- len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf));
- if (len == -1)
- logerr("%s: read", __func__);
- else if (len == 0)
- // FIXME: Why does this happen?
- ;
- else if (ctx->ps_control_client == NULL)
- logerrx("%s: clientfd #%d disconnected (len=%zd)",
- __func__, ctx->ps_ctl->psp_work_fd, len);
- else {
- errno = 0;
- if (control_queue(ctx->ps_control_client, buf,
- (size_t)len) == -1)
- logerr("%s: control_queue", __func__);
+ fd = ctx->ps_ctl->psp_work_fd;
+ rlen = recvmsg(fd, &msg, MSG_WAITALL);
+ if (rlen == 0)
+ goto hangup;
+ if (rlen == -1) {
+ logerr("%s: recvmsg hdr", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+ if (rlen != sizeof(peer_id) + sizeof(msglen)) {
+ errno = EINVAL;
+ logerr("%s: recvmsg hdr", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+
+ if (msglen == 0) /* ulikely */
+ return;
+
+ if (ctx->io_buflen < msglen) {
+ void *n = realloc(ctx->io_buf, msglen);
+ if (n == NULL) {
+ logerr(__func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
}
+ ctx->io_buf = n;
+ ctx->io_buflen = msglen;
+ }
+
+ iov[0].iov_base = ctx->io_buf;
+ iov[0].iov_len = msglen;
+ msg.msg_iovlen = 1;
+ rlen = recvmsg(fd, &msg, MSG_WAITALL);
+ if (rlen == 0)
+ goto hangup;
+ if (rlen == -1) {
+ logerr("%s: recvmsg msg", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+ if ((size_t)rlen != msglen) {
+ errno = EINVAL;
+ logerr("%s: recvmsg msg", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+
+ /* Send to our peer */
+ TAILQ_FOREACH(fdl, &ctx->control_fds, next) {
+ if (fdl->id != peer_id)
+ continue;
+ if (control_queuef(fdl, ctx->io_buf, (size_t)msglen, 0) == -1)
+ logerr("%s: control_queue", __func__);
+ break;
}
}
ps_ctl_listen(void *arg, unsigned short events)
{
struct dhcpcd_ctx *ctx = arg;
- char buf[BUFSIZ];
ssize_t len;
- struct fd_list *fd;
+ size_t msglen;
+ struct iovec iov[] = { {
+ .iov_base = &msglen,
+ .iov_len = sizeof(msglen),
+ } };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = __arraycount(iov),
+ };
+ int fd;
+ struct fd_list *fdl;
+
+ if (events & ELE_HANGUP) {
+ hangup:
+ eloop_exit(ctx->eloop, EXIT_SUCCESS);
+ return;
+ }
if (!(events & ELE_READ))
logerrx("%s: unexpected event 0x%04x", __func__, events);
- len = read(ctx->ps_control->fd, buf, sizeof(buf));
- if (len == -1) {
- logerr("%s: read", __func__);
- eloop_exit(ctx->eloop, EXIT_FAILURE);
- return;
+ fd = ctx->ps_control->fd;
+ len = recvmsg(fd, &msg, MSG_WAITALL);
+ if (len == 0)
+ goto hangup;
+ if (len != sizeof(msglen)) {
+ logerr("%s: recvmsg len %zd", __func__, len);
+ goto err;
+ }
+
+ if (ps_bufalloc(ctx, msglen) == -1) {
+ logerr("%s: realloc", __func__);
+ goto err;
+ }
+
+ iov->iov_base = ctx->ps_buf;
+ iov->iov_len = msglen;
+ len = recvmsg(fd, &msg, MSG_WAITALL);
+ if (len == 0)
+ goto hangup;
+ if ((size_t)len != msglen) {
+ logerr("%s: recvmsg", __func__);
+ goto err;
}
/* Send to our listeners */
- TAILQ_FOREACH(fd, &ctx->control_fds, next) {
- if (!(fd->flags & FD_LISTEN))
+ TAILQ_FOREACH(fdl, &ctx->control_fds, next) {
+ if (!(fdl->flags & FD_LISTEN))
continue;
- if (control_queue(fd, buf, (size_t)len) == -1)
+ if (control_queue(fdl, ctx->ps_buf, msglen) == -1)
logerr("%s: control_queue", __func__);
}
+
+ return;
+
+err:
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
}
pid_t
if_closesockets(ctx);
- if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, work_fd) == -1 ||
- xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1)
+ if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, work_fd) ==
+ -1 ||
+ xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, listen_fd) ==
+ -1)
return -1;
#ifdef PRIVSEP_RIGHTS
if (ps_rights_limit_fdpair(work_fd) == -1 ||
psp->psp_work_fd = work_fd[0];
close(work_fd[1]);
close(listen_fd[1]);
- ctx->ps_control = control_new(ctx, listen_fd[0],
- FD_SENDLEN | FD_LISTEN);
+ ctx->ps_control = control_new(ctx, listen_fd[0], FD_LISTEN);
if (ctx->ps_control == NULL)
return -1;
return pid;
}
ssize_t
-ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len)
+ps_ctl_sendmsg(struct fd_list *fd, const struct msghdr *msg)
{
struct dhcpcd_ctx *ctx = fd->ctx;
-
- if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd)
- logerrx("%s: cannot deal with another client", __func__);
- ctx->ps_control_client = fd;
- return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL,
- fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV, data, len);
-}
-
-ssize_t
-ps_ctl_sendeof(struct fd_list *fd)
-{
- struct dhcpcd_ctx *ctx = fd->ctx;
-
- return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL_EOF, 0, NULL, 0);
+ uint16_t cmd;
+ unsigned long flags = (unsigned long)fd->id;
+
+ if (fd->flags & FD_CONTROL)
+ cmd = PS_CTL_CONTROL;
+ else if (fd->flags & FD_READ)
+ cmd = PS_CTL_READ;
+ else
+ cmd = PS_CTL;
+ return ps_sendmsg(ctx, PS_CTL_FD(ctx), cmd, flags, msg);
}
pid_t ps_ctl_start(struct dhcpcd_ctx *);
int ps_ctl_stop(struct dhcpcd_ctx *);
-ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t);
-ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t);
-ssize_t ps_ctl_sendeof(struct fd_list *fd);
+ssize_t ps_ctl_handleargs(struct fd_list *, const char *, size_t);
+ssize_t ps_ctl_sendmsg(struct fd_list *, const struct msghdr *);
+ssize_t ps_ctl_sendeof(struct dhcpcd_ctx *);
#endif
return writefile(file, mode, nc, len - flen);
}
+static ssize_t
+ps_root_douser_ingroup(void *data, size_t len)
+{
+ uid_t uid;
+ gid_t gid, grpid;
+ uint8_t *p;
+
+ if (len != sizeof(uid) + sizeof(gid) + sizeof(grpid)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ p = data;
+ memcpy(&uid, p, sizeof(uid));
+ p += sizeof(uid);
+ memcpy(&gid, p, sizeof(gid));
+ p += sizeof(gid);
+ memcpy(&grpid, p, sizeof(grpid));
+
+ return control_user_ingroup(uid, gid, grpid);
+}
+
#ifdef AUTH
static ssize_t
ps_root_monordm(uint64_t *rdm, size_t len)
case PS_LOGREOPEN:
err = logopen(ctx->logfile);
break;
+ case PS_USER_INGROUP:
+ err = ps_root_douser_ingroup(data, len);
+ break;
#ifdef AUTH
case PS_AUTH_MONORDM:
err = ps_root_monordm(data, len);
return ps_root_readerror(ctx, NULL, 0);
}
+ssize_t
+ps_root_user_ingroup(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid, gid_t grpid)
+{
+ struct iovec iov[] = {
+ {
+ .iov_base = &uid,
+ .iov_len = sizeof(uid),
+ },
+ {
+ .iov_base = &gid,
+ .iov_len = sizeof(gid),
+ },
+ {
+ .iov_base = &grpid,
+ .iov_len = sizeof(grpid),
+ },
+
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = __arraycount(iov),
+ };
+
+ if (ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_USER_INGROUP, 0, &msg) == -1) {
+ logerr(__func__);
+ return -1;
+ }
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
#ifdef PRIVSEP_GETHOSTNAME
int
ps_root_gethostname(struct dhcpcd_ctx *ctx, char *hname, size_t hnamelen)
ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t);
ssize_t ps_root_stopprocesses(struct dhcpcd_ctx *);
int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *);
+ssize_t ps_root_user_ingroup(struct dhcpcd_ctx *, uid_t, gid_t, gid_t);
#ifdef PRIVSEP_GETHOSTNAME
int ps_root_gethostname(struct dhcpcd_ctx *, char *, size_t);
#endif
/* Close things we no longer need */
pidfile_unlock();
- eloop_closefdwaiter(ctx->eloop);
+ if (ctx->ps_ctl != psp)
+ eloop_closefdwaiter(ctx->eloop);
/* Close more if we are not root */
if (ctx->ps_root != psp) {
- ps_root_close(ctx);
+ if (ctx->ps_ctl != psp)
+ ps_root_close(ctx);
#ifdef PLUGIN_DEV
dev_stop(ctx);
#endif
if (ctx->ps_root != psp) {
ctx->options &= ~DHCPCD_PRIVSEPROOT;
- ctx->ps_root = NULL;
if (ctx->ps_log_root_fd != -1) {
/* Already removed from eloop thanks to above clear. */
close(ctx->ps_log_root_fd);
TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) {
if (psp == notthis)
continue;
+ /* control needs root access to work out user group */
+ if (ctx->ps_ctl == notthis && psp == ctx->ps_root)
+ continue;
ps_freeprocess(psp);
}
}
#define PS_FILEMTIME 0x0016
#define PS_AUTH_MONORDM 0x0017
#define PS_CTL 0x0018
-#define PS_CTL_EOF 0x0019
-#define PS_LOGREOPEN 0x0020
-#define PS_STOPPROCS 0x0021
-#define PS_DAEMONISED 0x0022
+#define PS_CTL_CONTROL 0x0019
+#define PS_CTL_READ 0x0020
+#define PS_LOGREOPEN 0x0021
+#define PS_STOPPROCS 0x0022
+#define PS_DAEMONISED 0x0023
+#define PS_USER_INGROUP 0x0024
/* Domains */
#define PS_ROOT 0x0101
#define PS_DEV_IFREMOVED 0x0002
#define PS_DEV_IFUPDATED 0x0003
-/* Control Type (via flags) */
-#define PS_CTL_PRIV 0x0004
-#define PS_CTL_UNPRIV 0x0005
-
/* Sysctl Needs (via flags) */
#define PS_SYSCTL_OLEN 0x0001
#define PS_SYSCTL_ODATA 0x0002
return -1;
}
-static int
+static ssize_t
send_interface1(struct fd_list *fd, const struct interface *ifp,
const char *reason)
{