/*-
 * Copyright (c) 2012 The FreeBSD Foundation
 * All rights reserved.
 *
 * This software was developed by Pawel Jakub Dawidek under sponsorship from
 * the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $P4: //depot/projects/trustedbsd/openbsm/bin/auditdistd/auditdistd.c#3 $
 */

#include <config/config.h>

#include <sys/param.h>
#if defined(HAVE_SYS_ENDIAN_H) && defined(HAVE_BSWAP)
#include <sys/endian.h>
#else /* !HAVE_SYS_ENDIAN_H || !HAVE_BSWAP */
#ifdef HAVE_MACHINE_ENDIAN_H
#include <machine/endian.h>
#else /* !HAVE_MACHINE_ENDIAN_H */
#ifdef HAVE_ENDIAN_H
#include <endian.h>
#else /* !HAVE_ENDIAN_H */
#error "No supported endian.h"
#endif /* !HAVE_ENDIAN_H */
#endif /* !HAVE_MACHINE_ENDIAN_H */
#include <compat/endian.h>
#endif /* !HAVE_SYS_ENDIAN_H || !HAVE_BSWAP */
#include <sys/queue.h>
#include <sys/wait.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_LIBUTIL_H
#include <libutil.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <openssl/hmac.h>

#ifndef HAVE_PIDFILE_OPEN
#include <compat/pidfile.h>
#endif
#ifndef HAVE_STRLCPY
#include <compat/strlcpy.h>
#endif
#ifndef HAVE_SIGTIMEDWAIT
#include "sigtimedwait.h"
#endif

#include "auditdistd.h"
#include "pjdlog.h"
#include "proto.h"
#include "subr.h"
#include "synch.h"

/* Path to configuration file. */
const char *cfgpath = ADIST_CONFIG;
/* Auditdistd configuration. */
static struct adist_config *adcfg;
/* Was SIGINT or SIGTERM signal received? */
bool sigexit_received = false;
/* PID file handle. */
struct pidfh *pfh;

/* How often check for hooks running for too long. */
#define	SIGNALS_CHECK_INTERVAL	5

static void
usage(void)
{

	errx(EX_USAGE, "[-dFhl] [-c config] [-P pidfile]");
}

void
descriptors_cleanup(struct adist_host *adhost)
{
	struct adist_host *adh;
	struct adist_listen *lst;

	TAILQ_FOREACH(adh, &adcfg->adc_hosts, adh_next) {
		if (adh == adhost)
			continue;
		if (adh->adh_remote != NULL) {
			proto_close(adh->adh_remote);
			adh->adh_remote = NULL;
		}
	}
	TAILQ_FOREACH(lst, &adcfg->adc_listen, adl_next) {
		if (lst->adl_conn != NULL)
			proto_close(lst->adl_conn);
	}
	(void)pidfile_close(pfh);
	pjdlog_fini();
}

static void
child_cleanup(struct adist_host *adhost)
{

	if (adhost->adh_conn != NULL) {
		PJDLOG_ASSERT(adhost->adh_role == ADIST_ROLE_SENDER);
		proto_close(adhost->adh_conn);
		adhost->adh_conn = NULL;
	}
	adhost->adh_worker_pid = 0;
}

static void
child_exit_log(const char *type, unsigned int pid, int status)
{

	if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
		pjdlog_debug(1, "%s process exited gracefully (pid=%u).",
		    type, pid);
	} else if (WIFSIGNALED(status)) {
		pjdlog_error("%s process killed (pid=%u, signal=%d).",
		    type, pid, WTERMSIG(status));
	} else {
		pjdlog_error("%s process exited ungracefully (pid=%u, exitcode=%d).",
		    type, pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1);
	}
}

