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