[BACK]Return to privsep.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / syslogd

File: [local] / src / usr.sbin / syslogd / privsep.c (download)

Revision 1.77, Thu Oct 12 22:36:54 2023 UTC (7 months, 2 weeks ago) by bluhm
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.76: +3 -3 lines

Retry DNS lookup for remote loghost.

If DNS lookup for a remote loghost configured in syslog.conf did
not work at startup, the entry was ignored.  Better retry the lookup
in intervals until it succeeds.  Improve debug output to print IP
address after resolution.  Unify retry code that resolves DNS for
UDP and connects to TCP server.

testing and feedback from Paul de Weerd; OK deraadt@

/*	$OpenBSD: privsep.c,v 1.77 2023/10/12 22:36:54 bluhm Exp $	*/

/*
 * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
 * Copyright (c) 2016 Alexander Bluhm <bluhm@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmp.h>

#include "log.h"
#include "syslogd.h"

/*
 * syslogd can only go forward in these states; each state should represent
 * less privilege.   After STATE_INIT, the child is allowed to parse its
 * config file once, and communicate the information regarding what logfiles
 * it needs access to back to the parent.  When that is done, it sends a
 * message to the priv parent revoking this access, moving to STATE_RUNNING.
 * In this state, any log-files not in the access list are rejected.
 *
 * This allows a HUP signal to the child to reopen its log files, and
 * the config file to be parsed if it hasn't been changed (this is still
 * useful to force resolution of remote syslog servers again).
 * If the config file has been modified, then the child dies, and
 * the priv parent restarts itself.
 */
enum priv_state {
	STATE_INIT,		/* just started up */
	STATE_CONFIG,		/* parsing config file for first time */
	STATE_RUNNING,		/* running and accepting network traffic */
	STATE_QUIT		/* shutting down */
};

enum cmd_types {
	PRIV_OPEN_TTY,		/* open terminal or console device */
	PRIV_OPEN_LOG,		/* open logfile for appending */
	PRIV_OPEN_PIPE,		/* fork & exec child that gets logs on stdin */
	PRIV_OPEN_UTMP,		/* open utmp for reading only */
	PRIV_OPEN_CONFIG,	/* open config file for reading only */
	PRIV_CONFIG_MODIFIED,	/* check if config file has been modified */
	PRIV_GETADDRINFO,	/* resolve host/service names */
	PRIV_GETNAMEINFO,	/* resolve numeric address into hostname */
	PRIV_DONE_CONFIG_PARSE	/* signal that initial config parse is done */
};

static int priv_fd = -1;
static volatile pid_t child_pid = -1;
static volatile sig_atomic_t cur_state = STATE_INIT;

/* Queue for the allowed logfiles */
struct logname {
	char path[PATH_MAX];
	TAILQ_ENTRY(logname) next;
};
static TAILQ_HEAD(, logname) lognames;

static void check_log_name(char *, size_t);
static int open_file(char *);
static int open_pipe(char *);
static void check_tty_name(char *, size_t);
static void increase_state(int);
static void sig_pass_to_chld(int);
static void sig_got_chld(int);
static void must_read(int, void *, size_t);
static void must_write(int, void *, size_t);
static int  may_read(int, void *, size_t);

static struct passwd *pw;

