aboutsummaryrefslogtreecommitdiff
path: root/bin/auditdistd/auditdistd.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/auditdistd/auditdistd.c')
-rw-r--r--bin/auditdistd/auditdistd.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/bin/auditdistd/auditdistd.c b/bin/auditdistd/auditdistd.c
new file mode 100644
index 000000000000..2cab2760205b
--- /dev/null
+++ b/bin/auditdistd/auditdistd.c
@@ -0,0 +1,801 @@
+/*-
+ * 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#1 $
+ */
+
+#include "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 <pjdlog.h>
+
+#include "auditdistd.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);
+}