/*
 *
 *   Authors:
 *    Pedro Roque		<roque@di.fc.ul.pt>
 *    Lars Fenneberg		<lf@elemental.net>
 *
 *   This software is Copyright 1996-2000 by the above mentioned author(s),
 *   All Rights Reserved.
 *
 *   The license which is distributed with this software in the file COPYRIGHT
 *   applies to this software. If your distribution is missing this file, you
 *   may request it from https://github.com/radvd-project/radvd/issues
 *
 */

#include "radvd.h"
#include "config.h"
#include "includes.h"
#include "pathnames.h"

#ifdef HAVE_NETLINK
#include "netlink.h"
#endif

#include <libgen.h>
#include <poll.h>
#include <sys/file.h>

#ifdef HAVE_GETOPT_LONG

/* clang-format off */
static char usage_str[] = {
"\n"
"  -C, --config=PATH       Set the config file.  Default is /etc/radvd.d.\n"
"  -c, --configtest        Parse the config file and exit.\n"
"  -d, --debug=NUM         Set the debug level.  Values can be 1, 2, 3, 4 or 5.\n"
"  -f, --facility=NUM      Set the logging facility.\n"
"  -h, --help              Show this help screen.\n"
"  -l, --logfile=PATH      Set the log file.\n"
"  -m, --logmethod=X       Set method to: syslog, stderr, stderr_syslog, logfile,\n"
"                          stderr_clean, or none.\n"
"  -n, --nodaemon          Prevent the daemonizing.\n"
"  -p, --pidfile=PATH      Set the pid file.\n"
"  -t, --chrootdir=PATH    Chroot to the specified path.\n"
"  -u, --username=USER     Switch to the specified user.\n"
"  -v, --version           Print the version and quit.\n"
};

static struct option prog_opt[] = {
	{"chrootdir", 1, 0, 't'},
	{"config", 1, 0, 'C'},
	{"configtest", 0, 0, 'c'},
	{"debug", 1, 0, 'd'},
	{"facility", 1, 0, 'f'},
	{"help", 0, 0, 'h'},
	{"logfile", 1, 0, 'l'},
	{"logmethod", 1, 0, 'm'},
	{"nodaemon", 0, 0, 'n'},
	{"pidfile", 1, 0, 'p'},
	{"username", 1, 0, 'u'},
	{"version", 0, 0, 'v'},
	{NULL, 0, 0, 0}
};

#else

static char usage_str[] = {
"[-hvcn] [-d level] [-C config_path] [-m log_method] [-l log_file]\n"
"\t[-f facility] [-p pid_file] [-u username] [-t chrootdir]"

};
/* clang-format on */

#endif

static volatile int sighup_received = 0;
static volatile int sigint_received = 0;
static volatile int sigterm_received = 0;
static volatile int sigusr1_received = 0;

static int check_conffile_perm(const char *, const char *);
static int open_and_lock_pid_file(char const *daemon_pid_file_ident);
static int write_pid_file(char const *daemon_pid_file_ident, pid_t pid);
static pid_t daemonp(char const *daemon_pid_file_ident);
static pid_t do_daemonize(int log_method, char const *daemon_pid_file_ident);
static struct Interface *main_loop(int sock, struct Interface *ifaces, char const *conf_path);
static struct Interface *reload_config(int sock, struct Interface *ifaces, char const *conf_path);
static void check_pid_file(char const *daemon_pid_file_ident);
static void config_interface(struct Interface *iface);
static void kickoff_adverts(int sock, struct Interface *iface);
static void reset_prefix_lifetimes(struct Interface *ifaces);
static void reset_prefix_lifetimes_foo(struct Interface *iface, void *data);
static void setup_iface_foo(struct Interface *iface, void *data);
static void setup_ifaces(int sock, struct Interface *ifaces);
static void cleanup_ifaces(int sock, struct Interface *ifaces);
static void sighup_handler(int sig);
static void sigint_handler(int sig);
static void sigterm_handler(int sig);
static void sigusr1_handler(int sig);
static void stop_advert_foo(struct Interface *iface, void *data);
static void stop_adverts(int sock, struct Interface *ifaces);
static void timer_handler(int sock, struct Interface *iface);
static void usage(char const *pname);
static void version(void);

/* daemonize and write pid file.  The pid of the daemon child process
 * will be written to the pid file from the *parent* process.  This
 * insures there is no race condition as described in redhat bug 664783. */
