Changeset View
Changeset View
Standalone View
Standalone View
contrib/dhcpcd/src/privsep-root.c
- This file was added.
/* 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 |