static void
child_exit(void)
{
	struct adist_host *adhost;
	bool restart;
	int status;
	pid_t pid;

	restart = false;
	while ((pid = wait3(&status, WNOHANG, NULL)) > 0) {
		/* Find host related to the process that just exited. */
		TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
			if (pid == adhost->adh_worker_pid)
				break;
		}
		if (adhost == NULL) {
			child_exit_log("Sandbox", pid, status);
		} else {
			if (adhost->adh_role == ADIST_ROLE_SENDER)
				restart = true;
			pjdlog_prefix_set("[%s] (%s) ", adhost->adh_name,
			    role2str(adhost->adh_role));
			child_exit_log("Worker", pid, status);
			child_cleanup(adhost);
			pjdlog_prefix_set("%s", "");
		}
	}
	if (!restart)
		return;
	/* We have some sender processes to restart. */
	sleep(1);
	TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
		if (adhost->adh_role != ADIST_ROLE_SENDER)
			continue;
		if (adhost->adh_worker_pid != 0)
			continue;
		pjdlog_prefix_set("[%s] (%s) ", adhost->adh_name,
		    role2str(adhost->adh_role));
		pjdlog_info("Restarting sender process.");
		adist_sender(adcfg, adhost);
		pjdlog_prefix_set("%s", "");
	}
}

/* TODO */
static void
adist_reload(void)
{

	pjdlog_info("Reloading configuration is not yet implemented.");
}

static void
terminate_workers(void)
{
	struct adist_host *adhost;

	pjdlog_info("Termination signal received, exiting.");
	TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
		if (adhost->adh_worker_pid == 0)
			continue;
		pjdlog_info("Terminating worker process (adhost=%s, role=%s, pid=%u).",
		    adhost->adh_name, role2str(adhost->adh_role),
		    adhost->adh_worker_pid);
		if (kill(adhost->adh_worker_pid, SIGTERM) == 0)
			continue;
		pjdlog_errno(LOG_WARNING,
		    "Unable to send signal to worker process (adhost=%s, role=%s, pid=%u).",
		    adhost->adh_name, role2str(adhost->adh_role),
		    adhost->adh_worker_pid);
	}
}