static pid_t daemonp(char const *daemon_pid_file_ident)
{
	int pipe_ends[2];

	if (0 != pipe(pipe_ends)) {
		flog(LOG_ERR, "unable to create pipe: %s", strerror(errno));
		exit(-1);
	}

	pid_t pid = fork();

	if (-1 == pid) {
		flog(LOG_ERR, "unable to fork in daemonp");
		exit(-1);
	} else if (0 == pid) {
		/* Child process, detached.. */
		pid = getpid();
		close(pipe_ends[0]);
		if (0 != write_pid_file(daemon_pid_file_ident, pid)) {
			flog(LOG_ERR, "failure writing pid file");
			exit(-1);
		}
		if (sizeof(pid) != write(pipe_ends[1], &pid, sizeof(pid))) {
			flog(LOG_ERR, "failure piping pid to parent process");
		}

		umask(0);
		if (-1 == setsid()) {
			flog(LOG_ERR, "unable to become a session leader: %s", strerror(errno));
			exit(-1);
		}

		if (chdir("/") == -1) {
			perror("chdir");
			exit(1);
		}
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
		if (open("/dev/null", O_RDONLY) == -1) {
			flog(LOG_ERR, "unable to redirect stdin to /dev/null");
			exit(-1);
		}
		if (open("/dev/null", O_WRONLY) == -1) {
			flog(LOG_ERR, "unable to redirect stdout to /dev/null");
			exit(-1);
		}
		if (open("/dev/null", O_RDWR) == -1) {
			flog(LOG_ERR, "unable to redirect stderr to /dev/null");
			exit(-1);
		}
	} else {
		/* Parent.  Make sure the pid file is written before exiting. */
		close(pipe_ends[1]);
		pid_t msg = -1;
		ssize_t rc = read(pipe_ends[0], &msg, sizeof(msg));
		if (rc != sizeof(msg)) {
			flog(LOG_ERR, "child failed to signal pid file written: %s", strerror(errno));
			exit(-1);
		} else if (msg != pid) {
			flog(LOG_ERR, "child wrote wrong pid to pid file: %d", msg);
			exit(-1);
		} else {
			dlog(LOG_DEBUG, 5, "child signaled pid file written: %d", msg);
		}
		close(pipe_ends[0]);

		return pid;
	}
	return 0;
}

