Home | History | Annotate | Line # | Download | only in src
      1 /* SPDX-License-Identifier: BSD-2-Clause */
      2 /*
      3  * Privilege Separation for dhcpcd, control proxy
      4  * Copyright (c) 2006-2025 Roy Marples <roy (at) marples.name>
      5  * All rights reserved
      6 
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #include <errno.h>
     30 #include <stdlib.h>
     31 #include <string.h>
     32 
     33 #include "dhcpcd.h"
     34 #include "control.h"
     35 #include "eloop.h"
     36 #include "logerr.h"
     37 #include "privsep.h"
     38 
     39 /* We expect to have open 2 SEQPACKET, 2 STREAM and 2 file STREAM fds */
     40 
     41 static int
     42 ps_ctl_startcb(struct ps_process *psp)
     43 {
     44 	struct dhcpcd_ctx *ctx = psp->psp_ctx;
     45 	sa_family_t af;
     46 
     47 	if (ctx->options & DHCPCD_MANAGER) {
     48 		setproctitle("[control proxy]");
     49 		af = AF_UNSPEC;
     50 	} else {
     51 		setproctitle("[control proxy] %s%s%s",
     52 		    ctx->ifv[0],
     53 		    ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
     54 		    ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
     55 		if ((ctx->options &
     56 		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4)
     57 			af = AF_INET;
     58 		else if ((ctx->options &
     59 		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6)
     60 			af = AF_INET6;
     61 		else
     62 			af = AF_UNSPEC;
     63 	}
     64 
     65 	return control_start(ctx,
     66 	    ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af);
     67 }
     68 
     69 static void
     70 ps_ctl_recvmsg(void *arg, unsigned short events)
     71 {
     72 	struct ps_process *psp = arg;
     73 
     74 	if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, NULL, NULL) == -1)
     75 		logerr(__func__);
     76 }
     77 
     78 ssize_t
     79 ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len)
     80 {
     81 
     82 	/* Make any change here in dhcpcd.c as well. */
     83 	if (strncmp(data, "--version",
     84 	    MIN(strlen("--version"), len)) == 0) {
     85 		return control_queue(fd, UNCONST(VERSION),
     86 		    strlen(VERSION) + 1);
     87 	} else if (strncmp(data, "--getconfigfile",
     88 	    MIN(strlen("--getconfigfile"), len)) == 0) {
     89 		return control_queue(fd, UNCONST(fd->ctx->cffile),
     90 		    strlen(fd->ctx->cffile) + 1);
     91 	} else if (strncmp(data, "--listen",
     92 	    MIN(strlen("--listen"), len)) == 0) {
     93 		fd->flags |= FD_LISTEN;
     94 		return 0;
     95 	}
     96 
     97 	if (fd->ctx->ps_control_client != NULL &&
     98 	    fd->ctx->ps_control_client != fd)
     99 	{
    100 		logerrx("%s: cannot handle another client", __func__);
    101 		return 0;
    102 	}
    103 	return 1;
    104 }
    105 
    106 static ssize_t
    107 ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
    108 {
    109 	struct dhcpcd_ctx *ctx = arg;
    110 	struct iovec *iov = msg->msg_iov;
    111 	struct fd_list *fd;
    112 	unsigned int fd_flags = FD_SENDLEN;
    113 
    114 	switch (psm->ps_flags) {
    115 	case PS_CTL_PRIV:
    116 		break;
    117 	case PS_CTL_UNPRIV:
    118 		fd_flags |= FD_UNPRIV;
    119 		break;
    120 	}
    121 
    122 	switch (psm->ps_cmd) {
    123 	case PS_CTL:
    124 		if (msg->msg_iovlen != 1) {
    125 			errno = EINVAL;
    126 			return -1;
    127 		}
    128 		if (ctx->ps_control_client != NULL) {
    129 			logerrx("%s: cannot handle another client", __func__);
    130 			return 0;
    131 		}
    132 		fd = control_new(ctx, ctx->ps_ctl->psp_work_fd, fd_flags);
    133 		if (fd == NULL)
    134 			return -1;
    135 		ctx->ps_control_client = fd;
    136 		control_recvdata(fd, iov->iov_base, iov->iov_len);
    137 		break;
    138 	case PS_CTL_EOF:
    139 		ctx->ps_control_client = NULL;
    140 		break;
    141 	default:
    142 		errno = ENOTSUP;
    143 		return -1;
    144 	}
    145 	return 0;
    146 }
    147 
    148 static void
    149 ps_ctl_dodispatch(void *arg, unsigned short events)
    150 {
    151 	struct ps_process *psp = arg;
    152 
    153 	if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events,
    154 	    ps_ctl_dispatch, psp->psp_ctx) == -1)
    155 		logerr(__func__);
    156 }
    157 
    158 static void
    159 ps_ctl_recv(void *arg, unsigned short events)
    160 {
    161 	struct dhcpcd_ctx *ctx = arg;
    162 	char buf[BUFSIZ];
    163 	ssize_t len;
    164 
    165 	if (!(events & (ELE_READ | ELE_HANGUP)))
    166 		logerrx("%s: unexpected event 0x%04x", __func__, events);
    167 
    168 	if (events & ELE_READ) {
    169 		len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf));
    170 		if (len == -1)
    171 			logerr("%s: read", __func__);
    172 		else if (len == 0)
    173 			// FIXME: Why does this happen?
    174 			;
    175 		else if (ctx->ps_control_client == NULL)
    176 			logerrx("%s: clientfd #%d disconnected (len=%zd)",
    177 			    __func__, ctx->ps_ctl->psp_work_fd, len);
    178 		else {
    179 			errno = 0;
    180 			if (control_queue(ctx->ps_control_client,
    181 			    buf, (size_t)len) == -1)
    182 				logerr("%s: control_queue", __func__);
    183 		}
    184 	}
    185 }
    186 
    187 static void
    188 ps_ctl_listen(void *arg, unsigned short events)
    189 {
    190 	struct dhcpcd_ctx *ctx = arg;
    191 	char buf[BUFSIZ];
    192 	ssize_t len;
    193 	struct fd_list *fd;
    194 
    195 	if (!(events & ELE_READ))
    196 		logerrx("%s: unexpected event 0x%04x", __func__, events);
    197 
    198 	len = read(ctx->ps_control->fd, buf, sizeof(buf));
    199 	if (len == -1) {
    200 		logerr("%s: read", __func__);
    201 		eloop_exit(ctx->eloop, EXIT_FAILURE);
    202 		return;
    203 	}
    204 
    205 	/* Send to our listeners */
    206 	TAILQ_FOREACH(fd, &ctx->control_fds, next) {
    207 		if (!(fd->flags & FD_LISTEN))
    208 			continue;
    209 		if (control_queue(fd, buf, (size_t)len)== -1)
    210 			logerr("%s: control_queue", __func__);
    211 	}
    212 }
    213 
    214 pid_t
    215 ps_ctl_start(struct dhcpcd_ctx *ctx)
    216 {
    217 	struct ps_id id = {
    218 		.psi_ifindex = 0,
    219 		.psi_cmd = PS_CTL,
    220 	};
    221 	struct ps_process *psp;
    222 	int work_fd[2], listen_fd[2];
    223 	pid_t pid;
    224 
    225 	if_closesockets(ctx);
    226 
    227 	if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, work_fd) == -1 ||
    228 	    xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1)
    229 		return -1;
    230 #ifdef PRIVSEP_RIGHTS
    231 	if (ps_rights_limit_fdpair(work_fd) == -1 ||
    232 	    ps_rights_limit_fdpair(listen_fd) == -1)
    233 		return -1;
    234 #endif
    235 
    236 	psp = ctx->ps_ctl = ps_newprocess(ctx, &id);
    237 	strlcpy(psp->psp_name, "control proxy", sizeof(psp->psp_name));
    238 	pid = ps_startprocess(psp, ps_ctl_recvmsg, ps_ctl_dodispatch,
    239 	    ps_ctl_startcb, PSF_DROPPRIVS);
    240 
    241 	if (pid == -1)
    242 		return -1;
    243 	else if (pid != 0) {
    244 		psp->psp_work_fd = work_fd[0];
    245 		close(work_fd[1]);
    246 		close(listen_fd[1]);
    247 		ctx->ps_control = control_new(ctx,
    248 		    listen_fd[0], FD_SENDLEN | FD_LISTEN);
    249 		if (ctx->ps_control == NULL)
    250 			return -1;
    251 		return pid;
    252 	}
    253 
    254 	close(work_fd[0]);
    255 	close(listen_fd[0]);
    256 
    257 	psp->psp_work_fd = work_fd[1];
    258 	if (eloop_event_add(ctx->eloop, psp->psp_work_fd, ELE_READ,
    259 	    ps_ctl_recv, ctx) == -1)
    260 		return -1;
    261 
    262 	ctx->ps_control = control_new(ctx, listen_fd[1], 0);
    263 	if (ctx->ps_control == NULL)
    264 		return -1;
    265 	if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ,
    266 	    ps_ctl_listen, ctx) == -1)
    267 		return -1;
    268 
    269 	ps_entersandbox("stdio inet", NULL);
    270 	return 0;
    271 }
    272 
    273 int
    274 ps_ctl_stop(struct dhcpcd_ctx *ctx)
    275 {
    276 
    277 	return ps_stopprocess(ctx->ps_ctl);
    278 }
    279 
    280 ssize_t
    281 ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len)
    282 {
    283 	struct dhcpcd_ctx *ctx = fd->ctx;
    284 
    285 	if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd)
    286 		logerrx("%s: cannot deal with another client", __func__);
    287 	ctx->ps_control_client = fd;
    288 	return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL,
    289 	    fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV,
    290 	    data, len);
    291 }
    292 
    293 ssize_t
    294 ps_ctl_sendeof(struct fd_list *fd)
    295 {
    296 	struct dhcpcd_ctx *ctx = fd->ctx;
    297 
    298 	return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL_EOF, 0, NULL, 0);
    299 }
    300