static void
listen_accept(struct adist_listen *lst)
{
	unsigned char rnd[32], hash[32], resp[32];
	struct adist_host *adhost;
	struct proto_conn *conn;
	char adname[ADIST_HOSTSIZE];
	char laddr[256], raddr[256];
	char welcome[8];
	int status, version;
	pid_t pid;

	proto_local_address(lst->adl_conn, laddr, sizeof(laddr));
	pjdlog_debug(1, "Accepting connection to %s.", laddr);

	if (proto_accept(lst->adl_conn, &conn) == -1) {
		pjdlog_errno(LOG_ERR, "Unable to accept connection to %s",
		    laddr);
		return;
	}

	proto_local_address(conn, laddr, sizeof(laddr));
	proto_remote_address(conn, raddr, sizeof(raddr));
	pjdlog_info("Connection from %s to %s.", raddr, laddr);

	/* Error in setting timeout is not critical, but why should it fail? */
	if (proto_timeout(conn, ADIST_TIMEOUT) < 0)
		pjdlog_errno(LOG_WARNING, "Unable to set connection timeout");

	/*
	 * Before receiving any data see if remote host is known.
	 */
	TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
		if (adhost->adh_role != ADIST_ROLE_RECEIVER)
			continue;
		if (!proto_address_match(conn, adhost->adh_remoteaddr))
			continue;
		break;
	}
	if (adhost == NULL) {
		pjdlog_error("Client %s is not known.", raddr);
		goto close;
	}
	/* Ok, remote host is known. */

	/* Exchange welcome message, which include version number. */
	bzero(welcome, sizeof(welcome));
	if (proto_recv(conn, welcome, sizeof(welcome)) == -1) {
		pjdlog_errno(LOG_WARNING,
		    "Unable to receive welcome message from %s",
		    adhost->adh_remoteaddr);
		goto close;
	}
	if (strncmp(welcome, "ADIST", 5) != 0 || !isdigit(welcome[5]) ||
	    !isdigit(welcome[6]) || welcome[7] != '\0') {
		pjdlog_warning("Invalid welcome message from %s.",
		    adhost->adh_remoteaddr);
		goto close;
	}

	version = MIN(ADIST_VERSION, atoi(welcome + 5));

	(void)snprintf(welcome, sizeof(welcome), "ADIST%02d", version);
	if (proto_send(conn, welcome, sizeof(welcome)) == -1) {
		pjdlog_errno(LOG_WARNING,
		    "Unable to send welcome message to %s",
		    adhost->adh_remoteaddr);
		goto close;
	}

	if (proto_recv(conn, adname, sizeof(adhost->adh_name)) < 0) {
		pjdlog_errno(LOG_ERR, "Unable to receive hostname from %s",
		    raddr);
		goto close;
	}

	/* Find host now that we have hostname. */
	TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
		if (adhost->adh_role != ADIST_ROLE_RECEIVER)
			continue;
		if (!proto_address_match(conn, adhost->adh_remoteaddr))
			continue;
		if (strcmp(adhost->adh_name, adname) != 0)
			continue;
		break;
	}
	if (adhost == NULL) {
		pjdlog_error("No configuration for host %s from address %s.",
		    adname, raddr);
		goto close;
	}

	adhost->adh_version = version;
	pjdlog_debug(1, "Version %d negotiated with %s.", adhost->adh_version,
	    adhost->adh_remoteaddr);

	/* Now that we know host name setup log prefix. */
	pjdlog_prefix_set("[%s] (%s) ", adhost->adh_name,
	    role2str(adhost->adh_role));

	if (adist_random(rnd, sizeof(rnd)) == -1) {
		pjdlog_error("Unable to generate challenge.");
		goto close;
	}
	pjdlog_debug(1, "Challenge generated.");

	if (proto_send(conn, rnd, sizeof(rnd)) == -1) {
		pjdlog_errno(LOG_ERR, "Unable to send challenge to %s",
		    adhost->adh_remoteaddr);
		goto close;
	}
	pjdlog_debug(1, "Challenge sent.");

	if (proto_recv(conn, resp, sizeof(resp)) == -1) {
		pjdlog_errno(LOG_ERR, "Unable to receive response from %s",
		    adhost->adh_remoteaddr);
		goto close;
	}
	pjdlog_debug(1, "Response received.");

	if (HMAC(EVP_sha256(), adhost->adh_password,
	    (int)strlen(adhost->adh_password), rnd, (int)sizeof(rnd), hash,
	    NULL) == NULL) {
		pjdlog_error("Unable to generate hash.");
		goto close;
	}
	pjdlog_debug(1, "Hash generated.");

	if (memcmp(resp, hash, sizeof(hash)) != 0) {
		pjdlog_error("Invalid response from %s (wrong password?).",
		    adhost->adh_remoteaddr);
		goto close;
	}
	pjdlog_info("Sender authenticated.");

	if (proto_recv(conn, rnd, sizeof(rnd)) == -1) {
		pjdlog_errno(LOG_ERR, "Unable to receive challenge from %s",
		    adhost->adh_remoteaddr);
		goto close;
	}
	pjdlog_debug(1, "Challenge received.");

	if (HMAC(EVP_sha256(), adhost->adh_password,
	    (int)strlen(adhost->adh_password), rnd, (int)sizeof(rnd), hash,
	    NULL) == NULL) {
		pjdlog_error("Unable to generate response.");
		goto close;
	}
	pjdlog_debug(1, "Response generated.");

	if (proto_send(conn, hash, sizeof(hash)) == -1) {
		pjdlog_errno(LOG_ERR, "Unable to send response to %s",
		    adhost->adh_remoteaddr);
		goto close;
	}
	pjdlog_debug(1, "Response sent.");

	if (adhost->adh_worker_pid != 0) {
		pjdlog_debug(1,
		    "Receiver process exists (pid=%u), stopping it.",
		    (unsigned int)adhost->adh_worker_pid);
		/* Stop child process. */
		if (kill(adhost->adh_worker_pid, SIGINT) == -1) {
			pjdlog_errno(LOG_ERR,
			    "Unable to stop worker process (pid=%u)",
			    (unsigned int)adhost->adh_worker_pid);
			/*
			 * Other than logging the problem we
			 * ignore it - nothing smart to do.
			 */
		}
		/* Wait for it to exit. */
		else if ((pid = waitpid(adhost->adh_worker_pid,
		    &status, 0)) != adhost->adh_worker_pid) {
			/* We can only log the problem. */
			pjdlog_errno(LOG_ERR,
			    "Waiting for worker process (pid=%u) failed",
			    (unsigned int)adhost->adh_worker_pid);
		} else {
			child_exit_log("Worker", adhost->adh_worker_pid,
			    status);
		}
		child_cleanup(adhost);
	}

	adhost->adh_remote = conn;
	adist_receiver(adcfg, adhost);

	pjdlog_prefix_set("%s", "");
	return;
close:
	proto_close(conn);
	pjdlog_prefix_set("%s", "");
}