int main(int argc, char *argv[])
{
	int c;
	int log_method = L_UNSPEC;
	char *logfile = PATH_RADVD_LOG;
	int facility = LOG_FACILITY;
	char *username = NULL;
	char *chrootdir = NULL;
	int configtest = 0;
	int daemonize = 1;

	char const *pname = ((pname = strrchr(argv[0], '/')) != NULL) ? pname + 1 : argv[0];

	srand((unsigned int)time(NULL));

	char const *conf_path = PATH_RADVD_CONF;
	char const *daemon_pid_file_ident = PATH_RADVD_PID;

/* parse args */
#define OPTIONS_STR "d:C:l:m:p:t:u:vhcn"
#ifdef HAVE_GETOPT_LONG
	int opt_idx;
	while ((c = getopt_long(argc, argv, OPTIONS_STR, prog_opt, &opt_idx)) > 0)
#else
	while ((c = getopt(argc, argv, OPTIONS_STR)) > 0)
#endif
	{
		switch (c) {
		case 'C':
			conf_path = optarg;
			break;
		case 'd':
			set_debuglevel(atoi(optarg));
			break;
		case 'f':
			facility = atoi(optarg);
			break;
		case 'l':
			logfile = optarg;
			break;
		case 'p':
			daemon_pid_file_ident = optarg;
			break;
		case 'm':
			if (!strcmp(optarg, "syslog")) {
				log_method = L_SYSLOG;
			} else if (!strcmp(optarg, "stderr_syslog")) {
				log_method = L_STDERR_SYSLOG;
			} else if (!strcmp(optarg, "stderr")) {
				log_method = L_STDERR;
			} else if (!strcmp(optarg, "stderr_clean")) {
				log_method = L_STDERR_CLEAN;
			} else if (!strcmp(optarg, "logfile")) {
				log_method = L_LOGFILE;
			} else if (!strcmp(optarg, "none")) {
				log_method = L_NONE;
			} else {
				fprintf(stderr, "%s: unknown log method: %s\n", pname, optarg);
				exit(1);
			}
			break;
		case 't':
			chrootdir = strdup(optarg);
			break;
		case 'u':
			username = strdup(optarg);
			break;
		case 'v':
			version();
			break;
		case 'c':
			configtest = 1;
			break;
		case 'n':
			daemonize = 0;
			break;
		case 'h':
			usage(pname);
#ifdef HAVE_GETOPT_LONG
		case ':':
			fprintf(stderr, "%s: option %s: parameter expected\n", pname, prog_opt[opt_idx].name);
			exit(1);
#endif
		case '?':
			exit(1);
		}
	}

	/* TODO: Seems like this chroot'ing should happen *after* daemonizing for
	 * the sake of the PID file. */
	if (chrootdir) {
		if (!username) {
			fprintf(stderr, "Chroot as root is not safe, exiting\n");
			exit(1);
		}

		if (chroot(chrootdir) == -1) {
			perror("chroot");
			exit(1);
		}

		if (chdir("/") == -1) {
			perror("chdir");
			exit(1);
		}
		/* username will be switched later */
	}

	if (configtest) {
		set_debuglevel(1);
		switch (log_method) {
		case L_STDERR:
		case L_STDERR_CLEAN:
			break;
		case L_STDERR_SYSLOG:
		case L_NONE:
		case L_UNSPEC:
		case L_SYSLOG:
		case L_LOGFILE:
		default:
			log_method = L_STDERR;
			break;
		}
	}
	if (log_method == L_UNSPEC)
		log_method = daemonize ? L_STDERR_SYSLOG : L_STDERR;
	if (log_open(log_method, pname, logfile, facility) < 0) {
		perror("log_open");
		exit(1);
	}

	if (!configtest) {
		flog(LOG_INFO, "version %s started", VERSION);
	}

	/* check that 'other' cannot write the file
	 * for non-root, also that self/own group can't either
	 */
	if (check_conffile_perm(username, conf_path) != 0) {
		if (get_debuglevel() == 0) {
			flog(LOG_ERR, "exiting, permissions on conf_file invalid");
			exit(1);
		} else
			flog(LOG_WARNING, "Insecure file permissions, but continuing anyway");
	}

	/* parse config file */
	struct Interface *ifaces = NULL;
	if ((ifaces = readin_config(conf_path)) == 0) {
		flog(LOG_ERR, "exiting, failed to read config file");
		exit(1);
	}

	if (configtest) {
		free_ifaces(ifaces);
		exit(0);
	}

	/* get a raw socket for sending and receiving ICMPv6 messages */
	int sock = open_icmpv6_socket();
	if (sock < 0) {
		perror("open_icmpv6_socket");
		exit(1);
	}

	/* if we know how to do it, check whether forwarding is enabled */
	if (check_ip6_forwarding()) {
		flog(LOG_WARNING, "IPv6 forwarding seems to be disabled, but continuing anyway");
	}

	int const pidfd = open_and_lock_pid_file(daemon_pid_file_ident);

	/*
	 * okay, config file is read in, socket and stuff is setup, so
	 * lets fork now...
	 */
	if (daemonize) {
		pid_t pid = do_daemonize(log_method, daemon_pid_file_ident);
		if (pid != 0 && pid != -1) {
			/* We want to see clean output from valgrind, so free username, chrootdir,
			 * and ifaces in the child process. */
			if (ifaces)
				free_ifaces(ifaces);

			if (username)
				free(username);

			if (chrootdir)
				free(chrootdir);

			exit(0);
		}
	} else {
		if (0 != write_pid_file(daemon_pid_file_ident, getpid())) {
			flog(LOG_ERR, "failure writing pid file detected");
			exit(-1);
		}
	}

	check_pid_file(daemon_pid_file_ident);

#ifdef __linux__
	/* for privsep */ {
		dlog(LOG_DEBUG, 3, "initializing privsep");

		int pipefds[2];

		if (pipe(pipefds) != 0) {
			flog(LOG_ERR, "Couldn't create privsep pipe.");
			return -1;
		}

		pid_t pid = fork();

		if (pid == -1) {
			flog(LOG_ERR, "Couldn't fork for privsep.");
			return -1;
		}

		if (pid == 0) {
			/* We want to see clean output from valgrind, so free username, chrootdir,
			 * and ifaces in the child process. */
			if (ifaces)
				free_ifaces(ifaces);

			if (username)
				free(username);

			if (chrootdir)
				free(chrootdir);

			close(pipefds[1]);

			privsep_init(pipefds[0]);
			_exit(0);
		}

		dlog(LOG_DEBUG, 3, "radvd privsep PID is %d", pid);

		/* Continue execution (will drop privileges soon) */
		close(pipefds[0]);
		privsep_set_write_fd(pipefds[1]);
	}
#endif

	if (username) {
		if (drop_root_privileges(username) < 0) {
			perror("drop_root_privileges");
			flog(LOG_ERR, "unable to drop root privileges");
			exit(1);
		}
		dlog(LOG_DEBUG, 3, "running as user: %s", username);
	}

	setup_ifaces(sock, ifaces);
	ifaces = main_loop(sock, ifaces, conf_path);
	stop_adverts(sock, ifaces);
	cleanup_ifaces(sock, ifaces);
	close(sock);

	flog(LOG_INFO, "removing %s", daemon_pid_file_ident);
	unlink(daemon_pid_file_ident);
	close(pidfd);

	if (ifaces)
		free_ifaces(ifaces);

	if (chrootdir)
		free(chrootdir);

	if (username)
		free(username);

	flog(LOG_INFO, "returning from radvd main");
	log_close();

	return 0;
}