void
priv_init(int lockfd, int nullfd, int argc, char *argv[])
{
	int i, socks[2];
	char *execpath, childnum[11], **privargv;

	/* Create sockets */
	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
		err(1, "socketpair() failed");

	pw = getpwnam("_syslogd");
	if (pw == NULL)
		errx(1, "unknown user _syslogd");

	child_pid = fork();
	if (child_pid == -1)
		err(1, "fork() failed");

	if (!child_pid) {
		/* Child - drop privileges and return */
		if (chroot(pw->pw_dir) != 0)
			err(1, "chroot %s", pw->pw_dir);
		if (chdir("/") != 0)
			err(1, "chdir %s", pw->pw_dir);

		if (setgroups(1, &pw->pw_gid) == -1)
			err(1, "setgroups() failed");
		if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
			err(1, "setresgid() failed");
		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
			err(1, "setresuid() failed");
		close(socks[0]);
		priv_fd = socks[1];
		return;
	}
	close(socks[1]);

	if (strchr(argv[0], '/') == NULL)
		execpath = argv[0];
	else if ((execpath = realpath(argv[0], NULL)) == NULL)
		err(1, "realpath %s", argv[0]);
	if (chdir("/") != 0)
		err(1, "chdir /");

	if (!Debug) {
		close(lockfd);
		dup2(nullfd, STDIN_FILENO);
		dup2(nullfd, STDOUT_FILENO);
		dup2(nullfd, STDERR_FILENO);
	}
	if (nullfd > 2)
		close(nullfd);

	if (dup3(socks[0], 3, 0) == -1)
		err(1, "dup3 priv sock failed");
	if (closefrom(4) == -1)
		err(1, "closefrom 4 failed");

	snprintf(childnum, sizeof(childnum), "%d", child_pid);
	if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL)
		err(1, "alloc priv argv failed");
	privargv[0] = execpath;
	for (i = 1; i < argc; i++)
		privargv[i] = argv[i];
	privargv[i++] = "-P";
	privargv[i++] = childnum;
	privargv[i++] = NULL;
	execvp(privargv[0], privargv);
	err(1, "exec priv '%s' failed", privargv[0]);
}