static void
connection_migrate(struct adist_host *adhost)
{
	struct proto_conn *conn;
	int16_t val = 0;

	pjdlog_prefix_set("[%s] (%s) ", adhost->adh_name,
	    role2str(adhost->adh_role));

	PJDLOG_ASSERT(adhost->adh_role == ADIST_ROLE_SENDER);

	if (proto_recv(adhost->adh_conn, &val, sizeof(val)) < 0) {
		pjdlog_errno(LOG_WARNING,
		    "Unable to receive connection command");
		return;
	}
	if (proto_set("tls:fingerprint", adhost->adh_fingerprint) == -1) {
		val = errno;
		pjdlog_errno(LOG_WARNING, "Unable to set fingerprint");
		goto out;
	}
	if (proto_connect(adhost->adh_localaddr[0] != '\0' ?
	    adhost->adh_localaddr : NULL,
	    adhost->adh_remoteaddr, -1, &conn) < 0) {
		val = errno;
		pjdlog_errno(LOG_WARNING, "Unable to connect to %s",
		    adhost->adh_remoteaddr);
		goto out;
	}
	val = 0;
out:
	if (proto_send(adhost->adh_conn, &val, sizeof(val)) < 0) {
		pjdlog_errno(LOG_WARNING,
		    "Unable to send reply to connection request");
	}
	if (val == 0 && proto_connection_send(adhost->adh_conn, conn) < 0)
		pjdlog_errno(LOG_WARNING, "Unable to send connection");

	pjdlog_prefix_set("%s", "");
}

static void
check_signals(void)
{
	struct timespec sigtimeout;
	sigset_t mask;
	int signo;

	sigtimeout.tv_sec = 0;
	sigtimeout.tv_nsec = 0;

	PJDLOG_VERIFY(sigemptyset(&mask) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);

	while ((signo = sigtimedwait(&mask, NULL, &sigtimeout)) != -1) {
		switch (signo) {
		case SIGINT:
		case SIGTERM:
			sigexit_received = true;
			terminate_workers();
			exit(EX_OK);
			break;
		case SIGCHLD:
			child_exit();
			break;
		case SIGHUP:
			adist_reload();
			break;
		default:
			PJDLOG_ABORT("Unexpected signal (%d).", signo);
		}
	}
}

static void
main_loop(void)
{
	struct adist_host *adhost;
	struct adist_listen *lst;
	struct timeval seltimeout;
	int fd, maxfd, ret;
	fd_set rfds;

	seltimeout.tv_sec = SIGNALS_CHECK_INTERVAL;
	seltimeout.tv_usec = 0;

	pjdlog_info("Started successfully.");

	for (;;) {
		check_signals();

		/* Setup descriptors for select(2). */
		FD_ZERO(&rfds);
		maxfd = -1;
		TAILQ_FOREACH(lst, &adcfg->adc_listen, adl_next) {
			if (lst->adl_conn == NULL)
				continue;
			fd = proto_descriptor(lst->adl_conn);
			PJDLOG_ASSERT(fd >= 0);
			FD_SET(fd, &rfds);
			maxfd = fd > maxfd ? fd : maxfd;
		}
		TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
			if (adhost->adh_role == ADIST_ROLE_SENDER) {
				/* Only sender workers asks for connections. */
				PJDLOG_ASSERT(adhost->adh_conn != NULL);
				fd = proto_descriptor(adhost->adh_conn);
				PJDLOG_ASSERT(fd >= 0);
				FD_SET(fd, &rfds);
				maxfd = fd > maxfd ? fd : maxfd;
			} else {
				PJDLOG_ASSERT(adhost->adh_conn == NULL);
			}
		}

		PJDLOG_ASSERT(maxfd + 1 <= (int)FD_SETSIZE);
		ret = select(maxfd + 1, &rfds, NULL, NULL, &seltimeout);
		if (ret == 0) {
			/*
			 * select(2) timed out, so there should be no
			 * descriptors to check.
			 */
			continue;
		} else if (ret == -1) {
			if (errno == EINTR)
				continue;
			KEEP_ERRNO((void)pidfile_remove(pfh));
			pjdlog_exit(EX_OSERR, "select() failed");
		}
		PJDLOG_ASSERT(ret > 0);

		/*
		 * Check for signals before we do anything to update our
		 * info about terminated workers in the meantime.
		 */
		check_signals();

		TAILQ_FOREACH(lst, &adcfg->adc_listen, adl_next) {
			if (lst->adl_conn == NULL)
				continue;
			if (FD_ISSET(proto_descriptor(lst->adl_conn), &rfds))
				listen_accept(lst);
		}
		TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
			if (adhost->adh_role == ADIST_ROLE_SENDER) {
				PJDLOG_ASSERT(adhost->adh_conn != NULL);
				if (FD_ISSET(proto_descriptor(adhost->adh_conn),
				    &rfds)) {
					connection_migrate(adhost);
				}
			} else {
				PJDLOG_ASSERT(adhost->adh_conn == NULL);
			}
		}
	}
}

