From: Roy Marples Date: Mon, 3 Aug 2026 12:38:54 +0000 (+0100) Subject: control: remove unprivileged socket X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=HEAD;p=thirdparty%2Fdhcpcd.git control: remove unprivileged socket Instead workout credentials from the connected socket by getpeereid(2). Remove the limit of only one connected control client. Add readgroup, to so we can deny reading of dhcpcd interface data to everyone on the system. Always send back a result code to socket callers. This is a breaking ABI change, but allows dhcpcd-online, dhcpcd-gtk etc to display EPERM for example. --- diff --git a/compat/getpeereid.c b/compat/getpeereid.c new file mode 100644 index 00000000..404f8ab9 --- /dev/null +++ b/compat/getpeereid.c @@ -0,0 +1,64 @@ +/* + * compat: getpeereid + * SPDX-License-Identifier: BSD-2-Clause + * Copyright (c) 2006-2026 Roy Marples + * 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 +#include + +#ifdef __sun +#include +#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; +} diff --git a/compat/getpeereid.h b/compat/getpeereid.h new file mode 100644 index 00000000..c8949dc6 --- /dev/null +++ b/compat/getpeereid.h @@ -0,0 +1,36 @@ +/* + * compat: getpeereid + * SPDX-License-Identifier: BSD-2-Clause + * Copyright (c) 2006-2026 Roy Marples + * 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 + +int getpeereid(int, uid_t *, gid_t *); + +#endif diff --git a/configure b/configure index b246fd3e..32bf96ef 100755 --- a/configure +++ b/configure @@ -938,6 +938,29 @@ if [ "$ARC4RANDOM_UNIFORM" = no ]; then echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H fi +if [ -z "$GETPEEREID" ]; then + printf "Testing for getpeereid ... " + cat <_getpeereid.c +#include +#include +#include +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 <_memset_explicit.c diff --git a/src/control.c b/src/control.c index ad006c20..6ed7dd16 100644 --- a/src/control.c +++ b/src/control.c @@ -33,6 +33,8 @@ #include #include +#include +#include #include #include #include @@ -53,6 +55,10 @@ (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 @@ -80,11 +86,6 @@ control_queue_free(struct fd_list *fd) 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); @@ -95,40 +96,77 @@ control_free(struct fd_list *fd) 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; } @@ -136,31 +174,71 @@ control_handle_read(struct fd_list *fd) } #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__); @@ -179,27 +257,23 @@ control_handle_write(struct fd_list *fd) #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); @@ -211,11 +285,9 @@ control_handle_data(void *arg, unsigned short 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; @@ -224,12 +296,21 @@ hangup: } 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) { @@ -270,19 +351,64 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) } *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) @@ -294,6 +420,10 @@ control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags) 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; @@ -306,7 +436,7 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, 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); @@ -321,13 +451,6 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, 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; @@ -335,6 +458,7 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, if (eloop_event_add(ctx->eloop, l->fd, ELE_READ, control_handle_data, l) == -1) logerr("%s: eloop_event_add", __func__); + return; error: @@ -351,20 +475,10 @@ control_handle(void *arg, unsigned short events) 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: @@ -377,70 +491,53 @@ make_path(char *path, size_t len, const char *ifname, sa_family_t family, 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 @@ -451,14 +548,12 @@ control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) #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; @@ -466,12 +561,6 @@ control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) -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; } @@ -508,9 +597,6 @@ control_stop(struct dhcpcd_ctx *ctx) 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; @@ -524,24 +610,16 @@ control_stop(struct dhcpcd_ctx *ctx) 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); @@ -577,8 +655,9 @@ control_send(struct dhcpcd_ctx *ctx, int argc, char *const *argv) 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; @@ -623,12 +702,83 @@ control_queue(struct fd_list *fd, void *data, size_t data_len) } 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); } diff --git a/src/control.h b/src/control.h index 9e966f77..0ffdc9a0 100644 --- a/src/control.h +++ b/src/control.h @@ -41,6 +41,9 @@ #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 @@ -50,6 +53,10 @@ struct fd_data { 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); @@ -62,20 +69,29 @@ struct fd_list { #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 diff --git a/src/defs.h b/src/defs.h index d21a8fae..61f80579 100644 --- a/src/defs.h +++ b/src/defs.h @@ -29,7 +29,7 @@ #define DEFS_H #define PACKAGE "dhcpcd" -#define VERSION "10.3.2" +#define VERSION "10.5.0" #ifndef PRIVSEP_USER #define PRIVSEP_USER "_" PACKAGE diff --git a/src/dhcpcd.8.in b/src/dhcpcd.8.in index 86b69132..26d37772 100644 --- a/src/dhcpcd.8.in +++ b/src/dhcpcd.8.in @@ -23,7 +23,7 @@ .\" 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 @@ -861,12 +861,8 @@ running on the .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 , diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 36ee0e3b..2e8119cb 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1575,34 +1575,40 @@ dhcpcd_signal_cb(int sig, void *arg) } #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 */ @@ -1624,9 +1630,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, 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) { @@ -1661,12 +1665,36 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } } - /* 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) @@ -1677,13 +1705,21 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } 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; @@ -1700,23 +1736,21 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, 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) { @@ -1748,24 +1782,34 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, 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; } @@ -1847,6 +1891,23 @@ err: 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) { @@ -2078,7 +2139,7 @@ main(int argc, char **argv, char **envp) 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; @@ -2266,8 +2327,8 @@ main(int argc, char **argv, char **envp) 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; @@ -2302,8 +2363,8 @@ main(int argc, char **argv, char **envp) 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) @@ -2421,20 +2482,12 @@ main(int argc, char **argv, char **envp) 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); @@ -2444,6 +2497,10 @@ main(int argc, char **argv, char **envp) 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__); @@ -2490,7 +2547,7 @@ main(int argc, char **argv, char **envp) 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) @@ -2580,6 +2637,8 @@ main(int argc, char **argv, char **envp) 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); @@ -2672,8 +2731,8 @@ start_manager: 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; @@ -2751,7 +2810,8 @@ start_manager: 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); diff --git a/src/dhcpcd.conf.5.in b/src/dhcpcd.conf.5.in index 82425b1c..b7d596e9 100644 --- a/src/dhcpcd.conf.5.in +++ b/src/dhcpcd.conf.5.in @@ -23,7 +23,7 @@ .\" 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 @@ -162,10 +162,10 @@ In most cases, .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 diff --git a/src/dhcpcd.h b/src/dhcpcd.h index cb2a9303..f097c95f 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -174,10 +174,9 @@ struct dhcpcd_ctx { 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 */ @@ -199,13 +198,13 @@ struct dhcpcd_ctx { 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 @@ -267,7 +266,7 @@ void dhcpcd_daemonised(struct dhcpcd_ctx *); 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); diff --git a/src/eloop.c b/src/eloop.c index 912742a5..70090551 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -236,7 +236,7 @@ eloop_event_count(const struct eloop *eloop) 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; diff --git a/src/if-options.c b/src/if-options.c index e584a2d5..df8e3486 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -158,6 +158,7 @@ const struct option cf_options[] = { { "background", no_argument, NULL, 'b' }, { "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 }, @@ -2431,6 +2432,7 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ifo->options &= ~DHCPCD_DHCP6; break; case O_CONTROLGRP: + case O_READGRP: ARG_REQUIRED; #ifdef PRIVSEP /* Control group is already set by this point. @@ -2472,21 +2474,33 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, 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: diff --git a/src/if-options.h b/src/if-options.h index 288426ce..0158b9ae 100644 --- a/src/if-options.h +++ b/src/if-options.h @@ -204,6 +204,7 @@ #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[]; diff --git a/src/privsep-control.c b/src/privsep-control.c index 3737d07d..f94d1661 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -37,6 +37,8 @@ #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 @@ -79,68 +81,54 @@ ps_ctl_recvmsg(void *arg, unsigned short events) } 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; @@ -155,7 +143,7 @@ ps_ctl_dodispatch(void *arg, unsigned short events) 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__); } @@ -163,28 +151,91 @@ static void 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; } } @@ -192,27 +243,64 @@ static void 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 @@ -228,8 +316,10 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) 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 || @@ -248,8 +338,7 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) 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; @@ -281,21 +370,17 @@ ps_ctl_stop(struct dhcpcd_ctx *ctx) } 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); } diff --git a/src/privsep-control.h b/src/privsep-control.h index ef2a76d6..480fdf20 100644 --- a/src/privsep-control.h +++ b/src/privsep-control.h @@ -34,8 +34,8 @@ 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 diff --git a/src/privsep-root.c b/src/privsep-root.c index 497ca893..47170c8e 100644 --- a/src/privsep-root.c +++ b/src/privsep-root.c @@ -320,6 +320,28 @@ ps_root_dowritefile(const struct dhcpcd_ctx *ctx, mode_t mode, void *data, 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) @@ -563,6 +585,9 @@ ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) 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); @@ -1119,6 +1144,36 @@ ps_root_logreopen(struct dhcpcd_ctx *ctx) 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) diff --git a/src/privsep-root.h b/src/privsep-root.h index 58fe8da8..737f52ab 100644 --- a/src/privsep-root.h +++ b/src/privsep-root.h @@ -53,6 +53,7 @@ ssize_t ps_root_logreopen(struct dhcpcd_ctx *); 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 diff --git a/src/privsep.c b/src/privsep.c index a0806994..65955007 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -358,11 +358,13 @@ ps_startprocess(struct ps_process *psp, /* 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 @@ -391,7 +393,6 @@ ps_startprocess(struct ps_process *psp, 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); @@ -1185,6 +1186,9 @@ ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis) 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); } } diff --git a/src/privsep.h b/src/privsep.h index 8c6ec054..3d1fdea3 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -52,10 +52,12 @@ #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 @@ -82,10 +84,6 @@ #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 diff --git a/src/script.c b/src/script.c index ca273e2b..210fb66a 100644 --- a/src/script.c +++ b/src/script.c @@ -593,7 +593,7 @@ eexit: return -1; } -static int +static ssize_t send_interface1(struct fd_list *fd, const struct interface *ifp, const char *reason) {