__dead void
priv_exec(char *conf, int numeric, int child, int argc, char *argv[])
{
	int i, fd, sock, cmd, addr_len, result, restart;
	size_t path_len, protoname_len, hostname_len, servname_len;
	char path[PATH_MAX], protoname[5];
	char hostname[NI_MAXHOST], servname[NI_MAXSERV];
	struct sockaddr_storage addr;
	struct stat cf_info, cf_stat;
	struct addrinfo hints, *res0;
	struct sigaction sa;
	sigset_t sigmask;

	/* Redo the password lookup after re-exec of the privsep parent. */
	pw = getpwnam("_syslogd");
	if (pw == NULL)
		errx(1, "unknown user _syslogd");

	if (unveil(conf, "r") == -1)
		err(1, "unveil %s", conf);
	if (unveil(_PATH_UTMP, "r") == -1)
		err(1, "unveil %s", _PATH_UTMP);
	if (unveil(_PATH_DEV, "rw") == -1)
		err(1, "unveil %s", _PATH_DEV);
	if (unveil(_PATH_LOGPID, "c") == -1)
		err(1, "unveil %s", _PATH_LOGPID);

	/* for pipes */
	if (unveil(_PATH_BSHELL, "x") == -1)
		err(1, "unveil %s", _PATH_BSHELL);

	/* For HUP / re-exec */
	if (unveil("/usr/sbin/syslogd", "x") == -1)
		err(1, "unveil /usr/sbin/syslogd");
	if (argv[0][0] == '/')
		if (unveil(argv[0], "x") == -1)
			err(1, "unveil %s", argv[0]);

	if (pledge("stdio unveil rpath wpath cpath dns sendfd id proc exec",
	    NULL) == -1)
		err(1, "pledge priv");

	if (argc <= 2 || strcmp("-P", argv[argc - 2]) != 0)
		errx(1, "exec without priv");
	argv[argc -= 2] = NULL;

	sock = 3;
	closefrom(4);

	child_pid = child;

	memset(&sa, 0, sizeof(sa));
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	sa.sa_handler = SIG_DFL;
	for (i = 1; i < _NSIG; i++)
		sigaction(i, &sa, NULL);

	/* Pass TERM/HUP/INT/QUIT through to child, and accept CHLD */
	sa.sa_handler = sig_pass_to_chld;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGHUP, &sa, NULL);
	sigaction(SIGINT, &sa, NULL);
	sigaction(SIGQUIT, &sa, NULL);
	sa.sa_handler = sig_got_chld;
	sa.sa_flags |= SA_NOCLDSTOP;
	sigaction(SIGCHLD, &sa, NULL);

	setproctitle("[priv]");
	log_debug("[priv]: fork+exec done");

	sigemptyset(&sigmask);
	if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
		err(1, "sigprocmask priv");

	if (stat(conf, &cf_info) == -1)
		err(1, "stat config file failed");

	TAILQ_INIT(&lognames);
	increase_state(STATE_CONFIG);
	restart = 0;

	while (cur_state < STATE_QUIT) {
		if (may_read(sock, &cmd, sizeof(int)))
			break;
		switch (cmd) {
		case PRIV_OPEN_TTY:
			log_debug("[priv]: msg PRIV_OPEN_TTY received");
			/* Expecting: length, path */
			must_read(sock, &path_len, sizeof(size_t));
			if (path_len == 0 || path_len > sizeof(path))
				_exit(1);
			must_read(sock, &path, path_len);
			path[path_len - 1] = '\0';
			check_tty_name(path, sizeof(path));
			fd = open(path, O_WRONLY|O_NONBLOCK);
			send_fd(sock, fd);
			if (fd == -1)
				warnx("priv_open_tty failed");
			else
				close(fd);
			break;

		case PRIV_OPEN_LOG:
		case PRIV_OPEN_PIPE:
			log_debug("[priv]: msg PRIV_OPEN_%s received",
			    cmd == PRIV_OPEN_PIPE ? "PIPE" : "LOG");
			/* Expecting: length, path */
			must_read(sock, &path_len, sizeof(size_t));
			if (path_len == 0 || path_len > sizeof(path))
				_exit(1);
			must_read(sock, &path, path_len);
			path[path_len - 1] = '\0';
			check_log_name(path, sizeof(path));

			if (cmd == PRIV_OPEN_LOG)
				fd = open_file(path);
			else if (cmd == PRIV_OPEN_PIPE)
				fd = open_pipe(path);
			else
				errx(1, "invalid cmd");

			send_fd(sock, fd);
			if (fd == -1)
				warnx("priv_open_log failed");
			else
				close(fd);
			break;

		case PRIV_OPEN_UTMP:
			log_debug("[priv]: msg PRIV_OPEN_UTMP received");
			fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK);
			send_fd(sock, fd);
			if (fd == -1)
				warnx("priv_open_utmp failed");
			else
				close(fd);
			break;

		case PRIV_OPEN_CONFIG:
			log_debug("[priv]: msg PRIV_OPEN_CONFIG received");
			stat(conf, &cf_info);
			fd = open(conf, O_RDONLY|O_NONBLOCK);
			send_fd(sock, fd);
			if (fd == -1)
				warnx("priv_open_config failed");
			else
				close(fd);
			break;

		case PRIV_CONFIG_MODIFIED:
			log_debug("[priv]: msg PRIV_CONFIG_MODIFIED received");
			if (stat(conf, &cf_stat) == -1 ||
			    timespeccmp(&cf_info.st_mtim,
			    &cf_stat.st_mtim, <) ||
			    cf_info.st_size != cf_stat.st_size) {
				log_debug("config file modified: restarting");
				restart = result = 1;
				must_write(sock, &result, sizeof(int));
			} else {
				result = 0;
				must_write(sock, &result, sizeof(int));
			}
			break;

		case PRIV_DONE_CONFIG_PARSE:
			if (pledge("stdio rpath wpath cpath dns sendfd id proc exec",
			    NULL) == -1)
				err(1, "pledge done config");
			log_debug("[priv]: msg PRIV_DONE_CONFIG_PARSE "
			    "received");
			increase_state(STATE_RUNNING);
			break;

		case PRIV_GETADDRINFO:
			log_debug("[priv]: msg PRIV_GETADDRINFO received");
			/* Expecting: len, proto, len, host, len, serv */
			must_read(sock, &protoname_len, sizeof(size_t));
			if (protoname_len == 0 ||
			    protoname_len > sizeof(protoname))
				_exit(1);
			must_read(sock, &protoname, protoname_len);
			protoname[protoname_len - 1] = '\0';

			must_read(sock, &hostname_len, sizeof(size_t));
			if (hostname_len == 0 ||
			    hostname_len > sizeof(hostname))
				_exit(1);
			must_read(sock, &hostname, hostname_len);
			hostname[hostname_len - 1] = '\0';

			must_read(sock, &servname_len, sizeof(size_t));
			if (servname_len == 0 ||
			    servname_len > sizeof(servname))
				_exit(1);
			must_read(sock, &servname, servname_len);
			servname[servname_len - 1] = '\0';

			memset(&hints, 0, sizeof(hints));
			switch (strlen(protoname)) {
			case 3:
				hints.ai_family = AF_UNSPEC;
				break;
			case 4:
				switch (protoname[3]) {
				case '4':
					hints.ai_family = AF_INET;
					break;
				case '6':
					hints.ai_family = AF_INET6;
					break;
				default:
					errx(1, "bad ip version %s", protoname);
				}
				break;
			default:
				errx(1, "bad protocol length %s", protoname);
			}
			if (strncmp(protoname, "udp", 3) == 0) {
				hints.ai_socktype = SOCK_DGRAM;
				hints.ai_protocol = IPPROTO_UDP;
			} else if (strncmp(protoname, "tcp", 3) == 0) {
				hints.ai_socktype = SOCK_STREAM;
				hints.ai_protocol = IPPROTO_TCP;
			} else {
				errx(1, "unknown protocol %s", protoname);
			}
			i = getaddrinfo(hostname, servname, &hints, &res0);
			if (i != 0 || res0 == NULL) {
				addr_len = 0;
				must_write(sock, &addr_len, sizeof(int));
			} else {
				/* Just send the first address */
				i = res0->ai_addrlen;
				must_write(sock, &i, sizeof(int));
				must_write(sock, res0->ai_addr, i);
				freeaddrinfo(res0);
			}
			break;

		case PRIV_GETNAMEINFO:
			log_debug("[priv]: msg PRIV_GETNAMEINFO received");
			if (numeric)
				errx(1, "rejected attempt to getnameinfo");
			/* Expecting: length, sockaddr */
			must_read(sock, &addr_len, sizeof(int));
			if (addr_len <= 0 || (size_t)addr_len > sizeof(addr))
				_exit(1);
			must_read(sock, &addr, addr_len);
			if (getnameinfo((struct sockaddr *)&addr, addr_len,
			    hostname, sizeof(hostname), NULL, 0,
			    NI_NOFQDN|NI_NAMEREQD|NI_DGRAM) != 0) {
				addr_len = 0;
				must_write(sock, &addr_len, sizeof(int));
			} else {
				addr_len = strlen(hostname) + 1;
				must_write(sock, &addr_len, sizeof(int));
				must_write(sock, hostname, addr_len);
			}
			break;
		default:
			errx(1, "unknown command %d", cmd);
			break;
		}
	}

	close(sock);

	if (restart) {
		int status;

		waitpid(child_pid, &status, 0);
		sigemptyset(&sigmask);
		sigaddset(&sigmask, SIGHUP);
		if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1)
			err(1, "sigprocmask exec");
		execvp(argv[0], argv);
		err(1, "exec restart '%s' failed", argv[0]);
	}
	unlink(_PATH_LOGPID);
	exit(0);
}