static struct Interface *main_loop(int sock, struct Interface *ifaces, char const *conf_path)
{
	struct pollfd fds[2];
	sigset_t sigmask;
	sigset_t sigempty;
	struct sigaction sa;

	sigemptyset(&sigempty);

	sigemptyset(&sigmask);
	sigaddset(&sigmask, SIGHUP);
	sigaddset(&sigmask, SIGTERM);
	sigaddset(&sigmask, SIGINT);
	sigaddset(&sigmask, SIGUSR1);
	sigprocmask(SIG_BLOCK, &sigmask, NULL);

	sa.sa_handler = sighup_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGHUP, &sa, 0);

	sa.sa_handler = sigterm_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGTERM, &sa, 0);

	sa.sa_handler = sigint_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGINT, &sa, 0);

	sa.sa_handler = sigusr1_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGUSR1, &sa, 0);

	memset(fds, 0, sizeof(fds));

	fds[0].fd = sock;
	fds[0].events = POLLIN;

#if HAVE_NETLINK
	fds[1].fd = netlink_socket();
	fds[1].events = POLLIN;
#else
	fds[1].fd = -1;
#endif

	for (;;) {
		struct timespec *tsp = 0;

		struct Interface *next_iface_to_expire = find_iface_by_time(ifaces);
		if (next_iface_to_expire) {
			static struct timespec ts;
			int timeout = next_time_msec(next_iface_to_expire);
			ts.tv_sec = timeout / 1000;
			ts.tv_nsec = (timeout - 1000 * ts.tv_sec) * 1000000;
			tsp = &ts;
			dlog(LOG_DEBUG, 1, "polling for %g second(s), next iface is %s", timeout / 1000.0,
			     next_iface_to_expire->props.name);
		} else {
			dlog(LOG_DEBUG, 1, "no iface is next. Polling indefinitely");
		}
#ifdef HAVE_PPOLL
		int rc = ppoll(fds, sizeof(fds) / sizeof(fds[0]), tsp, &sigempty);
#else
		// tsp could be NULL so check for this
		int timeout_seconds = tsp != 0 ? 1000 * tsp->tv_sec : 0;
		int rc = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout_seconds);
#endif

		if (rc > 0) {
#ifdef HAVE_NETLINK
			if (fds[1].revents & POLLIN) {
				process_netlink_msg(fds[1].fd, ifaces, sock);
			} else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
				flog(LOG_WARNING, "socket error on fds[1].fd");
			}
