1/*	$OpenBSD: privsep.c,v 1.77 2023/10/12 22:36:54 bluhm Exp $	*/
2
3/*
4 * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
5 * Copyright (c) 2016 Alexander Bluhm <bluhm@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <sys/queue.h>
21#include <sys/stat.h>
22#include <sys/wait.h>
23
24#include <err.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <limits.h>
28#include <netdb.h>
29#include <paths.h>
30#include <pwd.h>
31#include <signal.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <utmp.h>
37
38#include "log.h"
39#include "syslogd.h"
40
41/*
42 * syslogd can only go forward in these states; each state should represent
43 * less privilege.   After STATE_INIT, the child is allowed to parse its
44 * config file once, and communicate the information regarding what logfiles
45 * it needs access to back to the parent.  When that is done, it sends a
46 * message to the priv parent revoking this access, moving to STATE_RUNNING.
47 * In this state, any log-files not in the access list are rejected.
48 *
49 * This allows a HUP signal to the child to reopen its log files, and
50 * the config file to be parsed if it hasn't been changed (this is still
51 * useful to force resolution of remote syslog servers again).
52 * If the config file has been modified, then the child dies, and
53 * the priv parent restarts itself.
54 */
55enum priv_state {
56	STATE_INIT,		/* just started up */
57	STATE_CONFIG,		/* parsing config file for first time */
58	STATE_RUNNING,		/* running and accepting network traffic */
59	STATE_QUIT		/* shutting down */
60};
61
62enum cmd_types {
63	PRIV_OPEN_TTY,		/* open terminal or console device */
64	PRIV_OPEN_LOG,		/* open logfile for appending */
65	PRIV_OPEN_PIPE,		/* fork & exec child that gets logs on stdin */
66	PRIV_OPEN_UTMP,		/* open utmp for reading only */
67	PRIV_OPEN_CONFIG,	/* open config file for reading only */
68	PRIV_CONFIG_MODIFIED,	/* check if config file has been modified */
69	PRIV_GETADDRINFO,	/* resolve host/service names */
70	PRIV_GETNAMEINFO,	/* resolve numeric address into hostname */
71	PRIV_DONE_CONFIG_PARSE	/* signal that initial config parse is done */
72};
73
74static int priv_fd = -1;
75static volatile pid_t child_pid = -1;
76static volatile sig_atomic_t cur_state = STATE_INIT;
77
78/* Queue for the allowed logfiles */
79struct logname {
80	char path[PATH_MAX];
81	TAILQ_ENTRY(logname) next;
82};
83static TAILQ_HEAD(, logname) lognames;
84
85static void check_log_name(char *, size_t);
86static int open_file(char *);
87static int open_pipe(char *);
88static void check_tty_name(char *, size_t);
89static void increase_state(int);
90static void sig_pass_to_chld(int);
91static void sig_got_chld(int);
92static void must_read(int, void *, size_t);
93static void must_write(int, void *, size_t);
94static int  may_read(int, void *, size_t);
95
96static struct passwd *pw;
97
98void
99priv_init(int lockfd, int nullfd, int argc, char *argv[])
100{
101	int i, socks[2];
102	char *execpath, childnum[11], **privargv;
103
104	/* Create sockets */
105	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
106		err(1, "socketpair() failed");
107
108	pw = getpwnam("_syslogd");
109	if (pw == NULL)
110		errx(1, "unknown user _syslogd");
111
112	child_pid = fork();
113	if (child_pid == -1)
114		err(1, "fork() failed");
115
116	if (!child_pid) {
117		/* Child - drop privileges and return */
118		if (chroot(pw->pw_dir) != 0)
119			err(1, "chroot %s", pw->pw_dir);
120		if (chdir("/") != 0)
121			err(1, "chdir %s", pw->pw_dir);
122
123		if (setgroups(1, &pw->pw_gid) == -1)
124			err(1, "setgroups() failed");
125		if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
126			err(1, "setresgid() failed");
127		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
128			err(1, "setresuid() failed");
129		close(socks[0]);
130		priv_fd = socks[1];
131		return;
132	}
133	close(socks[1]);
134
135	if (strchr(argv[0], '/') == NULL)
136		execpath = argv[0];
137	else if ((execpath = realpath(argv[0], NULL)) == NULL)
138		err(1, "realpath %s", argv[0]);
139	if (chdir("/") != 0)
140		err(1, "chdir /");
141
142	if (!Debug) {
143		close(lockfd);
144		dup2(nullfd, STDIN_FILENO);
145		dup2(nullfd, STDOUT_FILENO);
146		dup2(nullfd, STDERR_FILENO);
147	}
148	if (nullfd > 2)
149		close(nullfd);
150
151	if (dup3(socks[0], 3, 0) == -1)
152		err(1, "dup3 priv sock failed");
153	if (closefrom(4) == -1)
154		err(1, "closefrom 4 failed");
155
156	snprintf(childnum, sizeof(childnum), "%d", child_pid);
157	if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL)
158		err(1, "alloc priv argv failed");
159	privargv[0] = execpath;
160	for (i = 1; i < argc; i++)
161		privargv[i] = argv[i];
162	privargv[i++] = "-P";
163	privargv[i++] = childnum;
164	privargv[i++] = NULL;
165	execvp(privargv[0], privargv);
166	err(1, "exec priv '%s' failed", privargv[0]);
167}
168
169__dead void
170priv_exec(char *conf, int numeric, int child, int argc, char *argv[])
171{
172	int i, fd, sock, cmd, addr_len, result, restart;
173	size_t path_len, protoname_len, hostname_len, servname_len;
174	char path[PATH_MAX], protoname[5];
175	char hostname[NI_MAXHOST], servname[NI_MAXSERV];
176	struct sockaddr_storage addr;
177	struct stat cf_info, cf_stat;
178	struct addrinfo hints, *res0;
179	struct sigaction sa;
180	sigset_t sigmask;
181
182	/* Redo the password lookup after re-exec of the privsep parent. */
183	pw = getpwnam("_syslogd");
184	if (pw == NULL)
185		errx(1, "unknown user _syslogd");
186
187	if (unveil(conf, "r") == -1)
188		err(1, "unveil %s", conf);
189	if (unveil(_PATH_UTMP, "r") == -1)
190		err(1, "unveil %s", _PATH_UTMP);
191	if (unveil(_PATH_DEV, "rw") == -1)
192		err(1, "unveil %s", _PATH_DEV);
193	if (unveil(_PATH_LOGPID, "c") == -1)
194		err(1, "unveil %s", _PATH_LOGPID);
195
196	/* for pipes */
197	if (unveil(_PATH_BSHELL, "x") == -1)
198		err(1, "unveil %s", _PATH_BSHELL);
199
200	/* For HUP / re-exec */
201	if (unveil("/usr/sbin/syslogd", "x") == -1)
202		err(1, "unveil /usr/sbin/syslogd");
203	if (argv[0][0] == '/')
204		if (unveil(argv[0], "x") == -1)
205			err(1, "unveil %s", argv[0]);
206
207	if (pledge("stdio unveil rpath wpath cpath dns sendfd id proc exec",
208	    NULL) == -1)
209		err(1, "pledge priv");
210
211	if (argc <= 2 || strcmp("-P", argv[argc - 2]) != 0)
212		errx(1, "exec without priv");
213	argv[argc -= 2] = NULL;
214
215	sock = 3;
216	closefrom(4);
217
218	child_pid = child;
219
220	memset(&sa, 0, sizeof(sa));
221	sigemptyset(&sa.sa_mask);
222	sa.sa_flags = SA_RESTART;
223	sa.sa_handler = SIG_DFL;
224	for (i = 1; i < _NSIG; i++)
225		sigaction(i, &sa, NULL);
226
227	/* Pass TERM/HUP/INT/QUIT through to child, and accept CHLD */
228	sa.sa_handler = sig_pass_to_chld;
229	sigaction(SIGTERM, &sa, NULL);
230	sigaction(SIGHUP, &sa, NULL);
231	sigaction(SIGINT, &sa, NULL);
232	sigaction(SIGQUIT, &sa, NULL);
233	sa.sa_handler = sig_got_chld;
234	sa.sa_flags |= SA_NOCLDSTOP;
235	sigaction(SIGCHLD, &sa, NULL);
236
237	setproctitle("[priv]");
238	log_debug("[priv]: fork+exec done");
239
240	sigemptyset(&sigmask);
241	if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
242		err(1, "sigprocmask priv");
243
244	if (stat(conf, &cf_info) == -1)
245		err(1, "stat config file failed");
246
247	TAILQ_INIT(&lognames);
248	increase_state(STATE_CONFIG);
249	restart = 0;
250
251	while (cur_state < STATE_QUIT) {
252		if (may_read(sock, &cmd, sizeof(int)))
253			break;
254		switch (cmd) {
255		case PRIV_OPEN_TTY:
256			log_debug("[priv]: msg PRIV_OPEN_TTY received");
257			/* Expecting: length, path */
258			must_read(sock, &path_len, sizeof(size_t));
259			if (path_len == 0 || path_len > sizeof(path))
260				_exit(1);
261			must_read(sock, &path, path_len);
262			path[path_len - 1] = '\0';
263			check_tty_name(path, sizeof(path));
264			fd = open(path, O_WRONLY|O_NONBLOCK);
265			send_fd(sock, fd);
266			if (fd == -1)
267				warnx("priv_open_tty failed");
268			else
269				close(fd);
270			break;
271
272		case PRIV_OPEN_LOG:
273		case PRIV_OPEN_PIPE:
274			log_debug("[priv]: msg PRIV_OPEN_%s received",
275			    cmd == PRIV_OPEN_PIPE ? "PIPE" : "LOG");
276			/* Expecting: length, path */
277			must_read(sock, &path_len, sizeof(size_t));
278			if (path_len == 0 || path_len > sizeof(path))
279				_exit(1);
280			must_read(sock, &path, path_len);
281			path[path_len - 1] = '\0';
282			check_log_name(path, sizeof(path));
283
284			if (cmd == PRIV_OPEN_LOG)
285				fd = open_file(path);
286			else if (cmd == PRIV_OPEN_PIPE)
287				fd = open_pipe(path);
288			else
289				errx(1, "invalid cmd");
290
291			send_fd(sock, fd);
292			if (fd == -1)
293				warnx("priv_open_log failed");
294			else
295				close(fd);
296			break;
297
298		case PRIV_OPEN_UTMP:
299			log_debug("[priv]: msg PRIV_OPEN_UTMP received");
300			fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK);
301			send_fd(sock, fd);
302			if (fd == -1)
303				warnx("priv_open_utmp failed");
304			else
305				close(fd);
306			break;
307
308		case PRIV_OPEN_CONFIG:
309			log_debug("[priv]: msg PRIV_OPEN_CONFIG received");
310			stat(conf, &cf_info);
311			fd = open(conf, O_RDONLY|O_NONBLOCK);
312			send_fd(sock, fd);
313			if (fd == -1)
314				warnx("priv_open_config failed");
315			else
316				close(fd);
317			break;
318
319		case PRIV_CONFIG_MODIFIED:
320			log_debug("[priv]: msg PRIV_CONFIG_MODIFIED received");
321			if (stat(conf, &cf_stat) == -1 ||
322			    timespeccmp(&cf_info.st_mtim,
323			    &cf_stat.st_mtim, <) ||
324			    cf_info.st_size != cf_stat.st_size) {
325				log_debug("config file modified: restarting");
326				restart = result = 1;
327				must_write(sock, &result, sizeof(int));
328			} else {
329				result = 0;
330				must_write(sock, &result, sizeof(int));
331			}
332			break;
333
334		case PRIV_DONE_CONFIG_PARSE:
335			if (pledge("stdio rpath wpath cpath dns sendfd id proc exec",
336			    NULL) == -1)
337				err(1, "pledge done config");
338			log_debug("[priv]: msg PRIV_DONE_CONFIG_PARSE "
339			    "received");
340			increase_state(STATE_RUNNING);
341			break;
342
343		case PRIV_GETADDRINFO:
344			log_debug("[priv]: msg PRIV_GETADDRINFO received");
345			/* Expecting: len, proto, len, host, len, serv */
346			must_read(sock, &protoname_len, sizeof(size_t));
347			if (protoname_len == 0 ||
348			    protoname_len > sizeof(protoname))
349				_exit(1);
350			must_read(sock, &protoname, protoname_len);
351			protoname[protoname_len - 1] = '\0';
352
353			must_read(sock, &hostname_len, sizeof(size_t));
354			if (hostname_len == 0 ||
355			    hostname_len > sizeof(hostname))
356				_exit(1);
357			must_read(sock, &hostname, hostname_len);
358			hostname[hostname_len - 1] = '\0';
359
360			must_read(sock, &servname_len, sizeof(size_t));
361			if (servname_len == 0 ||
362			    servname_len > sizeof(servname))
363				_exit(1);
364			must_read(sock, &servname, servname_len);
365			servname[servname_len - 1] = '\0';
366
367			memset(&hints, 0, sizeof(hints));
368			switch (strlen(protoname)) {
369			case 3:
370				hints.ai_family = AF_UNSPEC;
371				break;
372			case 4:
373				switch (protoname[3]) {
374				case '4':
375					hints.ai_family = AF_INET;
376					break;
377				case '6':
378					hints.ai_family = AF_INET6;
379					break;
380				default:
381					errx(1, "bad ip version %s", protoname);
382				}
383				break;
384			default:
385				errx(1, "bad protocol length %s", protoname);
386			}
387			if (strncmp(protoname, "udp", 3) == 0) {
388				hints.ai_socktype = SOCK_DGRAM;
389				hints.ai_protocol = IPPROTO_UDP;
390			} else if (strncmp(protoname, "tcp", 3) == 0) {
391				hints.ai_socktype = SOCK_STREAM;
392				hints.ai_protocol = IPPROTO_TCP;
393			} else {
394				errx(1, "unknown protocol %s", protoname);
395			}
396			i = getaddrinfo(hostname, servname, &hints, &res0);
397			if (i != 0 || res0 == NULL) {
398				addr_len = 0;
399				must_write(sock, &addr_len, sizeof(int));
400			} else {
401				/* Just send the first address */
402				i = res0->ai_addrlen;
403				must_write(sock, &i, sizeof(int));
404				must_write(sock, res0->ai_addr, i);
405				freeaddrinfo(res0);
406			}
407			break;
408
409		case PRIV_GETNAMEINFO:
410			log_debug("[priv]: msg PRIV_GETNAMEINFO received");
411			if (numeric)
412				errx(1, "rejected attempt to getnameinfo");
413			/* Expecting: length, sockaddr */
414			must_read(sock, &addr_len, sizeof(int));
415			if (addr_len <= 0 || (size_t)addr_len > sizeof(addr))
416				_exit(1);
417			must_read(sock, &addr, addr_len);
418			if (getnameinfo((struct sockaddr *)&addr, addr_len,
419			    hostname, sizeof(hostname), NULL, 0,
420			    NI_NOFQDN|NI_NAMEREQD|NI_DGRAM) != 0) {
421				addr_len = 0;
422				must_write(sock, &addr_len, sizeof(int));
423			} else {
424				addr_len = strlen(hostname) + 1;
425				must_write(sock, &addr_len, sizeof(int));
426				must_write(sock, hostname, addr_len);
427			}
428			break;
429		default:
430			errx(1, "unknown command %d", cmd);
431			break;
432		}
433	}
434
435	close(sock);
436
437	if (restart) {
438		int status;
439
440		waitpid(child_pid, &status, 0);
441		sigemptyset(&sigmask);
442		sigaddset(&sigmask, SIGHUP);
443		if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
444			err(1, "sigprocmask exec");
445		execvp(argv[0], argv);
446		err(1, "exec restart '%s' failed", argv[0]);
447	}
448	unlink(_PATH_LOGPID);
449	exit(0);
450}
451
452static int
453open_file(char *path)
454{
455	/* must not start with | */
456	if (path[0] == '|')
457		return (-1);
458
459	return (open(path, O_WRONLY|O_APPEND|O_NONBLOCK));
460}
461
462static int
463open_pipe(char *cmd)
464{
465	char *argp[] = {"sh", "-c", NULL, NULL};
466	int fd[2];
467	int bsize, flags;
468	pid_t pid;
469
470	/* skip over leading | and whitespace */
471	if (cmd[0] != '|')
472		return (-1);
473	for (cmd++; *cmd && *cmd == ' '; cmd++)
474		; /* nothing */
475	if (!*cmd)
476		return (-1);
477
478	argp[2] = cmd;
479
480	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) {
481		warnx("open_pipe");
482		return (-1);
483	}
484
485	/* make the fd on syslogd's side nonblocking */
486	if ((flags = fcntl(fd[1], F_GETFL)) == -1) {
487		warnx("fcntl");
488		return (-1);
489	}
490	flags |= O_NONBLOCK;
491	if ((flags = fcntl(fd[1], F_SETFL, flags)) == -1) {
492		warnx("fcntl");
493		return (-1);
494	}
495
496	switch (pid = fork()) {
497	case -1:
498		warnx("fork error");
499		return (-1);
500	case 0:
501		break;
502	default:
503		close(fd[0]);
504		return (fd[1]);
505	}
506
507	close(fd[1]);
508
509	/* grow receive buffer */
510	bsize = 65535;
511	while (bsize > 0 && setsockopt(fd[0], SOL_SOCKET, SO_RCVBUF,
512	    &bsize, sizeof(bsize)) == -1)
513		bsize /= 2;
514
515	if (setgroups(1, &pw->pw_gid) == -1 ||
516	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
517	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
518		err(1, "failure dropping privs");
519
520	if (dup2(fd[0], STDIN_FILENO) == -1)
521		err(1, "dup2 failed");
522	closefrom(STDERR_FILENO + 1);
523	if (execv("/bin/sh", argp) == -1)
524		err(1, "execv %s", cmd);
525	/* NOTREACHED */
526	return (-1);
527}
528
529/* Check that the terminal device is ok, and if not, rewrite to /dev/null.
530 * Either /dev/console or /dev/tty* are allowed.
531 */
532static void
533check_tty_name(char *tty, size_t ttysize)
534{
535	const char ttypre[] = "/dev/tty";
536	char *p;
537
538	/* Any path containing '..' is invalid.  */
539	for (p = tty; p + 1 < tty + ttysize && *p; p++)
540		if (*p == '.' && *(p + 1) == '.')
541			goto bad_path;
542
543	if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
544		goto bad_path;
545	return;
546
547bad_path:
548	warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
549	    "check_tty_name", tty);
550	strlcpy(tty, "/dev/null", ttysize);
551}
552
553/* If we are in the initial configuration state, accept a logname and add
554 * it to the list of acceptable logfiles.  Otherwise, check against this list
555 * and rewrite to /dev/null if it's a bad path.
556 */
557static void
558check_log_name(char *lognam, size_t logsize)
559{
560	struct logname *lg;
561	char *p;
562
563	/* Any path containing '..' is invalid.  */
564	for (p = lognam; p + 1 < lognam + logsize && *p; p++)
565		if (*p == '.' && *(p + 1) == '.')
566			goto bad_path;
567
568	switch (cur_state) {
569	case STATE_CONFIG:
570		lg = malloc(sizeof(struct logname));
571		if (!lg)
572			err(1, "check_log_name() malloc");
573		strlcpy(lg->path, lognam, PATH_MAX);
574		TAILQ_INSERT_TAIL(&lognames, lg, next);
575		if (lognam[0] != '|') {
576			if (unveil(lognam, "w") == -1)
577				goto bad_path;
578		}
579		break;
580	case STATE_RUNNING:
581		TAILQ_FOREACH(lg, &lognames, next)
582			if (!strcmp(lg->path, lognam))
583				return;
584		goto bad_path;
585		break;
586	default:
587		/* Any other state should just refuse the request */
588		goto bad_path;
589		break;
590	}
591	return;
592
593bad_path:
594	warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
595	    "check_log_name", lognam);
596	strlcpy(lognam, "/dev/null", logsize);
597}
598
599/* Crank our state into less permissive modes */
600static void
601increase_state(int state)
602{
603	if (state <= cur_state)
604		errx(1, "attempt to decrease or match current state");
605	if (state < STATE_INIT || state > STATE_QUIT)
606		errx(1, "attempt to switch to invalid state");
607	cur_state = state;
608}
609
610/* Open console or a terminal device for writing */
611int
612priv_open_tty(const char *tty)
613{
614	char path[PATH_MAX];
615	int cmd, fd;
616	size_t path_len;
617
618	if (priv_fd < 0)
619		errx(1, "%s: called from privileged portion", __func__);
620
621	if (strlcpy(path, tty, sizeof path) >= sizeof(path))
622		return -1;
623	path_len = strlen(path) + 1;
624
625	cmd = PRIV_OPEN_TTY;
626	must_write(priv_fd, &cmd, sizeof(int));
627	must_write(priv_fd, &path_len, sizeof(size_t));
628	must_write(priv_fd, path, path_len);
629	fd = receive_fd(priv_fd);
630	return fd;
631}
632
633/* Open log-file */
634int
635priv_open_log(const char *lognam)
636{
637	char path[PATH_MAX];
638	int cmd, fd;
639	size_t path_len;
640
641	if (priv_fd < 0)
642		errx(1, "%s: called from privileged child", __func__);
643
644	if (strlcpy(path, lognam, sizeof path) >= sizeof(path))
645		return -1;
646	path_len = strlen(path) + 1;
647
648	if (lognam[0] == '|')
649		cmd = PRIV_OPEN_PIPE;
650	else
651		cmd = PRIV_OPEN_LOG;
652	must_write(priv_fd, &cmd, sizeof(int));
653	must_write(priv_fd, &path_len, sizeof(size_t));
654	must_write(priv_fd, path, path_len);
655	fd = receive_fd(priv_fd);
656	return fd;
657}
658
659/* Open utmp for reading */
660FILE *
661priv_open_utmp(void)
662{
663	int cmd, fd;
664	FILE *fp;
665
666	if (priv_fd < 0)
667		errx(1, "%s: called from privileged portion", __func__);
668
669	cmd = PRIV_OPEN_UTMP;
670	must_write(priv_fd, &cmd, sizeof(int));
671	fd = receive_fd(priv_fd);
672	if (fd < 0)
673		return NULL;
674
675	fp = fdopen(fd, "r");
676	if (!fp) {
677		warn("priv_open_utmp: fdopen() failed");
678		close(fd);
679		return NULL;
680	}
681
682	return fp;
683}
684
685/* Open syslog config file for reading */
686FILE *
687priv_open_config(void)
688{
689	int cmd, fd;
690	FILE *fp;
691
692	if (priv_fd < 0)
693		errx(1, "%s: called from privileged portion", __func__);
694
695	cmd = PRIV_OPEN_CONFIG;
696	must_write(priv_fd, &cmd, sizeof(int));
697	fd = receive_fd(priv_fd);
698	if (fd < 0)
699		return NULL;
700
701	fp = fdopen(fd, "r");
702	if (!fp) {
703		warn("priv_open_config: fdopen() failed");
704		close(fd);
705		return NULL;
706	}
707
708	return fp;
709}
710
711/* Ask if config file has been modified since last attempt to read it */
712int
713priv_config_modified(void)
714{
715	int cmd, res;
716
717	if (priv_fd < 0)
718		errx(1, "%s: called from privileged portion", __func__);
719
720	cmd = PRIV_CONFIG_MODIFIED;
721	must_write(priv_fd, &cmd, sizeof(int));
722
723	/* Expect back integer signalling 1 for modification */
724	must_read(priv_fd, &res, sizeof(int));
725	return res;
726}
727
728/* Child can signal that its initial parsing is done, so that parent
729 * can revoke further logfile permissions.  This call only works once. */
730void
731priv_config_parse_done(void)
732{
733	int cmd;
734
735	if (priv_fd < 0)
736		errx(1, "%s: called from privileged portion", __func__);
737
738	cmd = PRIV_DONE_CONFIG_PARSE;
739	must_write(priv_fd, &cmd, sizeof(int));
740}
741
742/* Name/service to address translation.  Response is placed into addr.
743 * Return 0 for success or < 0 for error like getaddrinfo(3) */
744int
745priv_getaddrinfo(const char *proto, const char *host, const char *serv,
746    struct sockaddr *addr, size_t addr_len)
747{
748	char protocpy[5], hostcpy[NI_MAXHOST], servcpy[NI_MAXSERV];
749	int cmd, ret_len;
750	size_t protoname_len, hostname_len, servname_len;
751
752	if (priv_fd < 0)
753		errx(1, "%s: called from privileged portion", __func__);
754
755	if (strlcpy(protocpy, proto, sizeof(protocpy)) >= sizeof(protocpy))
756		errx(1, "%s: overflow attempt in protoname", __func__);
757	protoname_len = strlen(protocpy) + 1;
758	if (strlcpy(hostcpy, host, sizeof(hostcpy)) >= sizeof(hostcpy))
759		errx(1, "%s: overflow attempt in hostname", __func__);
760	hostname_len = strlen(hostcpy) + 1;
761	if (strlcpy(servcpy, serv, sizeof(servcpy)) >= sizeof(servcpy))
762		errx(1, "%s: overflow attempt in servname", __func__);
763	servname_len = strlen(servcpy) + 1;
764
765	cmd = PRIV_GETADDRINFO;
766	must_write(priv_fd, &cmd, sizeof(int));
767	must_write(priv_fd, &protoname_len, sizeof(size_t));
768	must_write(priv_fd, protocpy, protoname_len);
769	must_write(priv_fd, &hostname_len, sizeof(size_t));
770	must_write(priv_fd, hostcpy, hostname_len);
771	must_write(priv_fd, &servname_len, sizeof(size_t));
772	must_write(priv_fd, servcpy, servname_len);
773
774	/* Expect back an integer size, and then a string of that length */
775	must_read(priv_fd, &ret_len, sizeof(int));
776
777	/* Check there was no error (indicated by a return of 0) */
778	if (!ret_len)
779		return (-1);
780
781	/* Make sure we aren't overflowing the passed in buffer */
782	if (ret_len < 0 || (size_t)ret_len > addr_len)
783		errx(1, "%s: overflow attempt in return", __func__);
784
785	/* Read the resolved address and make sure we got all of it */
786	memset(addr, '\0', addr_len);
787	must_read(priv_fd, addr, ret_len);
788
789	return (0);
790}
791
792/* Reverse address resolution; response is placed into host.
793 * Return 0 for success or < 0 for error like getnameinfo(3) */
794int
795priv_getnameinfo(struct sockaddr *sa, socklen_t salen, char *host,
796    size_t hostlen)
797{
798	int cmd, ret_len;
799
800	if (priv_fd < 0)
801		errx(1, "%s called from privileged portion", __func__);
802
803	cmd = PRIV_GETNAMEINFO;
804	must_write(priv_fd, &cmd, sizeof(int));
805	must_write(priv_fd, &salen, sizeof(int));
806	must_write(priv_fd, sa, salen);
807
808	/* Expect back an integer size, and then a string of that length */
809	must_read(priv_fd, &ret_len, sizeof(int));
810
811	/* Check there was no error (indicated by a return of 0) */
812	if (!ret_len)
813		return (-1);
814
815	/* Check we don't overflow the passed in buffer */
816	if (ret_len < 0 || (size_t)ret_len > hostlen)
817		errx(1, "%s: overflow attempt in return", __func__);
818
819	/* Read the resolved hostname */
820	must_read(priv_fd, host, ret_len);
821	return (0);
822}
823
824/* Pass the signal through to child */
825static void
826sig_pass_to_chld(int sig)
827{
828	int save_errno = errno;
829
830	if (child_pid != -1)
831		kill(child_pid, sig);
832	errno = save_errno;
833}
834
835/* When child dies, move into the shutdown state */
836static void
837sig_got_chld(int sig)
838{
839	int save_errno = errno;
840	pid_t	pid;
841
842	do {
843		pid = waitpid(WAIT_ANY, NULL, WNOHANG);
844		if (pid == child_pid && cur_state < STATE_QUIT)
845			cur_state = STATE_QUIT;
846	} while (pid > 0 || (pid == -1 && errno == EINTR));
847	errno = save_errno;
848}
849
850/* Read all data or return 1 for error.  */
851static int
852may_read(int fd, void *buf, size_t n)
853{
854	char *s = buf;
855	ssize_t res;
856	size_t pos = 0;
857
858	while (n > pos) {
859		res = read(fd, s + pos, n - pos);
860		switch (res) {
861		case -1:
862			if (errno == EINTR || errno == EAGAIN)
863				continue;
864		case 0:
865			return (1);
866		default:
867			pos += res;
868		}
869	}
870	return (0);
871}
872
873/* Read data with the assertion that it all must come through, or
874 * else abort the process.  Based on atomicio() from openssh. */
875static void
876must_read(int fd, void *buf, size_t n)
877{
878	char *s = buf;
879	ssize_t res;
880	size_t pos = 0;
881
882	while (n > pos) {
883		res = read(fd, s + pos, n - pos);
884		switch (res) {
885		case -1:
886			if (errno == EINTR || errno == EAGAIN)
887				continue;
888		case 0:
889			_exit(1);
890		default:
891			pos += res;
892		}
893	}
894}
895
896/* Write data with the assertion that it all has to be written, or
897 * else abort the process.  Based on atomicio() from openssh. */
898static void
899must_write(int fd, void *buf, size_t n)
900{
901	char *s = buf;
902	ssize_t res;
903	size_t pos = 0;
904
905	while (n > pos) {
906		res = write(fd, s + pos, n - pos);
907		switch (res) {
908		case -1:
909			if (errno == EINTR || errno == EAGAIN)
910				continue;
911		case 0:
912			_exit(1);
913		default:
914			pos += res;
915		}
916	}
917}
918