static int
open_file(char *path)
{
	/* must not start with | */
	if (path[0] == '|')
		return (-1);

	return (open(path, O_WRONLY|O_APPEND|O_NONBLOCK));
}

static int
open_pipe(char *cmd)
{
	char *argp[] = {"sh", "-c", NULL, NULL};
	int fd[2];
	int bsize, flags;
	pid_t pid;

	/* skip over leading | and whitespace */
	if (cmd[0] != '|')
		return (-1);
	for (cmd++; *cmd && *cmd == ' '; cmd++)
		; /* nothing */
	if (!*cmd)
		return (-1);

	argp[2] = cmd;

	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) {
		warnx("open_pipe");
		return (-1);
	}

	/* make the fd on syslogd's side nonblocking */
	if ((flags = fcntl(fd[1], F_GETFL)) == -1) {
		warnx("fcntl");
		return (-1);
	}
	flags |= O_NONBLOCK;
	if ((flags = fcntl(fd[1], F_SETFL, flags)) == -1) {
		warnx("fcntl");
		return (-1);
	}

	switch (pid = fork()) {
	case -1:
		warnx("fork error");
		return (-1);
	case 0:
		break;
	default:
		close(fd[0]);
		return (fd[1]);
	}

	close(fd[1]);

	/* grow receive buffer */
	bsize = 65535;
	while (bsize > 0 && setsockopt(fd[0], SOL_SOCKET, SO_RCVBUF,
	    &bsize, sizeof(bsize)) == -1)
		bsize /= 2;

	if (setgroups(1, &pw->pw_gid) == -1 ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
		err(1, "failure dropping privs");

	if (dup2(fd[0], STDIN_FILENO) == -1)
		err(1, "dup2 failed");
	closefrom(STDERR_FILENO + 1);
	if (execv("/bin/sh", argp) == -1)
		err(1, "execv %s", cmd);
	/* NOTREACHED */
	return (-1);
}

