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..9526cc15 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,54 @@ 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 msghdr msg = { .msg_iov = iov }; struct fd_data *data; + ssize_t len; data = TAILQ_FIRST(&fd->queue); - if (data->data_flags & FD_SENDLEN) { + if (data->data_flags & FD_DATA_SENDLEN) { iov[0].iov_base = &data->data_len; - iov[0].iov_len = sizeof(size_t); + iov[0].iov_len = sizeof(data->data_len); iov[1].iov_base = data->data; iov[1].iov_len = data->data_len; - iov_len = 2; + msg.msg_iovlen = 2; + } else { iov[0].iov_base = data->data; iov[0].iov_len = data->data_len; - iov_len = 1; + msg.msg_iovlen = 1; } - if (writev(fd->fd, iov, iov_len) == -1) { + 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 +240,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 +268,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 +279,21 @@ control_handle_data(void *arg, unsigned short events) } 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,20 +334,42 @@ 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; + l = control_find(ctx, fd); + if (l != NULL) { + l->flags = flags; + return l; + } + l = malloc(sizeof(*l)); if (l == NULL) return NULL; @@ -306,7 +392,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 +407,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 +414,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 +431,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 +447,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 +504,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 +517,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 +553,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 +566,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 +611,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 +658,80 @@ 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; 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..ff78ded3 100644 --- a/src/control.h +++ b/src/control.h @@ -50,6 +50,7 @@ struct fd_data { size_t data_size; size_t data_len; unsigned int data_flags; +#define FD_DATA_SENDLEN 0x01 }; TAILQ_HEAD(fd_data_head, fd_data); @@ -65,17 +66,22 @@ struct fd_list { }; TAILQ_HEAD(fd_list_head, fd_list); +#define FD_READ 0x02U +#define FD_CONTROL 0x08U +#define FD_COMMAND 0x04U #define FD_LISTEN 0x01U -#define FD_UNPRIV 0x02U -#define FD_SENDLEN 0x04U 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..9f7690d5 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; + 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,7 +1630,6 @@ 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; @@ -1661,12 +1666,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,12 +1706,18 @@ 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) goto dumperr; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) @@ -1700,23 +1735,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 +1781,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 +1890,23 @@ dhcpcd_readdump1(void *arg, unsigned short events) 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 +2138,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 +2326,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 +2362,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 +2481,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 +2496,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 +2546,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 +2636,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 +2730,8 @@ main(int argc, char **argv, char **envp) 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 +2809,8 @@ main(int argc, char **argv, char **envp) 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..6cc5918d 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,12 @@ 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 */ #endif #ifdef INET @@ -267,7 +265,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..5a60043c 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,49 @@ 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, __unused 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), + /* Make any change here in dhcpcd.c as well. + * --version is NOT terminated with \n. */ + if (strcmp(data, "--version") == 0) { + return control_queue(fd, VERSION, strlen(VERSION) + 1); + } else if (strcmp(data, "--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 (strcmp(data, "--isprivileged\n") == 0) { + const char *ret = fd->flags & FD_CONTROL ? "true" : "false"; + return control_queue(fd, ret, strlen(ret) + 1); + } else if (strcmp(data, "--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 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) { + 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; @@ -164,27 +147,42 @@ ps_ctl_recv(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; char buf[BUFSIZ]; - ssize_t len; + struct iovec iov[] = { { + .iov_base = buf, + .iov_len = sizeof(buf), + } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + ssize_t msglen; + int fd; + 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; + msglen = recvmsg(fd, &msg, 0); + if (msglen == 0) + goto hangup; + if (msglen == -1) { + logerr("%s: recvmsg", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + + /* Send to our command controls */ + TAILQ_FOREACH(fdl, &ctx->control_fds, next) { + if (!(fdl->flags & FD_COMMAND)) + continue; + if (control_queuef(fdl, buf, (size_t)msglen, 0) == -1) + logerr("%s: control_queue", __func__); } } @@ -192,27 +190,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 +263,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 +285,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 +317,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->fd; + + 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 @@ make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, return -1; } -static int +static ssize_t send_interface1(struct fd_list *fd, const struct interface *ifp, const char *reason) {