#endif

			if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
				flog(LOG_WARNING, "socket error on fds[0].fd");
			} else if (fds[0].revents & POLLIN) {
				int len, hoplimit;
				struct sockaddr_in6 rcv_addr;
				struct in6_pktinfo *pkt_info = NULL;
				unsigned char msg[MSG_SIZE_RECV];
				unsigned char chdr[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))];

				len = recv_rs_ra(sock, msg, &rcv_addr, &pkt_info, &hoplimit, chdr);
				if (len > 0 && pkt_info) {
					process(sock, ifaces, msg, len, &rcv_addr, pkt_info, hoplimit);
				} else if (!pkt_info) {
					dlog(LOG_INFO, 4, "recv_rs_ra returned null pkt_info");
				} else if (len <= 0) {
					dlog(LOG_INFO, 4, "recv_rs_ra returned len <= 0: %d", len);
				}
			}
		} else if (rc == 0) {
			if (next_iface_to_expire)
				timer_handler(sock, next_iface_to_expire);
		} else if (rc == -1) {
			dlog(LOG_INFO, 3, "poll returned early: %s", strerror(errno));
		}

		if (sigint_received) {
			flog(LOG_WARNING, "exiting, %d sigint(s) received", sigint_received);
			break;
		}

		if (sigterm_received) {
			flog(LOG_WARNING, "exiting, %d sigterm(s) received", sigterm_received);
			break;
		}

		if (sighup_received) {
			dlog(LOG_INFO, 3, "sig hup received");
			ifaces = reload_config(sock, ifaces, conf_path);
			sighup_received = 0;
		}

		if (sigusr1_received) {
			dlog(LOG_INFO, 3, "sig usr1 received");
			reset_prefix_lifetimes(ifaces);
			sigusr1_received = 0;
		}
	}

	return ifaces;
}

static pid_t do_daemonize(int log_method, char const *daemon_pid_file_ident)
{
	pid_t pid = -1;

	pid = daemonp(daemon_pid_file_ident);

	if (-1 == pid) {
		flog(LOG_ERR, "unable to daemonize: %s", strerror(errno));
		exit(-1);
	}
	return pid;
}

static int open_pid_file(char const *daemon_pid_file_ident)
{
	int pidfd = open(daemon_pid_file_ident, O_SYNC | O_CREAT | O_RDWR, 0644);
	if (-1 == pidfd) {
		flog(LOG_ERR, "unable to open pid file, %s: %s", daemon_pid_file_ident, strerror(errno));
		exit(-1);
	} else {
		dlog(LOG_DEBUG, 5, "opened pid file %s", daemon_pid_file_ident);
	}
	return pidfd;
}

static int open_and_lock_pid_file(char const *daemon_pid_file_ident)
{
	dlog(LOG_DEBUG, 3, "radvd startup PID is %d", getpid());

	int pidfd = open_pid_file(daemon_pid_file_ident);

	int lock = flock(pidfd, LOCK_EX | LOCK_NB);
	if (0 != lock) {
		flog(LOG_ERR, "unable to lock pid file, %s: %s", daemon_pid_file_ident, strerror(errno));
		exit(-1);
	} else {
		dlog(LOG_DEBUG, 4, "locked pid file %s", daemon_pid_file_ident);
	}

	return pidfd;
}

static int write_pid_file(char const *daemon_pid_file_ident, pid_t pid)
{
	int pidfd = open_pid_file(daemon_pid_file_ident);
	char pid_str[20] = {""};
	sprintf(pid_str, "%d", pid);
	dlog(LOG_DEBUG, 3, "radvd PID is %s", pid_str);
	size_t len = strlen(pid_str);
	int rc = write(pidfd, pid_str, len);
	if (rc != (int)len) {
		return -1;
	}
	char newline[] = {"\n"};
	len = strlen(newline);
	rc = write(pidfd, newline, len);
	if (rc != (int)len) {
		return -1;
	}
	rc = fsync(pidfd);
	if (rc != 0) {
		dlog(LOG_DEBUG, 4, "failed to fsync pid file: %s", daemon_pid_file_ident);
	}
	rc = close(pidfd);
	if (rc != 0) {
		dlog(LOG_DEBUG, 4, "failed to close pid file: %s", daemon_pid_file_ident);
	}
	char *dirstrcopy = strdup(daemon_pid_file_ident);
	char *dirstr = dirname(dirstrcopy);
	int dirfd = open(dirstr, O_RDONLY);
	rc = fsync(dirfd);
	if (rc != 0) {
		dlog(LOG_DEBUG, 4, "failed to fsync pid dir: %s", dirstr);
	}
	rc = close(dirfd);
	if (rc != 0) {
		dlog(LOG_DEBUG, 4, "failed to close pid dir: %s", dirstr);
	}
	free(dirstrcopy);
	dlog(LOG_DEBUG, 4, "wrote pid %d to pid file: %s", pid, daemon_pid_file_ident);
	return rc;
}