/* Check that the terminal device is ok, and if not, rewrite to /dev/null.
 * Either /dev/console or /dev/tty* are allowed.
 */
static void
check_tty_name(char *tty, size_t ttysize)
{
	const char ttypre[] = "/dev/tty";
	char *p;

	/* Any path containing '..' is invalid.  */
	for (p = tty; p + 1 < tty + ttysize && *p; p++)
		if (*p == '.' && *(p + 1) == '.')
			goto bad_path;

	if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
		goto bad_path;
	return;

bad_path:
	warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
	    "check_tty_name", tty);
	strlcpy(tty, "/dev/null", ttysize);
}

/* If we are in the initial configuration state, accept a logname and add
 * it to the list of acceptable logfiles.  Otherwise, check against this list
 * and rewrite to /dev/null if it's a bad path.
 */
static void
check_log_name(char *lognam, size_t logsize)
{
	struct logname *lg;
	char *p;

	/* Any path containing '..' is invalid.  */
	for (p = lognam; p + 1 < lognam + logsize && *p; p++)
		if (*p == '.' && *(p + 1) == '.')
			goto bad_path;

	switch (cur_state) {
	case STATE_CONFIG:
		lg = malloc(sizeof(struct logname));
		if (!lg)
			err(1, "check_log_name() malloc");
		strlcpy(lg->path, lognam, PATH_MAX);
		TAILQ_INSERT_TAIL(&lognames, lg, next);
		if (lognam[0] != '|') {
			if (unveil(lognam, "w") == -1)
				goto bad_path;
		}
		break;
	case STATE_RUNNING:
		TAILQ_FOREACH(lg, &lognames, next)
			if (!strcmp(lg->path, lognam))
				return;
		goto bad_path;
		break;
	default:
		/* Any other state should just refuse the request */
		goto bad_path;
		break;
	}
	return;

bad_path:
	warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
	    "check_log_name", lognam);
	strlcpy(lognam, "/dev/null", logsize);
}

/* Crank our state into less permissive modes */
static void
increase_state(int state)
{
	if (state <= cur_state)
		errx(1, "attempt to decrease or match current state");
	if (state < STATE_INIT || state > STATE_QUIT)
		errx(1, "attempt to switch to invalid state");
	cur_state = state;
}

/* Open console or a terminal device for writing */
int
priv_open_tty(const char *tty)
{
	char path[PATH_MAX];
	int cmd, fd;
	size_t path_len;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	if (strlcpy(path, tty, sizeof path) >= sizeof(path))
		return -1;
	path_len = strlen(path) + 1;

	cmd = PRIV_OPEN_TTY;
	must_write(priv_fd, &cmd, sizeof(int));
	must_write(priv_fd, &path_len, sizeof(size_t));
	must_write(priv_fd, path, path_len);
	fd = receive_fd(priv_fd);
	return fd;
}

