summaryrefslogtreecommitdiff
path: root/src/privsep-root.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/privsep-root.c')
-rw-r--r--src/privsep-root.c1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/src/privsep-root.c b/src/privsep-root.c
new file mode 100644
index 000000000000..45af3910feed
--- /dev/null
+++ b/src/privsep-root.c
@@ -0,0 +1,1069 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, privileged proxy
+ * Copyright (c) 2006-2021 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "auth.h"
+#include "common.h"
+#include "dev.h"
+#include "dhcpcd.h"
+#include "dhcp6.h"
+#include "eloop.h"
+#include "if.h"
+#include "ipv6nd.h"
+#include "logerr.h"
+#include "privsep.h"
+#include "sa.h"
+#include "script.h"
+
+__CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long));
+
+struct psr_error
+{
+ ssize_t psr_result;
+ int psr_errno;
+ char psr_pad[sizeof(ssize_t) - sizeof(int)];
+ size_t psr_datalen;
+};
+
+struct psr_ctx {
+ struct dhcpcd_ctx *psr_ctx;
+ struct psr_error psr_error;
+ size_t psr_datalen;
+ void *psr_data;
+};
+
+static void
+ps_root_readerrorcb(void *arg)
+{
+ struct psr_ctx *psr_ctx = arg;
+ struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
+ struct psr_error *psr_error = &psr_ctx->psr_error;
+ struct iovec iov[] = {
+ { .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
+ { .iov_base = psr_ctx->psr_data,
+ .iov_len = psr_ctx->psr_datalen },
+ };
+ ssize_t len;
+ int exit_code = EXIT_FAILURE;
+
+#define PSR_ERROR(e) \
+ do { \
+ psr_error->psr_result = -1; \
+ psr_error->psr_errno = (e); \
+ goto out; \
+ } while (0 /* CONSTCOND */)
+
+ len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len < sizeof(*psr_error))
+ PSR_ERROR(EINVAL);
+ exit_code = EXIT_SUCCESS;
+
+out:
+ eloop_exit(ctx->ps_eloop, exit_code);
+}
+
+ssize_t
+ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len)
+{
+ struct psr_ctx psr_ctx = {
+ .psr_ctx = ctx,
+ .psr_data = data, .psr_datalen = len,
+ };
+
+ if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
+ ps_root_readerrorcb, &psr_ctx) == -1)
+ return -1;
+
+ eloop_enter(ctx->ps_eloop);
+ eloop_start(ctx->ps_eloop, &ctx->sigset);
+
+ errno = psr_ctx.psr_error.psr_errno;
+ return psr_ctx.psr_error.psr_result;
+}
+
+#ifdef PRIVSEP_GETIFADDRS
+static void
+ps_root_mreaderrorcb(void *arg)
+{
+ struct psr_ctx *psr_ctx = arg;
+ struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
+ struct psr_error *psr_error = &psr_ctx->psr_error;
+ struct iovec iov[] = {
+ { .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
+ { .iov_base = NULL, .iov_len = 0 },
+ };
+ ssize_t len;
+ int exit_code = EXIT_FAILURE;
+
+ len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK);
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len < sizeof(*psr_error))
+ PSR_ERROR(EINVAL);
+
+ if (psr_error->psr_datalen > SSIZE_MAX)
+ PSR_ERROR(ENOBUFS);
+ else if (psr_error->psr_datalen != 0) {
+ psr_ctx->psr_data = malloc(psr_error->psr_datalen);
+ if (psr_ctx->psr_data == NULL)
+ PSR_ERROR(errno);
+ psr_ctx->psr_datalen = psr_error->psr_datalen;
+ iov[1].iov_base = psr_ctx->psr_data;
+ iov[1].iov_len = psr_ctx->psr_datalen;
+ }
+
+ len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
+ if (len == -1)
+ PSR_ERROR(errno);
+ else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen)
+ PSR_ERROR(EINVAL);
+ exit_code = EXIT_SUCCESS;
+
+out:
+ eloop_exit(ctx->ps_eloop, exit_code);
+}
+
+ssize_t
+ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len)
+{
+ struct psr_ctx psr_ctx = {
+ .psr_ctx = ctx,
+ };
+
+ if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
+ ps_root_mreaderrorcb, &psr_ctx) == -1)
+ return -1;
+
+ eloop_enter(ctx->ps_eloop);
+ eloop_start(ctx->ps_eloop, &ctx->sigset);
+
+ errno = psr_ctx.psr_error.psr_errno;
+ *data = psr_ctx.psr_data;
+ *len = psr_ctx.psr_datalen;
+ return psr_ctx.psr_error.psr_result;
+}
+#endif
+
+static ssize_t
+ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result,
+ void *data, size_t len)
+{
+ struct psr_error psr = {
+ .psr_result = result,
+ .psr_errno = errno,
+ .psr_datalen = len,
+ };
+ struct iovec iov[] = {
+ { .iov_base = &psr, .iov_len = sizeof(psr) },
+ { .iov_base = data, .iov_len = len },
+ };
+
+#ifdef PRIVSEP_DEBUG
+ logdebugx("%s: result %zd errno %d", __func__, result, errno);
+#endif
+
+ return writev(ctx->ps_root_fd, iov, __arraycount(iov));
+}
+
+static ssize_t
+ps_root_doioctl(unsigned long req, void *data, size_t len)
+{
+ int s, err;
+
+ /* Only allow these ioctls */
+ switch(req) {
+#ifdef SIOCAIFADDR
+ case SIOCAIFADDR: /* FALLTHROUGH */
+ case SIOCDIFADDR: /* FALLTHROUGH */
+#endif
+#ifdef SIOCSIFHWADDR
+ case SIOCSIFHWADDR: /* FALLTHROUGH */
+#endif
+#ifdef SIOCGIFPRIORITY
+ case SIOCGIFPRIORITY: /* FALLTHROUGH */
+#endif
+ case SIOCSIFFLAGS: /* FALLTHROUGH */
+ case SIOCGIFMTU: /* FALLTHROUGH */
+ case SIOCSIFMTU:
+ break;
+ default:
+ errno = EPERM;
+ return -1;
+ }
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s != -1)
+#ifdef IOCTL_REQUEST_TYPE
+ {
+ ioctl_request_t reqt;
+
+ memcpy(&reqt, &req, sizeof(reqt));
+ err = ioctl(s, reqt, data, len);
+ }
+#else
+ err = ioctl(s, req, data, len);
+#endif
+ else
+ err = -1;
+ if (s != -1)
+ close(s);
+ return err;
+}
+
+static ssize_t
+ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
+{
+ const char *envbuf = data;
+ char * const argv[] = { ctx->script, NULL };
+ pid_t pid;
+ int status;
+
+ if (len == 0)
+ return 0;
+
+ if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL)
+ return -1;
+
+ pid = script_exec(argv, ctx->script_env);
+ if (pid == -1)
+ return -1;
+ /* Wait for the script to finish */
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ logerr(__func__);
+ status = 0;
+ break;
+ }
+ }
+ return status;
+}
+
+static bool
+ps_root_validpath(const struct dhcpcd_ctx *ctx, uint16_t cmd, const char *path)
+{
+
+ /* Avoid a previous directory attack to avoid /proc/../
+ * dhcpcd should never use a path with double dots. */
+ if (strstr(path, "..") != NULL)
+ return false;
+
+ if (cmd == PS_READFILE) {
+#ifdef EMBEDDED_CONFIG
+ if (strcmp(ctx->cffile, EMBEDDED_CONFIG) == 0)
+ return true;
+#endif
+ if (strcmp(ctx->cffile, path) == 0)
+ return true;
+ }
+ if (strncmp(DBDIR, path, strlen(DBDIR)) == 0)
+ return true;
+ if (strncmp(RUNDIR, path, strlen(RUNDIR)) == 0)
+ return true;
+
+#ifdef __linux__
+ if (strncmp("/proc/net/", path, strlen("/proc/net/")) == 0 ||
+ strncmp("/proc/sys/net/", path, strlen("/proc/sys/net/")) == 0 ||
+ strncmp("/sys/class/net/", path, strlen("/sys/class/net/")) == 0)
+ return true;
+#endif
+
+ errno = EPERM;
+ return false;
+}
+
+static ssize_t
+ps_root_dowritefile(const struct dhcpcd_ctx *ctx,
+ mode_t mode, void *data, size_t len)
+{
+ char *file = data, *nc;
+
+ nc = memchr(file, '\0', len);
+ if (nc == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!ps_root_validpath(ctx, PS_WRITEFILE, file))
+ return -1;
+ nc++;
+ return writefile(file, mode, nc, len - (size_t)(nc - file));
+}
+
+#ifdef AUTH
+static ssize_t
+ps_root_monordm(uint64_t *rdm, size_t len)
+{
+
+ if (len != sizeof(*rdm)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return auth_get_rdm_monotonic(rdm);
+}
+#endif
+
+#ifdef PRIVSEP_GETIFADDRS
+#define IFA_NADDRS 4
+static ssize_t
+ps_root_dogetifaddrs(void **rdata, size_t *rlen)
+{
+ struct ifaddrs *ifaddrs, *ifa;
+ size_t len;
+ uint8_t *buf, *sap;
+ socklen_t salen;
+
+ if (getifaddrs(&ifaddrs) == -1)
+ return -1;
+ if (ifaddrs == NULL) {
+ *rdata = NULL;
+ *rlen = 0;
+ return 0;
+ }
+
+ /* Work out the buffer length required.
+ * Ensure everything is aligned correctly, which does
+ * create a larger buffer than what is needed to send,
+ * but makes creating the same structure in the client
+ * much easier. */
+ len = 0;
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ len += ALIGN(sizeof(*ifa));
+ len += ALIGN(IFNAMSIZ);
+ len += ALIGN(sizeof(salen) * IFA_NADDRS);
+ if (ifa->ifa_addr != NULL)
+ len += ALIGN(sa_len(ifa->ifa_addr));
+ if (ifa->ifa_netmask != NULL)
+ len += ALIGN(sa_len(ifa->ifa_netmask));
+ if (ifa->ifa_broadaddr != NULL)
+ len += ALIGN(sa_len(ifa->ifa_broadaddr));
+#ifdef BSD
+ /*
+ * On BSD we need to carry ifa_data so we can access
+ * if_data->ifi_link_state
+ */
+ if (ifa->ifa_addr != NULL &&
+ ifa->ifa_addr->sa_family == AF_LINK)
+ len += ALIGN(sizeof(struct if_data));
+#endif
+ }
+
+ /* Use calloc to set everything to zero.
+ * This satisfies memory sanitizers because don't write
+ * where we don't need to. */
+ buf = calloc(1, len);
+ if (buf == NULL) {
+ freeifaddrs(ifaddrs);
+ return -1;
+ }
+ *rdata = buf;
+ *rlen = len;
+
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ memcpy(buf, ifa, sizeof(*ifa));
+ buf += ALIGN(sizeof(*ifa));
+
+ strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ);
+ buf += ALIGN(IFNAMSIZ);
+ sap = buf;
+ buf += ALIGN(sizeof(salen) * IFA_NADDRS);
+
+#define COPYINSA(addr) \
+ do { \
+ if ((addr) != NULL) \
+ salen = sa_len((addr)); \
+ else \
+ salen = 0; \
+ if (salen != 0) { \
+ memcpy(sap, &salen, sizeof(salen)); \
+ memcpy(buf, (addr), salen); \
+ buf += ALIGN(salen); \
+ } \
+ sap += sizeof(salen); \
+ } while (0 /*CONSTCOND */)
+
+ COPYINSA(ifa->ifa_addr);
+ COPYINSA(ifa->ifa_netmask);
+ COPYINSA(ifa->ifa_broadaddr);
+
+#ifdef BSD
+ if (ifa->ifa_addr != NULL &&
+ ifa->ifa_addr->sa_family == AF_LINK)
+ {
+ salen = (socklen_t)sizeof(struct if_data);
+ memcpy(buf, ifa->ifa_data, salen);
+ buf += ALIGN(salen);
+ } else
+#endif
+ salen = 0;
+ memcpy(sap, &salen, sizeof(salen));
+ }
+
+ freeifaddrs(ifaddrs);
+ return 0;
+}
+#endif
+
+static ssize_t
+ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ uint16_t cmd;
+ struct ps_process *psp;
+ struct iovec *iov = msg->msg_iov;
+ void *data = iov->iov_base, *rdata = NULL;
+ size_t len = iov->iov_len, rlen = 0;
+ uint8_t buf[PS_BUFLEN];
+ time_t mtime;
+ ssize_t err;
+ bool free_rdata = false;
+
+ cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
+ psp = ps_findprocess(ctx, &psm->ps_id);
+
+#ifdef PRIVSEP_DEBUG
+ logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
+#endif
+
+ if (psp != NULL) {
+ if (psm->ps_cmd & PS_STOP) {
+ int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
+
+ ps_freeprocess(psp);
+ return ret;
+ } else if (psm->ps_cmd & PS_START) {
+ /* Process has already started .... */
+ return 0;
+ }
+
+ err = ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg);
+ if (err == -1) {
+ logerr("%s: failed to send message to pid %d",
+ __func__, psp->psp_pid);
+ shutdown(psp->psp_fd, SHUT_RDWR);
+ close(psp->psp_fd);
+ psp->psp_fd = -1;
+ ps_freeprocess(psp);
+ }
+ return 0;
+ }
+
+ if (psm->ps_cmd & PS_STOP && psp == NULL)
+ return 0;
+
+ switch (cmd) {
+#ifdef INET
+#ifdef ARP
+ case PS_BPF_ARP: /* FALLTHROUGH */
+#endif
+ case PS_BPF_BOOTP:
+ return ps_bpf_cmd(ctx, psm, msg);
+#endif
+#ifdef INET
+ case PS_BOOTP:
+ return ps_inet_cmd(ctx, psm, msg);
+#endif
+#ifdef INET6
+#ifdef DHCP6
+ case PS_DHCP6: /* FALLTHROUGH */
+#endif
+ case PS_ND:
+ return ps_inet_cmd(ctx, psm, msg);
+#endif
+ default:
+ break;
+ }
+
+ assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1);
+
+ /* Reset errno */
+ errno = 0;
+
+ switch (psm->ps_cmd) {
+ case PS_IOCTL:
+ err = ps_root_doioctl(psm->ps_flags, data, len);
+ if (err != -1) {
+ rdata = data;
+ rlen = len;
+ }
+ break;
+ case PS_SCRIPT:
+ err = ps_root_run_script(ctx, data, len);
+ break;
+ case PS_UNLINK:
+ if (!ps_root_validpath(ctx, psm->ps_cmd, data)) {
+ err = -1;
+ break;
+ }
+ err = unlink(data);
+ break;
+ case PS_READFILE:
+ if (!ps_root_validpath(ctx, psm->ps_cmd, data)) {
+ err = -1;
+ break;
+ }
+ err = readfile(data, buf, sizeof(buf));
+ if (err != -1) {
+ rdata = buf;
+ rlen = (size_t)err;
+ }
+ break;
+ case PS_WRITEFILE:
+ err = ps_root_dowritefile(ctx, (mode_t)psm->ps_flags,
+ data, len);
+ break;
+ case PS_FILEMTIME:
+ err = filemtime(data, &mtime);
+ if (err != -1) {
+ rdata = &mtime;
+ rlen = sizeof(mtime);
+ }
+ break;
+ case PS_LOGREOPEN:
+ err = logopen(ctx->logfile);
+ break;
+#ifdef AUTH
+ case PS_AUTH_MONORDM:
+ err = ps_root_monordm(data, len);
+ if (err != -1) {
+ rdata = data;
+ rlen = len;
+ }
+ break;
+#endif
+#ifdef PRIVSEP_GETIFADDRS
+ case PS_GETIFADDRS:
+ err = ps_root_dogetifaddrs(&rdata, &rlen);
+ free_rdata = true;
+ break;
+#endif
+#if defined(INET6) && (defined(__linux__) || defined(HAVE_PLEDGE))
+ case PS_IP6FORWARDING:
+ err = ip6_forwarding(data);
+ break;
+#endif
+#ifdef PLUGIN_DEV
+ case PS_DEV_INITTED:
+ err = dev_initialised(ctx, data);
+ break;
+ case PS_DEV_LISTENING:
+ err = dev_listening(ctx);
+ break;
+#endif
+ default:
+ err = ps_root_os(psm, msg, &rdata, &rlen);
+ break;
+ }
+
+ err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen);
+ if (free_rdata)
+ free(rdata);
+ return err;
+}
+
+/* Receive from state engine, do an action. */
+static void
+ps_root_recvmsg(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1)
+ logerr(__func__);
+}
+
+#ifdef PLUGIN_DEV
+static int
+ps_root_handleinterface(void *arg, int action, const char *ifname)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ unsigned long flag;
+
+ if (action == 1)
+ flag = PS_DEV_IFADDED;
+ else if (action == -1)
+ flag = PS_DEV_IFREMOVED;
+ else if (action == 0)
+ flag = PS_DEV_IFUPDATED;
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return (int)ps_sendcmd(ctx, ctx->ps_data_fd, PS_DEV_IFCMD, flag,
+ ifname, strlen(ifname) + 1);
+}
+#endif
+
+static int
+ps_root_startcb(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ctx->options & DHCPCD_MANAGER)
+ setproctitle("[privileged proxy]");
+ else
+ setproctitle("[privileged proxy] %s%s%s",
+ ctx->ifv[0],
+ ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
+ ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
+ ctx->ps_root_pid = getpid();
+ ctx->options |= DHCPCD_PRIVSEPROOT;
+
+ /* Open network sockets for sending.
+ * This is a small bit wasteful for non sandboxed OS's
+ * but makes life very easy for unicasting DHCPv6 in non manager
+ * mode as we no longer care about address selection.
+ * We can't call shutdown SHUT_RD on the socket because it's
+ * not connectd. All we can do is try and set a zero sized
+ * receive buffer and just let it overflow.
+ * Reading from it just to drain it is a waste of CPU time. */
+#ifdef INET
+ if (ctx->options & DHCPCD_IPV4) {
+ int buflen = 1;
+
+ ctx->udp_wfd = xsocket(PF_INET,
+ SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
+ if (ctx->udp_wfd == -1)
+ logerr("%s: dhcp_openraw", __func__);
+ else if (setsockopt(ctx->udp_wfd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF DHCP", __func__);
+ }
+#endif
+#ifdef INET6
+ if (ctx->options & DHCPCD_IPV6) {
+ int buflen = 1;
+
+ ctx->nd_fd = ipv6nd_open(false);
+ if (ctx->nd_fd == -1)
+ logerr("%s: ipv6nd_open", __func__);
+ else if (setsockopt(ctx->nd_fd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF ND", __func__);
+ }
+#endif
+#ifdef DHCP6
+ if (ctx->options & DHCPCD_IPV6) {
+ int buflen = 1;
+
+ ctx->dhcp6_wfd = dhcp6_openraw();
+ if (ctx->dhcp6_wfd == -1)
+ logerr("%s: dhcp6_openraw", __func__);
+ else if (setsockopt(ctx->dhcp6_wfd, SOL_SOCKET, SO_RCVBUF,
+ &buflen, sizeof(buflen)) == -1)
+ logerr("%s: setsockopt SO_RCVBUF DHCP6", __func__);
+ }
+#endif
+
+#ifdef PLUGIN_DEV
+ /* Start any dev listening plugin which may want to
+ * change the interface name provided by the kernel */
+ if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_DEV)) ==
+ (DHCPCD_MANAGER | DHCPCD_DEV))
+ dev_start(ctx, ps_root_handleinterface);
+#endif
+
+ return 0;
+}
+
+static void
+ps_root_signalcb(int sig, __unused void *arg)
+{
+
+ if (sig == SIGCHLD) {
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ;
+ return;
+ }
+}
+
+int (*handle_interface)(void *, int, const char *);
+
+#ifdef PLUGIN_DEV
+static ssize_t
+ps_root_devcb(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ int action;
+ struct iovec *iov = msg->msg_iov;
+
+ if (msg->msg_iovlen != 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch(psm->ps_flags) {
+ case PS_DEV_IFADDED:
+ action = 1;
+ break;
+ case PS_DEV_IFREMOVED:
+ action = -1;
+ break;
+ case PS_DEV_IFUPDATED:
+ action = 0;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ return dhcpcd_handleinterface(ctx, action, iov->iov_base);
+}
+#endif
+
+static ssize_t
+ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+ ssize_t err;
+
+ switch(psm->ps_cmd) {
+#ifdef PLUGIN_DEV
+ case PS_DEV_IFCMD:
+ err = ps_root_devcb(ctx, psm, msg);
+ break;
+#endif
+ default:
+#ifdef INET
+ err = ps_bpf_dispatch(ctx, psm, msg);
+ if (err == -1 && errno == ENOTSUP)
+#endif
+ err = ps_inet_dispatch(ctx, psm, msg);
+ }
+ return err;
+}
+
+static void
+ps_root_dispatch(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1)
+ logerr(__func__);
+}
+
+static void
+ps_root_log(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (logreadfd(ctx->ps_log_fd) == -1)
+ logerr(__func__);
+}
+
+pid_t
+ps_root_start(struct dhcpcd_ctx *ctx)
+{
+ int logfd[2], datafd[2];
+ pid_t pid;
+
+ if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, logfd) == -1)
+ return -1;
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(logfd) == -1)
+ return -1;
+#endif
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, datafd) == -1)
+ return -1;
+ if (ps_setbuf_fdpair(datafd) == -1)
+ return -1;
+#ifdef PRIVSEP_RIGHTS
+ if (ps_rights_limit_fdpair(datafd) == -1)
+ return -1;
+#endif
+
+ pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd,
+ ps_root_recvmsg, NULL, ctx,
+ ps_root_startcb, ps_root_signalcb, 0);
+
+ if (pid == 0) {
+ ctx->ps_log_fd = logfd[1];
+ if (eloop_event_add(ctx->eloop, ctx->ps_log_fd,
+ ps_root_log, ctx) == -1)
+ return -1;
+ close(logfd[0]);
+ ctx->ps_data_fd = datafd[1];
+ close(datafd[0]);
+ return 0;
+ } else if (pid == -1)
+ return -1;
+
+ logsetfd(logfd[0]);
+ close(logfd[1]);
+
+ ctx->ps_data_fd = datafd[0];
+ close(datafd[1]);
+ if (eloop_event_add(ctx->eloop, ctx->ps_data_fd,
+ ps_root_dispatch, ctx) == -1)
+ return -1;
+
+ if ((ctx->ps_eloop = eloop_new()) == NULL)
+ return -1;
+
+ eloop_signal_set_cb(ctx->ps_eloop,
+ dhcpcd_signals, dhcpcd_signals_len,
+ ps_root_signalcb, ctx);
+
+ return pid;
+}
+
+int
+ps_root_stop(struct dhcpcd_ctx *ctx)
+{
+
+ return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd);
+}
+
+ssize_t
+ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_SCRIPT, 0, data, len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data,
+ size_t len)
+{
+#ifdef IOCTL_REQUEST_TYPE
+ unsigned long ulreq = 0;
+
+ memcpy(&ulreq, &req, sizeof(req));
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1)
+ return -1;
+#else
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1)
+ return -1;
+#endif
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file,
+ void *data, size_t len)
+{
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, data, len);
+}
+
+ssize_t
+ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode,
+ const void *data, size_t len)
+{
+ char buf[PS_BUFLEN];
+ size_t flen;
+
+ flen = strlcpy(buf, file, sizeof(buf));
+ flen += 1;
+ if (flen > sizeof(buf) || flen + len > sizeof(buf)) {
+ errno = ENOBUFS;
+ return -1;
+ }
+ memcpy(buf + flen, data, len);
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode,
+ buf, flen + len) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+ssize_t
+ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0,
+ file, strlen(file) + 1) == -1)
+ return -1;
+ return ps_root_readerror(ctx, time, sizeof(*time));
+}
+
+ssize_t
+ps_root_logreopen(struct dhcpcd_ctx *ctx)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_LOGREOPEN, 0, NULL, 0) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+
+#ifdef PRIVSEP_GETIFADDRS
+int
+ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead)
+{
+ struct ifaddrs *ifa;
+ void *buf = NULL;
+ char *bp, *sap;
+ socklen_t salen;
+ size_t len;
+ ssize_t err;
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd,
+ PS_GETIFADDRS, 0, NULL, 0) == -1)
+ return -1;
+ err = ps_root_mreaderror(ctx, &buf, &len);
+
+ if (err == -1)
+ return -1;
+
+ /* Should be impossible - lo0 will always exist. */
+ if (len == 0) {
+ *ifahead = NULL;
+ return 0;
+ }
+
+ bp = buf;
+ *ifahead = (struct ifaddrs *)(void *)bp;
+ for (ifa = *ifahead; ifa != NULL; ifa = ifa->ifa_next) {
+ if (len < ALIGN(sizeof(*ifa)) +
+ ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS))
+ goto err;
+ bp += ALIGN(sizeof(*ifa));
+ ifa->ifa_name = bp;
+ bp += ALIGN(IFNAMSIZ);
+ sap = bp;
+ bp += ALIGN(sizeof(salen) * IFA_NADDRS);
+ len -= ALIGN(sizeof(*ifa)) +
+ ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS);
+
+#define COPYOUTSA(addr) \
+ do { \
+ memcpy(&salen, sap, sizeof(salen)); \
+ if (len < salen) \
+ goto err; \
+ if (salen != 0) { \
+ (addr) = (struct sockaddr *)bp; \
+ bp += ALIGN(salen); \
+ len -= ALIGN(salen); \
+ } \
+ sap += sizeof(salen); \
+ } while (0 /* CONSTCOND */)
+
+ COPYOUTSA(ifa->ifa_addr);
+ COPYOUTSA(ifa->ifa_netmask);
+ COPYOUTSA(ifa->ifa_broadaddr);
+
+ memcpy(&salen, sap, sizeof(salen));
+ if (len < salen)
+ goto err;
+ if (salen != 0) {
+ ifa->ifa_data = bp;
+ bp += ALIGN(salen);
+ len -= ALIGN(salen);
+ } else
+ ifa->ifa_data = NULL;
+
+ if (len != 0)
+ ifa->ifa_next = (struct ifaddrs *)(void *)bp;
+ else
+ ifa->ifa_next = NULL;
+ }
+ return 0;
+
+err:
+ free(buf);
+ *ifahead = NULL;
+ errno = EINVAL;
+ return -1;
+}
+#endif
+
+#if defined(__linux__) || defined(HAVE_PLEDGE)
+ssize_t
+ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IP6FORWARDING, 0,
+ ifname, ifname != NULL ? strlen(ifname) + 1 : 0) == -1)
+ return -1;
+ return ps_root_readerror(ctx, NULL, 0);
+}
+#endif
+
+#ifdef AUTH
+int
+ps_root_getauthrdm(struct dhcpcd_ctx *ctx, uint64_t *rdm)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_AUTH_MONORDM, 0,
+ rdm, sizeof(*rdm))== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, rdm, sizeof(*rdm));
+}
+#endif
+
+#ifdef PLUGIN_DEV
+int
+ps_root_dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_INITTED, 0,
+ ifname, strlen(ifname) + 1)== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, NULL, 0);
+}
+
+int
+ps_root_dev_listening(struct dhcpcd_ctx * ctx)
+{
+
+ if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_LISTENING, 0, NULL, 0)== -1)
+ return -1;
+ return (int)ps_root_readerror(ctx, NULL, 0);
+}
+#endif