static void
adist_config_dump(struct adist_config *cfg)
{
	struct adist_host *adhost;
	struct adist_listen *lst;

	pjdlog_debug(2, "Configuration:");
	pjdlog_debug(2, "  Global:");
	pjdlog_debug(2, "    pidfile: %s", cfg->adc_pidfile);
	pjdlog_debug(2, "    timeout: %d", cfg->adc_timeout);
	if (TAILQ_EMPTY(&cfg->adc_listen)) {
		pjdlog_debug(2, "  Sender only, not listening.");
	} else {
		pjdlog_debug(2, "  Listening on:");
		TAILQ_FOREACH(lst, &cfg->adc_listen, adl_next) {
			pjdlog_debug(2, "    listen: %s", lst->adl_addr);
			pjdlog_debug(2, "    conn: %p", lst->adl_conn);
		}
	}
	pjdlog_debug(2, "  Hosts:");
	TAILQ_FOREACH(adhost, &cfg->adc_hosts, adh_next) {
		pjdlog_debug(2, "    name: %s", adhost->adh_name);
		pjdlog_debug(2, "      role: %s", role2str(adhost->adh_role));
		pjdlog_debug(2, "      version: %d", adhost->adh_version);
		pjdlog_debug(2, "      localaddr: %s", adhost->adh_localaddr);
		pjdlog_debug(2, "      remoteaddr: %s", adhost->adh_remoteaddr);
		pjdlog_debug(2, "      remote: %p", adhost->adh_remote);
		pjdlog_debug(2, "      directory: %s", adhost->adh_directory);
		pjdlog_debug(2, "      compression: %d", adhost->adh_compression);
		pjdlog_debug(2, "      checksum: %d", adhost->adh_checksum);
		pjdlog_debug(2, "      pid: %ld", (long)adhost->adh_worker_pid);
		pjdlog_debug(2, "      conn: %p", adhost->adh_conn);
	}
}

static void
dummy_sighandler(int sig __unused)
{
	/* Nothing to do. */
}