/* Open log-file */
int
priv_open_log(const char *lognam)
{
	char path[PATH_MAX];
	int cmd, fd;
	size_t path_len;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged child", __func__);

	if (strlcpy(path, lognam, sizeof path) >= sizeof(path))
		return -1;
	path_len = strlen(path) + 1;

	if (lognam[0] == '|')
		cmd = PRIV_OPEN_PIPE;
	else
		cmd = PRIV_OPEN_LOG;
	must_write(priv_fd, &cmd, sizeof(int));
	must_write(priv_fd, &path_len, sizeof(size_t));
	must_write(priv_fd, path, path_len);
	fd = receive_fd(priv_fd);
	return fd;
}

/* Open utmp for reading */
FILE *
priv_open_utmp(void)
{
	int cmd, fd;
	FILE *fp;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	cmd = PRIV_OPEN_UTMP;
	must_write(priv_fd, &cmd, sizeof(int));
	fd = receive_fd(priv_fd);
	if (fd < 0)
		return NULL;

	fp = fdopen(fd, "r");
	if (!fp) {
		warn("priv_open_utmp: fdopen() failed");
		close(fd);
		return NULL;
	}

	return fp;
}

/* Open syslog config file for reading */
FILE *
priv_open_config(void)
{
	int cmd, fd;
	FILE *fp;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	cmd = PRIV_OPEN_CONFIG;
	must_write(priv_fd, &cmd, sizeof(int));
	fd = receive_fd(priv_fd);
	if (fd < 0)
		return NULL;

	fp = fdopen(fd, "r");
	if (!fp) {
		warn("priv_open_config: fdopen() failed");
		close(fd);
		return NULL;
	}

	return fp;
}

/* Ask if config file has been modified since last attempt to read it */
int
priv_config_modified(void)
{
	int cmd, res;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	cmd = PRIV_CONFIG_MODIFIED;
	must_write(priv_fd, &cmd, sizeof(int));

	/* Expect back integer signalling 1 for modification */
	must_read(priv_fd, &res, sizeof(int));
	return res;
}

/* Child can signal that its initial parsing is done, so that parent
 * can revoke further logfile permissions.  This call only works once. */
void
priv_config_parse_done(void)
{
	int cmd;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	cmd = PRIV_DONE_CONFIG_PARSE;
	must_write(priv_fd, &cmd, sizeof(int));
}

/* Name/service to address translation.  Response is placed into addr.
 * Return 0 for success or < 0 for error like getaddrinfo(3) */
int
priv_getaddrinfo(const char *proto, const char *host, const char *serv,
    struct sockaddr *addr, size_t addr_len)
{
	char protocpy[5], hostcpy[NI_MAXHOST], servcpy[NI_MAXSERV];
	int cmd, ret_len;
	size_t protoname_len, hostname_len, servname_len;

	if (priv_fd < 0)
		errx(1, "%s: called from privileged portion", __func__);

	if (strlcpy(protocpy, proto, sizeof(protocpy)) >= sizeof(protocpy))
		errx(1, "%s: overflow attempt in protoname", __func__);
	protoname_len = strlen(protocpy) + 1;
	if (strlcpy(hostcpy, host, sizeof(hostcpy)) >= sizeof(hostcpy))
		errx(1, "%s: overflow attempt in hostname", __func__);
	hostname_len = strlen(hostcpy) + 1;
	if (strlcpy(servcpy, serv, sizeof(servcpy)) >= sizeof(servcpy))
		errx(1, "%s: overflow attempt in servname", __func__);
	servname_len = strlen(servcpy) + 1;

