mirror of
https://github.com/radvd-project/radvd.git
synced 2025-12-20 01:11:37 +08:00
Previously, when an interface was removed radvd did not release it's multicast membership leaking kernel resources leading to a potential out of memory failure of the `setsockopt([...], IPV6_ADD_MEMBERSHIP, [...])` call. Signed-off-by: Antoine Damhet <antoine.damhet@blade-group.com>
936 lines
24 KiB
C
936 lines
24 KiB
C
/*
|
|
*
|
|
* 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 <reubenhwk@gmail.com>.
|
|
*
|
|
*/
|
|
|
|
#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 drop_root_privileges(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 drop_root_privileges(const char *username)
|
|
{
|
|
struct passwd *pw = getpwnam(username);
|
|
if (pw) {
|
|
if (initgroups(username, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0 || setuid(pw->pw_uid) != 0) {
|
|
flog(LOG_ERR, "Couldn't change to '%.32s' uid=%d gid=%d", username, pw->pw_uid, pw->pw_gid);
|
|
return -1;
|
|
}
|
|
} else {
|
|
flog(LOG_ERR, "Couldn't find user '%.32s'", username);
|
|
return -1;
|
|
}
|
|
return 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);
|
|
}
|