diff --git a/src/control.c b/src/control.c index ad006c20..8afc4f62 100644 --- a/src/control.c +++ b/src/control.c @@ -33,12 +33,18 @@ #include #include +#include +#include #include #include #include #include #include +#if defined(__sun) +#include +#endif + #include "common.h" #include "config.h" #include "control.h" @@ -53,6 +59,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 +90,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 +100,89 @@ 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); } +#ifdef SO_PEERCRED static int -control_handle_read(struct fd_list *fd) +getpeereid(int fd, uid_t *uid, gid_t *gid) { - char buffer[1024]; - ssize_t bytes; + 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; + return 0; +} +#elif defined(__sun) +static int +getpeereid(int fd, uid_t *uid, gid_t *gid) +{ + ucred_t *ucred = NULL; + + if (getpeerucred(fd, &ucred) == -1) + return -1; - bytes = read(fd->fd, buffer, sizeof(buffer) - 1); + *uid = ucred_geteuid(ucred); + *gid_t gid = ucred_getegid(ucred); + ucred_free(ucred); + return 0; +} +#endif + +static int +control_handle_read(struct fd_list *fd) +{ + uid_t uid; + gid_t 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; + if (getpeereid(fd->fd, &uid, &gid) == -1) { + logerr("%s: getpeereid", __func__); + return -1; + } + #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { - ssize_t err; + ssize_t perr; + + perr = ps_root_user_ispriv(fd->ctx, uid, gid); + if (perr == -1) { + logerr(__func__); + return -1; + } + if (perr == 0) + fd->flags &= ~FD_PRIV; + else + fd->flags |= FD_PRIV; fd->flags |= FD_SENDLEN; - err = ps_ctl_handleargs(fd, buffer, (size_t)bytes); + perr = ps_ctl_handleargs(fd, buf, (size_t)bytes); fd->flags &= ~FD_SENDLEN; - if (err == -1) { + if (perr == -1) { logerr(__func__); return 0; } - if (err == 1 && - ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { + iov[0].iov_len = (size_t)bytes; + if (perr == 1 && ps_ctl_sendmsg(fd, &msg) == -1) { logerr(__func__); return -1; } @@ -136,7 +190,16 @@ control_handle_read(struct fd_list *fd) } #endif - return control_recvdata(fd, buffer, (size_t)bytes); + err = control_user_ispriv(fd->ctx, uid, gid); + if (err == -1) { + logerr(__func__); + return -1; + } + if (err == 0) + fd->flags &= ~FD_PRIV; + else + fd->flags |= FD_PRIV; + return control_recvmsg(fd, &msg, (size_t)bytes); } static int @@ -181,13 +244,6 @@ control_handle_write(struct fd_list *fd) 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 - /* Done sending data, stop watching write to fd */ if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ, control_handle_data, fd) == -1) @@ -211,7 +267,7 @@ 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) @@ -224,12 +280,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) { @@ -271,7 +336,7 @@ 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) + if (errno != EINTR && errno != EAGAIN && errno != EPERM) return -1; } } @@ -279,11 +344,31 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) 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 +391,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); @@ -335,6 +420,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 +437,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 +453,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 +510,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 +523,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 +559,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 +572,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); @@ -632,3 +672,40 @@ control_queue(struct fd_list *fd, void *data, size_t data_len) return eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data, fd); } + +int +control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +{ + gid_t *groups = NULL, *gp; + 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, groups, &ngroups) == -1) { + gp = reallocarray(groups, (size_t)ngroups, sizeof(*groups)); + if (gp == NULL) + goto out; + groups = gp; + if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) + goto out; + } + + for (gp = groups; ngroups != 0; ngroups--, gp++) { + if (*gp == ctx->control_group) { + err = 1; + goto out; + } + } + err = 0; + +out: + free(groups); + return err; +} diff --git a/src/control.h b/src/control.h index 9e966f77..85086025 100644 --- a/src/control.h +++ b/src/control.h @@ -66,16 +66,18 @@ struct fd_list { TAILQ_HEAD(fd_list_head, fd_list); #define FD_LISTEN 0x01U -#define FD_UNPRIV 0x02U +#define FD_PRIV 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); +int control_recvmsg(struct fd_list *, struct msghdr *, size_t); +int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid); #endif 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..f45c4f38 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1704,7 +1704,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } /* Only privileged users can control dhcpcd via the socket. */ - if (fd->flags & FD_UNPRIV) { + if (!(fd->flags & FD_PRIV)) { errno = EPERM; return -1; } @@ -2078,7 +2078,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; @@ -2421,20 +2421,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); @@ -2580,6 +2572,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); diff --git a/src/dhcpcd.h b/src/dhcpcd.h index cb2a9303..e1865cb5 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -174,10 +174,8 @@ 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 control_group; /* DHCP Enterprise options, RFC3925 */ @@ -199,13 +197,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 diff --git a/src/privsep-control.c b/src/privsep-control.c index 3737d07d..cb27de1b 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 @@ -93,12 +95,6 @@ ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) fd->flags |= FD_LISTEN; return 0; } - - if (fd->ctx->ps_control_client != NULL && - fd->ctx->ps_control_client != fd) { - logerrx("%s: cannot handle another client", __func__); - return 0; - } return 1; } @@ -106,41 +102,24 @@ 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; 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_PRIV: + fd_flags |= FD_PRIV; /* 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; @@ -162,29 +141,34 @@ ps_ctl_dodispatch(void *arg, unsigned short events) static void ps_ctl_recv(void *arg, unsigned short events) { - struct dhcpcd_ctx *ctx = arg; char buf[BUFSIZ]; + struct dhcpcd_ctx *ctx = arg; ssize_t len; + struct fd_list *fd; - if (!(events & (ELE_READ | ELE_HANGUP))) + if (events & ELE_HANGUP) { + hangup: + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + if (!(events & ELE_READ)) { logerrx("%s: unexpected event 0x%04x", __func__, events); + return; + } - 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__); - } + len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf)); + if (len == -1) { + logerr("%s: read", __func__); + return; + } else if (len == 0) + goto hangup; + + TAILQ_FOREACH(fd, &ctx->control_fds, next) { + if (fd->flags & FD_LISTEN) + continue; + if (control_queue(fd, buf, (size_t)len) == -1) + logerr("%s: control_queue", __func__); } } @@ -196,6 +180,12 @@ ps_ctl_listen(void *arg, unsigned short events) ssize_t len; struct fd_list *fd; + if (events & ELE_HANGUP) { + hangup: + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -205,10 +195,12 @@ ps_ctl_listen(void *arg, unsigned short events) eloop_exit(ctx->eloop, EXIT_FAILURE); return; } + if (len == 0) + goto hangup; /* Send to our listeners */ TAILQ_FOREACH(fd, &ctx->control_fds, next) { - if (!(fd->flags & FD_LISTEN)) + if (fd == ctx->ps_control || !(fd->flags & FD_LISTEN)) continue; if (control_queue(fd, buf, (size_t)len) == -1) logerr("%s: control_queue", __func__); @@ -263,7 +255,7 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) ctx) == -1) return -1; - ctx->ps_control = control_new(ctx, listen_fd[1], 0); + ctx->ps_control = control_new(ctx, listen_fd[1], FD_LISTEN); if (ctx->ps_control == NULL) return -1; if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ, @@ -281,21 +273,11 @@ ps_ctl_stop(struct dhcpcd_ctx *ctx) } ssize_t -ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len) -{ - 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) +ps_ctl_sendmsg(struct fd_list *fd, const struct msghdr *msg) { struct dhcpcd_ctx *ctx = fd->ctx; + uint16_t cmd = fd->flags & FD_PRIV ? PS_CTL_PRIV : PS_CTL; + unsigned long flags = (unsigned long)fd->fd; - return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL_EOF, 0, NULL, 0); + 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..08b75862 100644 --- a/src/privsep-control.h +++ b/src/privsep-control.h @@ -35,7 +35,7 @@ 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_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..dec46b0d 100644 --- a/src/privsep-root.c +++ b/src/privsep-root.c @@ -320,6 +320,26 @@ 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_ispriv(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + uid_t uid; + gid_t gid; + uint8_t *p; + + if (len != sizeof(uid) + sizeof(gid)) { + errno = EINVAL; + return -1; + } + + p = data; + memcpy(&uid, p, sizeof(uid)); + p += sizeof(uid); + memcpy(&gid, p, sizeof(gid)); + + return control_user_ispriv(ctx, uid, gid); +} + #ifdef AUTH static ssize_t ps_root_monordm(uint64_t *rdm, size_t len) @@ -563,6 +583,9 @@ ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) case PS_LOGREOPEN: err = logopen(ctx->logfile); break; + case PS_USER_ISPRIV: + err = ps_root_douser_ispriv(ctx, data, len); + break; #ifdef AUTH case PS_AUTH_MONORDM: err = ps_root_monordm(data, len); @@ -1119,6 +1142,29 @@ ps_root_logreopen(struct dhcpcd_ctx *ctx) return ps_root_readerror(ctx, NULL, 0); } +ssize_t +ps_root_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +{ + struct iovec iov[] = { { + .iov_base = &uid, + .iov_len = sizeof(uid), + }, + { + .iov_base = &gid, + .iov_len = sizeof(gid), + } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + + if (ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_USER_ISPRIV, 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..af5fad95 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_ispriv(struct dhcpcd_ctx *, uid_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..20e59170 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -52,10 +52,11 @@ #define PS_FILEMTIME 0x0016 #define PS_AUTH_MONORDM 0x0017 #define PS_CTL 0x0018 -#define PS_CTL_EOF 0x0019 +#define PS_CTL_PRIV 0x0019 #define PS_LOGREOPEN 0x0020 #define PS_STOPPROCS 0x0021 #define PS_DAEMONISED 0x0022 +#define PS_USER_ISPRIV 0x0023 /* Domains */ #define PS_ROOT 0x0101 @@ -82,10 +83,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