static void check_pid_file(char const *daemon_pid_file_ident)
{
	FILE *pidfile = fopen(daemon_pid_file_ident, "r");

	if (!pidfile) {
		flog(LOG_ERR, "unable to open pid file, %s: %s", daemon_pid_file_ident, strerror(errno));
		exit(-1);
	}

	pid_t pid = -1;

	int rc = fscanf(pidfile, "%d", &pid);
	fclose(pidfile);

	if (rc != 1) {
		flog(LOG_ERR, "unable to read pid from pid file: %s", daemon_pid_file_ident);
		exit(-1);
	}

	if (pid != getpid()) {
		flog(LOG_ERR, "pid in file, %s, doesn't match getpid(): %d != %d", daemon_pid_file_ident, pid, getpid());
		exit(-1);
	}
	dlog(LOG_DEBUG, 4, "validated pid file, %s: %d", daemon_pid_file_ident, pid);
}

static void timer_handler(int sock, struct Interface *iface)
{
	dlog(LOG_DEBUG, 1, "timer_handler called for %s", iface->props.name);

	if (send_ra_forall(sock, iface, NULL) != 0) {
		dlog(LOG_DEBUG, 4, "send_ra_forall failed on interface %s", iface->props.name);
	}

	double next = rand_between(iface->MinRtrAdvInterval, iface->MaxRtrAdvInterval);

	reschedule_iface(iface, next);
}

static void config_interface(struct Interface *iface)
{
	if (iface->AdvLinkMTU)
		set_interface_linkmtu(iface->props.name, iface->AdvLinkMTU);
	if (iface->ra_header_info.AdvCurHopLimit)
		set_interface_curhlim(iface->props.name, iface->ra_header_info.AdvCurHopLimit);
	if (iface->ra_header_info.AdvReachableTime)
		set_interface_reachtime(iface->props.name, iface->ra_header_info.AdvReachableTime);
	if (iface->ra_header_info.AdvRetransTimer)
		set_interface_retranstimer(iface->props.name, iface->ra_header_info.AdvRetransTimer);
}

/*
 *      send initial advertisement and set timers
 */
static void kickoff_adverts(int sock, struct Interface *iface)
{
	clock_gettime(CLOCK_MONOTONIC, &iface->times.last_ra_time);

	if (iface->UnicastOnly)
		return;

	clock_gettime(CLOCK_MONOTONIC, &iface->times.last_multicast);

	/* send an initial advertisement */
	if (send_ra_forall(sock, iface, NULL) != 0) {
		dlog(LOG_DEBUG, 4, "send_ra_forall failed on interface %s", iface->props.name);
	}

	double next = min(MAX_INITIAL_RTR_ADVERT_INTERVAL, iface->MaxRtrAdvInterval);
	reschedule_iface(iface, next);
}

static void stop_advert_foo(struct Interface *iface, void *data)
{
	if (!iface->UnicastOnly && iface->RemoveAdvOnExit) {
		/* send a final advertisement with zero Router Lifetime */
		dlog(LOG_DEBUG, 4, "stopping all adverts on %s", iface->props.name);
		iface->state_info.cease_adv = 1;
		int sock = *(int *)data;
		send_ra_forall(sock, iface, NULL);
	}
}

static void stop_adverts(int sock, struct Interface *ifaces)
{
	flog(LOG_INFO, "sending stop adverts");
	/*
	 *      send final RA (a SHOULD in RFC4861 section 6.2.5)
	 */
	for_each_iface(ifaces, stop_advert_foo, &sock);
}

static void setup_iface_foo(struct Interface *iface, void *data)
{
	int sock = *(int *)data;

	int setup_iface_result = setup_iface(sock, iface);
	if (setup_iface_result < 0) {
		if (iface->IgnoreIfMissing) {
			dlog(LOG_DEBUG, 4,
					"interface %s does not exist or is not set up properly, ignoring the interface (setup_iface=%d)",
					iface->props.name,
					setup_iface_result
					);
			return;
		} else {
			flog(LOG_ERR,
					"interface %s does not exist or is not set up properly (setup_iface=%d)",
					iface->props.name,
					setup_iface_result
					);
			exit(1);
		}
	}

	config_interface(iface);
	kickoff_adverts(sock, iface);
}

static void cleanup_iface_foo(struct Interface *iface, void *data)
{
	int sock = *(int *)data;
	cleanup_iface(sock, iface);
}

static void setup_ifaces(int sock, struct Interface *ifaces) { for_each_iface(ifaces, setup_iface_foo, &sock); }
static void cleanup_ifaces(int sock, struct Interface *ifaces) { for_each_iface(ifaces, cleanup_iface_foo, &sock); }

