xref: /dragonfly/contrib/dhcpcd/src/privsep.c (revision 5b8cd87c)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * Privilege Separation for dhcpcd
4  * Copyright (c) 2006-2020 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 /*
30  * The current design is this:
31  * Spawn a priv process to carry out privileged actions and
32  * spawning unpriv process to initate network connections such as BPF
33  * or address specific listener.
34  * Spawn an unpriv process to send/receive common network data.
35  * Then drop all privs and start running.
36  * Every process aside from the privileged actioneer is chrooted.
37  *
38  * dhcpcd will maintain the config file in the chroot, no need to handle
39  * this in a script or something.
40  */
41 
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 
47 #ifdef AF_LINK
48 #include <net/if_dl.h>
49 #endif
50 
51 #include <assert.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <grp.h>
55 #include <paths.h>
56 #include <pwd.h>
57 #include <stddef.h>	/* For offsetof, struct padding debug */
58 #include <signal.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62 
63 #include "arp.h"
64 #include "common.h"
65 #include "control.h"
66 #include "dev.h"
67 #include "dhcp.h"
68 #include "dhcp6.h"
69 #include "eloop.h"
70 #include "ipv6nd.h"
71 #include "logerr.h"
72 #include "privsep.h"
73 
74 #ifdef HAVE_CAPSICUM
75 #include <sys/capsicum.h>
76 #endif
77 #ifdef HAVE_UTIL_H
78 #include <util.h>
79 #endif
80 
81 int
82 ps_init(struct dhcpcd_ctx *ctx)
83 {
84 	struct passwd *pw;
85 	struct stat st;
86 
87 	errno = 0;
88 	if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) {
89 		ctx->options &= ~DHCPCD_PRIVSEP;
90 		if (errno == 0) {
91 			logerrx("no such user %s", PRIVSEP_USER);
92 			/* Just incase logerrx caused an error... */
93 			errno = 0;
94 		} else
95 			logerr("getpwnam");
96 		return -1;
97 	}
98 
99 	if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
100 		ctx->options &= ~DHCPCD_PRIVSEP;
101 		logerrx("refusing chroot: %s: %s",
102 		    PRIVSEP_USER, pw->pw_dir);
103 		errno = 0;
104 		return -1;
105 	}
106 
107 	ctx->options |= DHCPCD_PRIVSEP;
108 	return 0;
109 }
110 
111 int
112 ps_dropprivs(struct dhcpcd_ctx *ctx)
113 {
114 	struct passwd *pw = ctx->ps_user;
115 
116 	if (!(ctx->options & DHCPCD_FORKED))
117 		logdebugx("chrooting to `%s' as %s", pw->pw_dir, pw->pw_name);
118 	if (chroot(pw->pw_dir) == -1)
119 		logerr("%s: chroot `%s'", __func__, pw->pw_dir);
120 	if (chdir("/") == -1)
121 		logerr("%s: chdir `/'", __func__);
122 
123 	if (setgroups(1, &pw->pw_gid) == -1 ||
124 	     setgid(pw->pw_gid) == -1 ||
125 	     setuid(pw->pw_uid) == -1)
126 	{
127 		logerr("failed to drop privileges");
128 		return -1;
129 	}
130 
131 	return 0;
132 }
133 
134 static int
135 ps_setbuf0(int fd, int ctl, int minlen)
136 {
137 	int len;
138 	socklen_t slen;
139 
140 	slen = sizeof(len);
141 	if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1)
142 		return -1;
143 
144 #ifdef __linux__
145 	len /= 2;
146 #endif
147 	if (len >= minlen)
148 		return 0;
149 
150 	return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen));
151 }
152 
153 static int
154 ps_setbuf(int fd)
155 {
156 	/* Ensure we can receive a fully sized privsep message.
157 	 * Double the send buffer. */
158 	int minlen = (int)sizeof(struct ps_msg);
159 
160 	if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 ||
161 	    ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1)
162 	{
163 		logerr(__func__);
164 		return -1;
165 	}
166 	return 0;
167 }
168 
169 pid_t
170 ps_dostart(struct dhcpcd_ctx *ctx,
171     pid_t *priv_pid, int *priv_fd,
172     void (*recv_msg)(void *), void (*recv_unpriv_msg),
173     void *recv_ctx, int (*callback)(void *), void (*signal_cb)(int, void *),
174     unsigned int flags)
175 {
176 	int stype;
177 	int fd[2];
178 	pid_t pid;
179 #ifdef HAVE_CAPSICUM
180 	cap_rights_t rights;
181 
182 	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN);
183 #endif
184 
185 	stype = SOCK_CLOEXEC | SOCK_NONBLOCK;
186 	if (socketpair(AF_UNIX, SOCK_DGRAM | stype, 0, fd) == -1) {
187 		logerr("socketpair");
188 		return -1;
189 	}
190 
191 	switch (pid = fork()) {
192 	case -1:
193 		logerr("fork");
194 		return -1;
195 	case 0:
196 		*priv_fd = fd[1];
197 		close(fd[0]);
198 		ps_setbuf(*priv_fd);
199 		break;
200 	default:
201 		*priv_pid = pid;
202 		*priv_fd = fd[0];
203 		close(fd[1]);
204 		ps_setbuf(*priv_fd);
205 		if (recv_unpriv_msg == NULL)
206 			;
207 #ifdef HAVE_CAPSICUM
208 		else if (cap_rights_limit(*priv_fd, &rights) == -1
209 		    && errno != ENOSYS)
210 		{
211 			logerr("%s: cap_rights_limit", __func__);
212 			return -1;
213 		}
214 #endif
215 		else if (eloop_event_add(ctx->eloop, *priv_fd,
216 		    recv_unpriv_msg, recv_ctx) == -1)
217 		{
218 			logerr("%s: eloop_event_add", __func__);
219 			return -1;
220 		}
221 		return pid;
222 	}
223 
224 	ctx->options |= DHCPCD_UNPRIV | DHCPCD_FORKED;
225 	if (ctx->fork_fd != -1) {
226 		close(ctx->fork_fd);
227 		ctx->fork_fd = -1;
228 	}
229 	pidfile_clean();
230 	eloop_clear(ctx->eloop);
231 
232 	/* We are not root */
233 	if (priv_fd != &ctx->ps_root_fd) {
234 		ps_freeprocesses(ctx, recv_ctx);
235 		if (ctx->ps_root_fd != -1) {
236 			close(ctx->ps_root_fd);
237 			ctx->ps_root_fd = -1;
238 		}
239 	}
240 
241 	if (priv_fd != &ctx->ps_inet_fd && ctx->ps_inet_fd != -1) {
242 		close(ctx->ps_inet_fd);
243 		ctx->ps_inet_fd = -1;
244 	}
245 
246 	eloop_signal_set_cb(ctx->eloop,
247 	    dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx);
248 
249 	/* ctx->sigset aready has the initial sigmask set in main() */
250 	if (eloop_signal_mask(ctx->eloop, NULL) == -1) {
251 		logerr("%s: eloop_signal_mask", __func__);
252 		goto errexit;
253 	}
254 
255 #ifdef HAVE_CAPSICUM
256 	if (cap_rights_limit(*priv_fd, &rights) == -1 && errno != ENOSYS)
257 		goto errexit;
258 #endif
259 
260 	if (eloop_event_add(ctx->eloop, *priv_fd, recv_msg, recv_ctx) == -1)
261 	{
262 		logerr("%s: eloop_event_add", __func__);
263 		goto errexit;
264 	}
265 
266 	if (callback(recv_ctx) == -1)
267 		goto errexit;
268 
269 	if (!(ctx->options & DHCPCD_DEBUG) &&
270 	   (!(ctx->options & DHCPCD_TEST) || loggetopts() & LOGERR_QUIET))
271 	{
272 		(void)freopen(_PATH_DEVNULL, "w", stdout);
273 		(void)freopen(_PATH_DEVNULL, "w", stderr);
274 	}
275 
276 	if (flags & PSF_DROPPRIVS)
277 		ps_dropprivs(ctx);
278 
279 	return 0;
280 
281 errexit:
282 	/* Failure to start root or inet processes is fatal. */
283 	if (priv_fd == &ctx->ps_root_fd || priv_fd == &ctx->ps_inet_fd)
284 		(void)ps_sendcmd(ctx, *priv_fd, PS_STOP, 0, NULL, 0);
285 	shutdown(*priv_fd, SHUT_RDWR);
286 	*priv_fd = -1;
287 	eloop_exit(ctx->eloop, EXIT_FAILURE);
288 	return -1;
289 }
290 
291 int
292 ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd)
293 {
294 	int err = 0;
295 
296 #ifdef PRIVSEP_DEBUG
297 	logdebugx("%s: pid %d fd %d", __func__, *pid, *fd);
298 #endif
299 
300 	if (*fd != -1) {
301 		eloop_event_delete(ctx->eloop, *fd);
302 		if (ps_sendcmd(ctx, *fd, PS_STOP, 0, NULL, 0) == -1 ||
303 		    shutdown(*fd, SHUT_RDWR) == -1)
304 		{
305 			logerr(__func__);
306 			err = -1;
307 		}
308 		close(*fd);
309 		*fd = -1;
310 	}
311 
312 	/* Don't wait for the process as it may not respond to the shutdown
313 	 * request. We'll reap the process on receipt of SIGCHLD. */
314 	*pid = 0;
315 	return err;
316 }
317 
318 int
319 ps_start(struct dhcpcd_ctx *ctx)
320 {
321 	pid_t pid;
322 
323 	TAILQ_INIT(&ctx->ps_processes);
324 
325 	switch (pid = ps_root_start(ctx)) {
326 	case -1:
327 		logerr("ps_root_start");
328 		return -1;
329 	case 0:
330 		return 0;
331 	default:
332 		logdebugx("spawned privileged actioneer on PID %d", pid);
333 	}
334 
335 	/* No point in spawning the generic network listener if we're
336 	 * not going to use it. */
337 	if (!(ctx->options & (DHCPCD_MASTER | DHCPCD_IPV6)))
338 		goto started;
339 
340 	switch (pid = ps_inet_start(ctx)) {
341 	case -1:
342 		if (errno == ENXIO)
343 			return 0;
344 		return -1;
345 	case 0:
346 		return 0;
347 	default:
348 		logdebugx("spawned network proxy on PID %d", pid);
349 	}
350 
351 started:
352 
353 #ifdef ARC4RANDOM_H
354 	/* Seed the random number generator early incase it needs /dev/urandom
355 	 * which won't be available in the chroot. */
356 	arc4random();
357 #endif
358 
359 	return 1;
360 }
361 
362 int
363 ps_stop(struct dhcpcd_ctx *ctx)
364 {
365 	int r, ret = 0;
366 
367 	if (!(ctx->options & DHCPCD_PRIVSEP) ||
368 	    ctx->options & DHCPCD_FORKED ||
369 	    ctx->eloop == NULL)
370 		return 0;
371 
372 	r = ps_inet_stop(ctx);
373 	if (r != 0)
374 		ret = r;
375 
376 	/* We've been chrooted, so we need to tell the
377 	 * privileged actioneer to remove the pidfile. */
378 	ps_root_unlink(ctx, ctx->pidfile);
379 
380 	r = ps_root_stop(ctx);
381 	if (r != 0)
382 		ret = r;
383 
384 	ctx->options &= ~DHCPCD_PRIVSEP;
385 	return ret;
386 }
387 
388 void
389 ps_freeprocess(struct ps_process *psp)
390 {
391 
392 	TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next);
393 	if (psp->psp_fd != -1) {
394 		eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd);
395 		close(psp->psp_fd);
396 	}
397 	if (psp->psp_work_fd != -1) {
398 		eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd);
399 		close(psp->psp_work_fd);
400 	}
401 #ifdef INET
402 	if (psp->psp_bpf != NULL)
403 		bpf_close(psp->psp_bpf);
404 #endif
405 	free(psp);
406 }
407 
408 static void
409 ps_free(struct dhcpcd_ctx *ctx)
410 {
411 	struct ps_process *psp;
412 	bool stop = ctx->ps_root_pid == getpid();
413 
414 	while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) {
415 		if (stop)
416 			ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
417 		ps_freeprocess(psp);
418 	}
419 }
420 
421 int
422 ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm,
423     const void *data, size_t len)
424 {
425 	uint8_t *datap, *namep, *controlp;
426 
427 	namep = UNCONST(data);
428 	controlp = namep + psm->ps_namelen;
429 	datap = controlp + psm->ps_controllen;
430 
431 	if (psm->ps_namelen != 0) {
432 		if (psm->ps_namelen > len) {
433 			errno = EINVAL;
434 			return -1;
435 		}
436 		msg->msg_name = namep;
437 		len -= psm->ps_namelen;
438 	} else
439 		msg->msg_name = NULL;
440 	msg->msg_namelen = psm->ps_namelen;
441 
442 	if (psm->ps_controllen != 0) {
443 		if (psm->ps_controllen > len) {
444 			errno = EINVAL;
445 			return -1;
446 		}
447 		msg->msg_control = controlp;
448 		len -= psm->ps_controllen;
449 	} else
450 		msg->msg_control = NULL;
451 	msg->msg_controllen = psm->ps_controllen;
452 
453 	if (len != 0) {
454 		msg->msg_iovlen = 1;
455 		msg->msg_iov[0].iov_base = datap;
456 		msg->msg_iov[0].iov_len = len;
457 	} else {
458 		msg->msg_iovlen = 0;
459 		msg->msg_iov[0].iov_base = NULL;
460 		msg->msg_iov[0].iov_len = 0;
461 	}
462 	return 0;
463 }
464 
465 ssize_t
466 ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd,
467     struct ps_msghdr *psm, const struct msghdr *msg)
468 {
469 	struct iovec iov[] = {
470 		{ .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) },
471 		{ .iov_base = NULL, },	/* name */
472 		{ .iov_base = NULL, },	/* control */
473 		{ .iov_base = NULL, },	/* payload 1 */
474 		{ .iov_base = NULL, },	/* payload 2 */
475 		{ .iov_base = NULL, },	/* payload 3 */
476 	};
477 	int iovlen;
478 	ssize_t len;
479 
480 	if (msg != NULL) {
481 		struct iovec *iovp = &iov[1];
482 		int i;
483 
484 		psm->ps_namelen = msg->msg_namelen;
485 		psm->ps_controllen = (socklen_t)msg->msg_controllen;
486 
487 		iovp->iov_base = msg->msg_name;
488 		iovp->iov_len = msg->msg_namelen;
489 		iovp++;
490 		iovp->iov_base = msg->msg_control;
491 		iovp->iov_len = msg->msg_controllen;
492 		iovlen = 3;
493 
494 		for (i = 0; i < (int)msg->msg_iovlen; i++) {
495 			if ((size_t)(iovlen + i) > __arraycount(iov)) {
496 				errno =	ENOBUFS;
497 				return -1;
498 			}
499 			iovp++;
500 			iovp->iov_base = msg->msg_iov[i].iov_base;
501 			iovp->iov_len = msg->msg_iov[i].iov_len;
502 		}
503 		iovlen += i;
504 	} else
505 		iovlen = 1;
506 
507 	len = writev(fd, iov, iovlen);
508 #ifdef PRIVSEP_DEBUG
509 	logdebugx("%s: %zd", __func__, len);
510 #endif
511 	if ((len == -1 || len == 0) && ctx->options & DHCPCD_FORKED &&
512 	    !(ctx->options & DHCPCD_PRIVSEPROOT))
513 		eloop_exit(ctx->eloop, len == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
514 	return len;
515 }
516 
517 ssize_t
518 ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd,
519     struct ps_msghdr *psm, const void *data, size_t len)
520 {
521 	struct iovec iov[] = {
522 		{ .iov_base = UNCONST(data), .iov_len = len },
523 	};
524 	struct msghdr msg = {
525 		.msg_iov = iov, .msg_iovlen = 1,
526 	};
527 
528 	return ps_sendpsmmsg(ctx, fd, psm, &msg);
529 }
530 
531 
532 ssize_t
533 ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
534     const struct msghdr *msg)
535 {
536 	struct ps_msghdr psm = {
537 		.ps_cmd = cmd,
538 		.ps_flags = flags,
539 		.ps_namelen = msg->msg_namelen,
540 		.ps_controllen = (socklen_t)msg->msg_controllen,
541 	};
542 	size_t i;
543 
544 	for (i = 0; i < (size_t)msg->msg_iovlen; i++)
545 		psm.ps_datalen += msg->msg_iov[i].iov_len;
546 
547 #if 0	/* For debugging structure padding. */
548 	logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family));
549 	logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad));
550 	logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u));
551 	logerrx("psa %zu", sizeof(psm.ps_id.psi_addr));
552 
553 	logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr));
554 	logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex));
555 	logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd));
556 	logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad));
557 	logerrx("psi %zu", sizeof(struct ps_id));
558 
559 	logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd));
560 	logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad));
561 	logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags));
562 
563 	logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id));
564 
565 	logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen));
566 	logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen));
567 	logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2));
568 	logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen));
569 	logerrx("psm %zu", sizeof(psm));
570 #endif
571 
572 	return ps_sendpsmmsg(ctx, fd, &psm, msg);
573 }
574 
575 ssize_t
576 ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
577     const void *data, size_t len)
578 {
579 	struct ps_msghdr psm = {
580 		.ps_cmd = cmd,
581 		.ps_flags = flags,
582 	};
583 	struct iovec iov[] = {
584 		{ .iov_base = UNCONST(data), .iov_len = len }
585 	};
586 	struct msghdr msg = {
587 		.msg_iov = iov, .msg_iovlen = 1,
588 	};
589 
590 	return ps_sendpsmmsg(ctx, fd, &psm, &msg);
591 }
592 
593 static ssize_t
594 ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg)
595 {
596 	struct ps_msghdr psm = { .ps_cmd = cmd };
597 	uint8_t data[PS_BUFLEN], *p = data;
598 	struct iovec iov[] = {
599 		{ .iov_base = &psm, .iov_len = sizeof(psm) },
600 		{ .iov_base = data, .iov_len = 0 },
601 	};
602 	size_t dl = sizeof(data);
603 
604 	if (msg->msg_namelen != 0) {
605 		if (msg->msg_namelen > dl)
606 			goto nobufs;
607 		psm.ps_namelen = msg->msg_namelen;
608 		memcpy(p, msg->msg_name, msg->msg_namelen);
609 		p += msg->msg_namelen;
610 		dl -= msg->msg_namelen;
611 	}
612 
613 	if (msg->msg_controllen != 0) {
614 		if (msg->msg_controllen > dl)
615 			goto nobufs;
616 		psm.ps_controllen = (socklen_t)msg->msg_controllen;
617 		memcpy(p, msg->msg_control, msg->msg_controllen);
618 		p += msg->msg_controllen;
619 		dl -= msg->msg_controllen;
620 	}
621 
622 	psm.ps_datalen = msg->msg_iov[0].iov_len;
623 	if (psm.ps_datalen > dl)
624 		goto nobufs;
625 
626 	iov[1].iov_len = psm.ps_namelen + psm.ps_controllen + psm.ps_datalen;
627 	if (psm.ps_datalen != 0)
628 		memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen);
629 	return writev(fd, iov, __arraycount(iov));
630 
631 nobufs:
632 	errno = ENOBUFS;
633 	return -1;
634 }
635 
636 ssize_t
637 ps_recvmsg(struct dhcpcd_ctx *ctx, int rfd, uint16_t cmd, int wfd)
638 {
639 	struct sockaddr_storage ss = { .ss_family = AF_UNSPEC };
640 	uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 };
641 	uint8_t databuf[64 * 1024];
642 	struct iovec iov[] = {
643 	    { .iov_base = databuf, .iov_len = sizeof(databuf) }
644 	};
645 	struct msghdr msg = {
646 		.msg_name = &ss, .msg_namelen = sizeof(ss),
647 		.msg_control = controlbuf, .msg_controllen = sizeof(controlbuf),
648 		.msg_iov = iov, .msg_iovlen = 1,
649 	};
650 
651 	ssize_t len = recvmsg(rfd, &msg, 0);
652 #ifdef PRIVSEP_DEBUG
653 	logdebugx("%s: recv fd %d, %zd bytes", __func__, rfd, len);
654 #endif
655 
656 	if (len == -1 || len == 0) {
657 		if (ctx->options & DHCPCD_FORKED &&
658 		    !(ctx->options & DHCPCD_PRIVSEPROOT))
659 			eloop_exit(ctx->eloop,
660 			    len == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
661 		return len;
662 	}
663 
664 	iov[0].iov_len = (size_t)len;
665 	len = ps_sendcmdmsg(wfd, cmd, &msg);
666 #ifdef PRIVSEP_DEBUG
667 	logdebugx("%s: send fd %d, %zu bytes", __func__, wfd, len);
668 #endif
669 	if ((len == -1 || len == 0) && ctx->options & DHCPCD_FORKED &&
670 	    !(ctx->options & DHCPCD_PRIVSEPROOT))
671 		eloop_exit(ctx->eloop, len == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
672 	return len;
673 }
674 
675 ssize_t
676 ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd,
677     ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *),
678     void *cbctx)
679 {
680 	struct ps_msg psm;
681 	ssize_t len;
682 	size_t dlen;
683 	struct iovec iov[1];
684 	struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 };
685 	bool stop = false;
686 
687 	len = read(fd, &psm, sizeof(psm));
688 #ifdef PRIVSEP_DEBUG
689 	logdebugx("%s: %zd", __func__, len);
690 #endif
691 
692 	if (len == -1 || len == 0)
693 		stop = true;
694 	else {
695 		dlen = (size_t)len;
696 		if (dlen < sizeof(psm.psm_hdr)) {
697 			errno = EINVAL;
698 			return -1;
699 		}
700 
701 		if (psm.psm_hdr.ps_cmd == PS_STOP) {
702 			stop = true;
703 			len = 0;
704 		}
705 	}
706 
707 	if (stop) {
708 #ifdef PRIVSEP_DEBUG
709 		logdebugx("process %d stopping", getpid());
710 #endif
711 		ps_free(ctx);
712 #ifdef PLUGIN_DEV
713 		dev_stop(ctx);
714 #endif
715 		eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
716 		return len;
717 	}
718 	dlen -= sizeof(psm.psm_hdr);
719 
720 	if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1)
721 		return -1;
722 
723 	if (callback == NULL)
724 		return 0;
725 
726 	errno = 0;
727 	return callback(cbctx, &psm.psm_hdr, &msg);
728 }
729 
730 struct ps_process *
731 ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
732 {
733 	struct ps_process *psp;
734 
735 	TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
736 		if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0)
737 			return psp;
738 	}
739 	errno = ESRCH;
740 	return NULL;
741 }
742 
743 struct ps_process *
744 ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
745 {
746 	struct ps_process *psp;
747 
748 	psp = calloc(1, sizeof(*psp));
749 	if (psp == NULL)
750 		return NULL;
751 	psp->psp_ctx = ctx;
752 	memcpy(&psp->psp_id, psid, sizeof(psp->psp_id));
753 	psp->psp_work_fd = -1;
754 	TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next);
755 	return psp;
756 }
757 
758 void
759 ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis)
760 {
761 	struct ps_process *psp, *psn;
762 
763 	TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) {
764 		if (psp == notthis)
765 			continue;
766 		ps_freeprocess(psp);
767 	}
768 }
769