int
main(int argc, char *argv[])
{
	struct adist_host *adhost;
	struct adist_listen *lst;
	const char *execpath, *pidfile;
	bool foreground, launchd;
	pid_t otherpid;
	int debuglevel;
	sigset_t mask;

	execpath = argv[0];
	if (execpath[0] != '/') {
		errx(EX_USAGE,
		    "auditdistd requires execution with an absolute path.");
	}

	/*
	 * We are executed from proto to create sandbox.
	 */
	if (argc > 1 && strcmp(argv[1], "proto") == 0) {
		argc -= 2;
		argv += 2;
		if (proto_exec(argc, argv) == -1)
			err(EX_USAGE, "Unable to execute proto");
	}

	foreground = false;
	debuglevel = 0;
	launchd = false;
	pidfile = NULL;

	for (;;) {
		int ch;

		ch = getopt(argc, argv, "c:dFhlP:");
		if (ch == -1)
			break;
		switch (ch) {
		case 'c':
			cfgpath = optarg;
			break;
		case 'd':
			debuglevel++;
			break;
		case 'F':
			foreground = true;
			break;
		case 'l':
			launchd = true;
			break;
		case 'P':
			pidfile = optarg;
			break;
		case 'h':
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	pjdlog_init(PJDLOG_MODE_STD);
	pjdlog_debug_set(debuglevel);

	if (proto_set("execpath", execpath) == -1)
		pjdlog_exit(EX_TEMPFAIL, "Unable to set executable name");
	if (proto_set("user", ADIST_USER) == -1)
		pjdlog_exit(EX_TEMPFAIL, "Unable to set proto user");
	if (proto_set("tcp:port", ADIST_TCP_PORT) == -1)
		pjdlog_exit(EX_TEMPFAIL, "Unable to set default TCP port");

	/*
	 * When path to the configuration file is relative, obtain full path,
	 * so we can always find the file, even after daemonizing and changing
	 * working directory to /.
	 */
	if (cfgpath[0] != '/') {
		const char *newcfgpath;

		newcfgpath = realpath(cfgpath, NULL);
		if (newcfgpath == NULL) {
			pjdlog_exit(EX_CONFIG,
			    "Unable to obtain full path of %s", cfgpath);
		}
		cfgpath = newcfgpath;
	}

	adcfg = yy_config_parse(cfgpath, true);
	PJDLOG_ASSERT(adcfg != NULL);
	adist_config_dump(adcfg);

	if (proto_set("tls:certfile", adcfg->adc_certfile) == -1)
		pjdlog_exit(EX_TEMPFAIL, "Unable to set certfile path");
	if (proto_set("tls:keyfile", adcfg->adc_keyfile) == -1)
		pjdlog_exit(EX_TEMPFAIL, "Unable to set keyfile path");

	if (pidfile != NULL) {
		if (strlcpy(adcfg->adc_pidfile, pidfile,
		    sizeof(adcfg->adc_pidfile)) >=
		    sizeof(adcfg->adc_pidfile)) {
			pjdlog_exitx(EX_CONFIG, "Pidfile path is too long.");
		}
	}
	if (foreground && pidfile == NULL) {
		pfh = NULL;
	} else {
		pfh = pidfile_open(adcfg->adc_pidfile, 0600, &otherpid);
		if (pfh == NULL) {
			if (errno == EEXIST) {
				pjdlog_exitx(EX_TEMPFAIL,
				    "Another auditdistd is already running, pid: %jd.",
				    (intmax_t)otherpid);
			}
			/*
			 * If we cannot create pidfile from other reasons,
			 * only warn.
			 */
			pjdlog_errno(LOG_WARNING,
			    "Unable to open or create pidfile %s",
			    adcfg->adc_pidfile);
		}
	}

	/*
	 * Restore default actions for interesting signals in case parent
	 * process (like init(8)) decided to ignore some of them (like SIGHUP).
	 */
	PJDLOG_VERIFY(signal(SIGHUP, SIG_DFL) != SIG_ERR);
	PJDLOG_VERIFY(signal(SIGINT, SIG_DFL) != SIG_ERR);
	PJDLOG_VERIFY(signal(SIGTERM, SIG_DFL) != SIG_ERR);
	/*
	 * Because SIGCHLD is ignored by default, setup dummy handler for it,
	 * so we can mask it.
	 */
	PJDLOG_VERIFY(signal(SIGCHLD, dummy_sighandler) != SIG_ERR);

	PJDLOG_VERIFY(sigemptyset(&mask) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGHUP) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGINT) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGTERM) == 0);
	PJDLOG_VERIFY(sigaddset(&mask, SIGCHLD) == 0);
	PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);

	/* Listen for remote connections. */
	TAILQ_FOREACH(lst, &adcfg->adc_listen, adl_next) {
		if (proto_server(lst->adl_addr, &lst->adl_conn) == -1) {
			KEEP_ERRNO((void)pidfile_remove(pfh));
			pjdlog_exit(EX_OSERR, "Unable to listen on address %s",
			    lst->adl_addr);
		}
	}

	if (!foreground) {
		if (!launchd && daemon(0, 0) == -1) {
			KEEP_ERRNO((void)pidfile_remove(pfh));
			pjdlog_exit(EX_OSERR, "Unable to daemonize");
		}

		/* Start logging to syslog. */
		pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
	}
	if (pfh != NULL) {
		/* Write PID to a file. */
		if (pidfile_write(pfh) < 0) {
			pjdlog_errno(LOG_WARNING,
			    "Unable to write PID to a file");
		}
	}

	TAILQ_FOREACH(adhost, &adcfg->adc_hosts, adh_next) {
		if (adhost->adh_role == ADIST_ROLE_SENDER)
			adist_sender(adcfg, adhost);
	}

	main_loop();

	exit(0);
}