static struct Interface *reload_config(int sock, struct Interface *ifaces, char const *conf_path)
{
	cleanup_ifaces(sock, ifaces);
	free_ifaces(ifaces);

	flog(LOG_INFO, "attempting to reread config file");

	/* reread config file */
	ifaces = readin_config(conf_path);
	if (!ifaces) {
		flog(LOG_ERR, "exiting, failed to read config file");
		exit(1);
	}
	setup_ifaces(sock, ifaces);

	flog(LOG_INFO, "resuming normal operation");

	return ifaces;
}

static void sighup_handler(int sig) { sighup_received = 1; }

static void sigterm_handler(int sig)
{
	++sigterm_received;

	if (sigterm_received > 2) {
		abort();
	}
}

static void sigint_handler(int sig)
{
	++sigint_received;

	if (sigint_received > 2) {
		abort();
	}
}

static void sigusr1_handler(int sig) { sigusr1_received = 1; }

static void reset_prefix_lifetimes_foo(struct Interface *iface, void *data)
{
	flog(LOG_INFO, "Resetting prefix lifetimes on %s", iface->props.name);

	for (struct AdvPrefix *prefix = iface->AdvPrefixList; prefix; prefix = prefix->next) {
		if (prefix->DecrementLifetimesFlag) {
			char pfx_str[INET6_ADDRSTRLEN];
			addrtostr(&prefix->Prefix, pfx_str, sizeof(pfx_str));
			dlog(LOG_DEBUG, 4, "%s/%u%%%s plft reset from %u to %u secs", pfx_str, prefix->PrefixLen,
			     iface->props.name, prefix->curr_preferredlft, prefix->AdvPreferredLifetime);
			dlog(LOG_DEBUG, 4, "%s/%u%%%s vlft reset from %u to %u secs", pfx_str, prefix->PrefixLen,
			     iface->props.name, prefix->curr_validlft, prefix->AdvValidLifetime);
			prefix->curr_validlft = prefix->AdvValidLifetime;
			prefix->curr_preferredlft = prefix->AdvPreferredLifetime;
		}
	}
}

static void reset_prefix_lifetimes(struct Interface *ifaces) { for_each_iface(ifaces, reset_prefix_lifetimes_foo, 0); }

static int check_conffile_perm(const char *username, const char *conf_file)
{
	FILE *fp = fopen(conf_file, "r");
	if (fp == NULL) {
		flog(LOG_ERR, "can't open %s: %s", conf_file, strerror(errno));
		return -1;
	}
	fclose(fp);


	struct stat stbuf;
	if (0 != stat(conf_file, &stbuf)) {
		return -1;
	}

	if (stbuf.st_mode & S_IWOTH) {
		flog(LOG_ERR, "Insecure file permissions (writable by others): %s", conf_file);
		return -1;
	}

	if(username != NULL && strncmp(username, "root", 5) != 0) {
		struct passwd *pw = getpwnam(username);
		if (!pw) {
			return -1;
		}

		/* for non-root: must not be writable by self/own group */
		/* TODO: this should check supplementary groups as well, via getgroups and looping */
		if ((stbuf.st_mode & S_IWGRP && pw->pw_gid == stbuf.st_gid) ||
			(stbuf.st_mode & S_IWUSR && pw->pw_uid == stbuf.st_uid)) {
			flog(LOG_ERR, "Insecure file permissions (writable by self/group): %s", conf_file);
			return -1;
		}
	}

	return 0;
}

static void version(void)
{
	fprintf(stderr, "Version: %s\n\n", VERSION);
	fprintf(stderr, "Compiled in settings:\n");
	fprintf(stderr, "  default config file		\"%s\"\n", PATH_RADVD_CONF);
	fprintf(stderr, "  default pidfile		\"%s\"\n", PATH_RADVD_PID);
	fprintf(stderr, "  default logfile		\"%s\"\n", PATH_RADVD_LOG);
	fprintf(stderr, "  default syslog facility	%d\n", LOG_FACILITY);
	fprintf(stderr, "Please send bug reports or suggestions to %s.\n", CONTACT_EMAIL);

	exit(1);
}

static void usage(char const *pname)
{
	fprintf(stderr, "usage: %s %s\n", pname, usage_str);
	exit(1);
}