	cmd = PRIV_GETADDRINFO;
	must_write(priv_fd, &cmd, sizeof(int));
	must_write(priv_fd, &protoname_len, sizeof(size_t));
	must_write(priv_fd, protocpy, protoname_len);
	must_write(priv_fd, &hostname_len, sizeof(size_t));
	must_write(priv_fd, hostcpy, hostname_len);
	must_write(priv_fd, &servname_len, sizeof(size_t));
	must_write(priv_fd, servcpy, servname_len);

	/* Expect back an integer size, and then a string of that length */
	must_read(priv_fd, &ret_len, sizeof(int));

	/* Check there was no error (indicated by a return of 0) */
	if (!ret_len)
		return (-1);

	/* Make sure we aren't overflowing the passed in buffer */
	if (ret_len < 0 || (size_t)ret_len > addr_len)
		errx(1, "%s: overflow attempt in return", __func__);

	/* Read the resolved address and make sure we got all of it */
	memset(addr, '\0', addr_len);
	must_read(priv_fd, addr, ret_len);

	return (0);
}

/* Reverse address resolution; response is placed into host.
 * Return 0 for success or < 0 for error like getnameinfo(3) */
int
priv_getnameinfo(struct sockaddr *sa, socklen_t salen, char *host,
    size_t hostlen)
{
	int cmd, ret_len;

	if (priv_fd < 0)
		errx(1, "%s called from privileged portion", __func__);

	cmd = PRIV_GETNAMEINFO;
	must_write(priv_fd, &cmd, sizeof(int));
	must_write(priv_fd, &salen, sizeof(int));
	must_write(priv_fd, sa, salen);

	/* Expect back an integer size, and then a string of that length */
	must_read(priv_fd, &ret_len, sizeof(int));

	/* Check there was no error (indicated by a return of 0) */
	if (!ret_len)
		return (-1);

	/* Check we don't overflow the passed in buffer */
	if (ret_len < 0 || (size_t)ret_len > hostlen)
		errx(1, "%s: overflow attempt in return", __func__);

	/* Read the resolved hostname */
	must_read(priv_fd, host, ret_len);
	return (0);
}

/* Pass the signal through to child */
static void
sig_pass_to_chld(int sig)
{
	int save_errno = errno;

	if (child_pid != -1)
		kill(child_pid, sig);
	errno = save_errno;
}

/* When child dies, move into the shutdown state */
static void
sig_got_chld(int sig)
{
	int save_errno = errno;
	pid_t	pid;

	do {
		pid = waitpid(WAIT_ANY, NULL, WNOHANG);
		if (pid == child_pid && cur_state < STATE_QUIT)
			cur_state = STATE_QUIT;
	} while (pid > 0 || (pid == -1 && errno == EINTR));
	errno = save_errno;
}

/* Read all data or return 1 for error.  */
static int
may_read(int fd, void *buf, size_t n)
{
	char *s = buf;
	ssize_t res;
	size_t pos = 0;

	while (n > pos) {
		res = read(fd, s + pos, n - pos);
		switch (res) {
		case -1:
			if (errno == EINTR || errno == EAGAIN)
				continue;
		case 0:
			return (1);
		default:
			pos += res;
		}
	}
	return (0);
}

/* Read data with the assertion that it all must come through, or
 * else abort the process.  Based on atomicio() from openssh. */
static void
must_read(int fd, void *buf, size_t n)
{
	char *s = buf;
	ssize_t res;
	size_t pos = 0;

	while (n > pos) {
		res = read(fd, s + pos, n - pos);
		switch (res) {
		case -1:
			if (errno == EINTR || errno == EAGAIN)
				continue;
		case 0:
			_exit(1);
		default:
			pos += res;
		}
	}
}

/* Write data with the assertion that it all has to be written, or
 * else abort the process.  Based on atomicio() from openssh. */
static void
must_write(int fd, void *buf, size_t n)
{
	char *s = buf;
	ssize_t res;
	size_t pos = 0;

	while (n > pos) {
		res = write(fd, s + pos, n - pos);
		switch (res) {
		case -1:
			if (errno == EINTR || errno == EAGAIN)
				continue;
		case 0:
			_exit(1);
		default:
			pos += res;
		}
	}
}