1/* SPDX-License-Identifier: BSD-2-Clause */
2/*
3 * Privilege Separation for dhcpcd, control proxy
4 * Copyright (c) 2006-2023 Roy Marples <roy@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
41static int
42ps_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
69static void
70ps_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
78ssize_t
79ps_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
106static ssize_t
107ps_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
148static void
149ps_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
158static void
159ps_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
187static void
188ps_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
214pid_t
215ps_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, NULL, 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
273int
274ps_ctl_stop(struct dhcpcd_ctx *ctx)
275{
276
277	return ps_stopprocess(ctx->ps_ctl);
278}
279
280ssize_t
281ps_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
293ssize_t
294ps_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