diff options
Diffstat (limited to 'src/main/radiusd.c')
-rw-r--r-- | src/main/radiusd.c | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/src/main/radiusd.c b/src/main/radiusd.c new file mode 100644 index 0000000..06b566d --- /dev/null +++ b/src/main/radiusd.c @@ -0,0 +1,793 @@ +/* + * radiusd.c Main loop of the radius server. + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000-2019 The FreeRADIUS server project + * Copyright 1999,2000 Miquel van Smoorenburg <miquels@cistron.nl> + * Copyright 2000 Alan DeKok <aland@ox.org> + * Copyright 2000 Alan Curry <pacman-radius@cqc.com> + * Copyright 2000 Jeff Carneal <jeff@apex.net> + * Copyright 2000 Chad Miller <cmiller@surfsouth.com> + */ + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/state.h> +#include <freeradius-devel/rad_assert.h> +#include <freeradius-devel/autoconf.h> + +#include <sys/file.h> + +#include <fcntl.h> +#include <ctype.h> + +#ifdef HAVE_GETOPT_H +# include <getopt.h> +#endif + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +#ifdef HAVE_SYSTEMD +# include <systemd/sd-daemon.h> +#endif + +/* + * Global variables. + */ +char const *radacct_dir = NULL; +char const *radlog_dir = NULL; + +bool log_stripped_names; + +char const *radiusd_version = "FreeRADIUS Version " RADIUSD_VERSION_STRING +#ifdef RADIUSD_VERSION_COMMIT +" (git #" STRINGIFY(RADIUSD_VERSION_COMMIT) ")" +#endif +", for host " HOSTINFO +#ifndef ENABLE_REPRODUCIBLE_BUILDS +", built on " __DATE__ " at " __TIME__ +#endif +; + +static pid_t radius_pid; + +/* + * Configuration items. + */ + +/* + * Static functions. + */ +static void usage(int); + +static void sig_fatal (int); +#ifdef SIGHUP +static void sig_hup (int); +#endif + +/* + * The main guy. + */ +int main(int argc, char *argv[]) +{ + int rcode = EXIT_SUCCESS; + int status; + int argval; + bool spawn_flag = true; + bool display_version = false; + int flag = 0; + int from_child[2] = {-1, -1}; + char *p; + fr_state_t *state = NULL; + + /* + * We probably don't want to free the talloc autofree context + * directly, so we'll allocate a new context beneath it, and + * free that before any leak reports. + */ + TALLOC_CTX *autofree = talloc_init("main"); + +#ifdef OSFC2 + set_auth_parameters(argc, argv); +#endif + +#ifdef WIN32 + { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 0), &wsaData)) { + fprintf(stderr, "%s: Unable to initialize socket library.\n", + main_config.name); + exit(EXIT_FAILURE); + } + } +#endif + + rad_debug_lvl = 0; + set_radius_dir(autofree, RADIUS_DIR); + + /* + * Ensure that the configuration is initialized. + */ + memset(&main_config, 0, sizeof(main_config)); + main_config.myip.af = AF_UNSPEC; + main_config.port = 0; + main_config.daemonize = true; + + p = strrchr(argv[0], FR_DIR_SEP); + if (!p) { + main_config.name = argv[0]; + } else { + main_config.name = p + 1; + } + + /* + * Don't put output anywhere until we get told a little + * more. + */ + default_log.dst = L_DST_NULL; + default_log.fd = -1; + main_config.log_file = NULL; + + /* Process the options. */ + while ((argval = getopt(argc, argv, "Cd:D:fhi:l:mMn:p:PstvxX")) != EOF) { + + switch (argval) { + case 'C': + check_config = true; + spawn_flag = false; + main_config.daemonize = false; + break; + + case 'd': + set_radius_dir(autofree, optarg); + break; + + case 'D': + main_config.dictionary_dir = talloc_typed_strdup(autofree, optarg); + break; + + case 'f': + main_config.daemonize = false; + break; + + case 'h': + usage(0); + break; + + case 'l': + if (strcmp(optarg, "stdout") == 0) { + goto do_stdout; + } + main_config.log_file = strdup(optarg); + default_log.dst = L_DST_FILES; + default_log.fd = open(main_config.log_file, + O_WRONLY | O_APPEND | O_CREAT, 0640); + if (default_log.fd < 0) { + fprintf(stderr, "%s: Failed to open log file %s: %s\n", + main_config.name, main_config.log_file, fr_syserror(errno)); + exit(EXIT_FAILURE); + } + fr_log_fp = fdopen(default_log.fd, "a"); + break; + + case 'i': + if (ip_hton(&main_config.myip, AF_UNSPEC, optarg, false) < 0) { + fprintf(stderr, "%s: Invalid IP Address or hostname \"%s\"\n", + main_config.name, optarg); + exit(EXIT_FAILURE); + } + flag |= 1; + break; + + case 'n': + main_config.name = optarg; + break; + + case 'm': + main_config.debug_memory = true; + break; + + case 'M': + main_config.memory_report = true; + main_config.debug_memory = true; + break; + + case 'p': + { + unsigned long port; + + port = strtoul(optarg, 0, 10); + if ((port == 0) || (port > UINT16_MAX)) { + fprintf(stderr, "%s: Invalid port number \"%s\"\n", + main_config.name, optarg); + exit(EXIT_FAILURE); + } + + main_config.port = (uint16_t) port; + flag |= 2; + } + break; + + case 'P': + /* Force the PID to be written, even in -f mode */ + main_config.write_pid = true; + break; + + case 's': /* Single process mode */ + spawn_flag = false; + main_config.daemonize = false; + break; + + case 't': /* no child threads */ + spawn_flag = false; + break; + + case 'v': + display_version = true; + break; + + case 'X': + spawn_flag = false; + main_config.daemonize = false; + rad_debug_lvl += 2; + main_config.log_auth = true; + main_config.log_auth_badpass = true; + main_config.log_auth_goodpass = true; + do_stdout: + fr_log_fp = stdout; + default_log.dst = L_DST_STDOUT; + default_log.fd = STDOUT_FILENO; + break; + + case 'x': + rad_debug_lvl++; + break; + + default: + usage(1); + break; + } + } + + /* + * Mismatch between the binary and the libraries it depends on. + */ + if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { + fr_perror("%s", main_config.name); + exit(EXIT_FAILURE); + } + + if (rad_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) exit(EXIT_FAILURE); + + /* + * Mismatch between build time OpenSSL and linked SSL, better to die + * here than segfault later. + */ +#ifdef HAVE_OPENSSL_CRYPTO_H + if (ssl_check_consistency() < 0) exit(EXIT_FAILURE); +#endif + + if (flag && (flag != 0x03)) { + fprintf(stderr, "%s: The options -i and -p cannot be used individually.\n", + main_config.name); + exit(EXIT_FAILURE); + } + + /* + * According to the talloc peeps, no two threads may modify any part of + * a ctx tree with a common root without synchronisation. + * + * So we can't run with a null context and threads. + */ + if (main_config.memory_report) { + if (spawn_flag) { + fprintf(stderr, "%s: The server cannot produce memory reports (-M) in threaded mode\n", + main_config.name); + exit(EXIT_FAILURE); + } + talloc_enable_null_tracking(); + } else { + talloc_disable_null_tracking(); + } + + /* + * Better here, so it doesn't matter whether we get passed -xv or -vx. + */ + if (display_version) { + if (rad_debug_lvl == 0) rad_debug_lvl = 1; + fr_log_fp = stdout; + default_log.dst = L_DST_STDOUT; + default_log.fd = STDOUT_FILENO; + + INFO("%s: %s", main_config.name, radiusd_version); + version_print(); + exit(EXIT_SUCCESS); + } + + if (rad_debug_lvl) version_print(); + + /* + * Under linux CAP_SYS_PTRACE is usually only available before setuid/setguid, + * so we need to check whether we can attach before calling those functions + * (in main_config_init()). + */ + fr_store_debug_state(); + + /* + * Initialising OpenSSL once, here, is safer than having individual modules do it. + */ +#ifdef HAVE_OPENSSL_CRYPTO_H + if (tls_global_init(spawn_flag, check_config) < 0) exit(EXIT_FAILURE); +#endif + + /* + * Write the PID always if we're running as a daemon. + */ + if (main_config.daemonize) main_config.write_pid = true; + + /* + * Read the configuration files, BEFORE doing anything else. + */ + if (main_config_init() < 0) exit(EXIT_FAILURE); + + /* + * This is very useful in figuring out why the panic_action didn't fire. + */ + INFO("%s", fr_debug_state_to_msg(fr_debug_state)); + + /* + * Check for vulnerabilities in the version of libssl were linked against. + */ +#if defined(HAVE_OPENSSL_CRYPTO_H) && defined(ENABLE_OPENSSL_VERSION_CHECK) + if (tls_global_version_check(main_config.allow_vulnerable_openssl) < 0) exit(EXIT_FAILURE); +#endif + + fr_talloc_fault_setup(); + + /* + * Set the panic action (if required) + */ + { + char const *panic_action = NULL; + + panic_action = getenv("PANIC_ACTION"); + if (!panic_action) panic_action = main_config.panic_action; + + if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) { + fr_perror("%s", main_config.name); + exit(EXIT_FAILURE); + } + } + + /* + * The systemd watchdog enablement must be checked before we + * daemonize, but the notifications can come from any process. + */ +#ifdef HAVE_SYSTEMD_WATCHDOG + if (!check_config) { + uint64_t usec; + + if ((sd_watchdog_enabled(0, &usec) > 0) && (usec > 0)) { + usec /= 2; + fr_timeval_from_usec(&sd_watchdog_interval, usec); + + INFO("systemd watchdog interval is %ld.%.2ld secs", sd_watchdog_interval.tv_sec, sd_watchdog_interval.tv_usec); + } else { + INFO("systemd watchdog is disabled"); + } + } +#else + /* + * Some users get frustrated due to can't handle the service using "systemctl start radiusd" + * even when the SO supports systemd. The reason is because the FreeRADIUS version was built + * without the proper support. + * + * Then, as can be seen in https://www.systutorials.com/docs/linux/man/3-sd_notify/ + * We could assume that if find the NOTIFY_SOCKET, it's because we are under systemd. + * + */ + if (getenv("NOTIFY_SOCKET")) + WARN("Built without support for systemd watchdog, but running under systemd."); +#endif + +#ifndef __MINGW32__ + /* + * Disconnect from session + */ + if (main_config.daemonize) { + pid_t pid; + int devnull; + + /* + * Really weird things happen if we leave stdin open and call things like + * system() later. + */ + devnull = open("/dev/null", O_RDWR); + if (devnull < 0) { + ERROR("Failed opening /dev/null: %s", fr_syserror(errno)); + exit(EXIT_FAILURE); + } + dup2(devnull, STDIN_FILENO); + + close(devnull); + + if (pipe(from_child) != 0) { + ERROR("Couldn't open pipe for child status: %s", fr_syserror(errno)); + exit(EXIT_FAILURE); + } + + pid = fork(); + if (pid < 0) { + ERROR("Couldn't fork: %s", fr_syserror(errno)); + exit(EXIT_FAILURE); + } + + /* + * The parent exits, so the child can run in the background. + * + * As the child can still encounter an error during initialisation + * we do a blocking read on a pipe between it and the parent. + * + * Just before entering the event loop the child will send a success + * or failure message to the parent, via the pipe. + */ + if (pid > 0) { + uint8_t ret = 0; + int stat_loc; + + /* So the pipe is correctly widowed if the child exits */ + close(from_child[1]); + + /* + * The child writes a 0x01 byte on success, and closes + * the pipe on error. + */ + if ((read(from_child[0], &ret, 1) < 0)) { + ret = 0; + } + + /* For cleanliness... */ + close(from_child[0]); + + /* Don't turn children into zombies */ + if (!ret) { + waitpid(pid, &stat_loc, WNOHANG); + exit(EXIT_FAILURE); + } + +#ifdef HAVE_SYSTEMD + /* + * Update the systemd MAINPID to be our child, + * as the parent is about to exit. + */ + sd_notifyf(0, "MAINPID=%lu", (unsigned long)pid); +#endif + + exit(EXIT_SUCCESS); + } + + /* so the pipe is correctly widowed if the parent exits?! */ + close(from_child[0]); +# ifdef HAVE_SETSID + setsid(); +# endif + } +#endif + + /* + * Ensure that we're using the CORRECT pid after forking, NOT the one + * we started with. + */ + radius_pid = getpid(); + + /* + * Initialize any event loops just enough so module instantiations can + * add fd/event to them, but do not start them yet. + * + * This has to be done post-fork in case we're using kqueue, where the + * queue isn't inherited by the child process. + */ + if (!radius_event_init(autofree)) exit(EXIT_FAILURE); + + /* + * Load the modules + */ + if (modules_init(main_config.config) < 0) exit(EXIT_FAILURE); + + /* + * Redirect stderr/stdout as appropriate. + */ + if (radlog_init(&default_log, main_config.daemonize) < 0) { + ERROR("%s", fr_strerror()); + exit(EXIT_FAILURE); + } + + event_loop_started = true; + + /* + * Start the event loop(s) and threads. + */ + radius_event_start(main_config.config, spawn_flag); + + /* + * Now that we've set everything up, we can install the signal + * handlers. Before this, if we get any signal, we don't know + * what to do, so we might as well do the default, and die. + */ +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + if ((fr_set_signal(SIGHUP, sig_hup) < 0) || + (fr_set_signal(SIGTERM, sig_fatal) < 0)) { + set_signal_error: + ERROR("%s", fr_strerror()); + exit(EXIT_FAILURE); + } + + /* + * If we're debugging, then a CTRL-C will cause the server to die + * immediately. Use SIGTERM to shut down the server cleanly in + * that case. + */ + if (fr_set_signal(SIGINT, sig_fatal) < 0) goto set_signal_error; + +#ifdef SIGQUIT + if (main_config.debug_memory || (rad_debug_lvl == 0)) { + if (fr_set_signal(SIGQUIT, sig_fatal) < 0) goto set_signal_error; + } +#endif + + /* + * Everything seems to have loaded OK, exit gracefully. + */ + if (check_config) { + DEBUG("Configuration appears to be OK"); + + /* for -C -m|-M */ + if (main_config.debug_memory) goto cleanup; + + exit(EXIT_SUCCESS); + } + +#ifdef WITH_STATS + radius_stats_init(0); +#endif + + /* + * Write the PID after we've forked, so that we write the correct one. + */ + if (main_config.write_pid) { + FILE *fp; + + fp = fopen(main_config.pid_file, "w"); + if (fp != NULL) { + /* + * @fixme What about following symlinks, + * and having it over-write a normal file? + */ + fprintf(fp, "%d\n", (int) radius_pid); + fclose(fp); + } else { + ERROR("Failed creating PID file %s: %s", main_config.pid_file, fr_syserror(errno)); + exit(EXIT_FAILURE); + } + } + + exec_trigger(NULL, NULL, "server.start", false); + + /* + * Inform the parent (who should still be waiting) that the rest of + * initialisation went OK, and that it should exit with a 0 status. + * If we don't get this far, then we just close the pipe on exit, and the + * parent gets a read failure. + */ + if (main_config.daemonize) { + if (write(from_child[1], "\001", 1) < 0) { + WARN("Failed informing parent of successful start: %s", + fr_syserror(errno)); + } + close(from_child[1]); + } + + /* + * Clear the libfreeradius error buffer. + */ + fr_strerror(); + + /* + * Initialise the state rbtree (used to link multiple rounds of challenges). + */ + state = fr_state_init(NULL); + +#ifdef HAVE_SYSTEMD + { + int ret_notif; + + ret_notif = sd_notify(0, "READY=1\nSTATUS=Processing requests"); + if (ret_notif < 0) + WARN("Failed notifying systemd that process is READY: %s", fr_syserror(ret_notif)); + } +#endif + + /* + * Process requests until HUP or exit. + */ + while ((status = radius_event_process()) == 0x80) { +#ifdef WITH_STATS + radius_stats_init(1); +#endif + main_config_hup(); + } + if (status < 0) { + ERROR("Exiting due to internal error: %s", fr_strerror()); + rcode = EXIT_FAILURE; + } else { + INFO("Exiting normally"); + } + + /* + * Ignore the TERM signal: we're about to die. + */ + signal(SIGTERM, SIG_IGN); + + /* + * Fire signal and stop triggers after ignoring SIGTERM, so handlers are + * not killed with the rest of the process group, below. + */ + if (status == 2) exec_trigger(NULL, NULL, "server.signal.term", true); + exec_trigger(NULL, NULL, "server.stop", false); + + /* + * Send a TERM signal to all associated processes + * (including us, which gets ignored.) + */ +#ifndef __MINGW32__ + if (spawn_flag) kill(-radius_pid, SIGTERM); +#endif + + /* + * We're exiting, so we can delete the PID file. + * (If it doesn't exist, we can ignore the error returned by unlink) + */ + if (main_config.daemonize) unlink(main_config.pid_file); + + radius_event_free(); + +cleanup: + /* + * Detach any modules. + */ + modules_free(); + + xlat_free(); /* modules may have xlat's */ + + fr_state_delete(state); + + /* + * Free the configuration items. + */ + main_config_free(); + +#ifdef WITH_COA_TUNNEL + /* + * This should be after freeing all of the listeners. + */ + listen_coa_free(); +#endif + +#ifdef WIN32 + WSACleanup(); +#endif + +#ifdef HAVE_OPENSSL_CRYPTO_H + tls_global_cleanup(); +#endif + + /* + * So we don't see autofreed memory in the talloc report + */ + talloc_free(autofree); + + if (main_config.memory_report) { + INFO("Allocated memory at time of report:"); + fr_log_talloc_report(NULL); + } + + return rcode; +} + + +/* + * Display the syntax for starting this program. + */ +static void NEVER_RETURNS usage(int status) +{ + FILE *output = status?stderr:stdout; + + fprintf(output, "Usage: %s [options]\n", main_config.name); + fprintf(output, "Options:\n"); + fprintf(output, " -C Check configuration and exit.\n"); + fprintf(stderr, " -d <raddb> Set configuration directory (defaults to " RADDBDIR ").\n"); + fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n"); + fprintf(output, " -f Run as a foreground process, not a daemon.\n"); + fprintf(output, " -h Print this help message.\n"); + fprintf(output, " -i <ipaddr> Listen on ipaddr ONLY.\n"); + fprintf(output, " -l <log_file> Logging output will be written to this file.\n"); + fprintf(output, " -m On SIGINT or SIGQUIT clean up all used memory instead of just exiting.\n"); + fprintf(output, " -n <name> Read raddb/name.conf instead of raddb/radiusd.conf.\n"); + fprintf(output, " -p <port> Listen on port ONLY.\n"); + fprintf(output, " -P Always write out PID, even with -f.\n"); + fprintf(output, " -s Do not spawn child processes to handle requests (same as -ft).\n"); + fprintf(output, " -t Disable threads.\n"); + fprintf(output, " -v Print server version information.\n"); + fprintf(output, " -X Turn on full debugging (similar to -tfxxl stdout).\n"); + fprintf(output, " -x Turn on additional debugging (-xx gives more debugging).\n"); + exit(status); +} + + +/* + * We got a fatal signal. + */ +static void sig_fatal(int sig) +{ + if (getpid() != radius_pid) _exit(sig); + + switch (sig) { + case SIGTERM: + radius_signal_self(RADIUS_SIGNAL_SELF_TERM); + break; + + case SIGINT: +#ifdef SIGQUIT + case SIGQUIT: +#endif + if (main_config.debug_memory || main_config.memory_report) { + radius_signal_self(RADIUS_SIGNAL_SELF_TERM); + break; + } + /* FALL-THROUGH */ + + default: + fr_exit(sig); + } +} + +#ifdef SIGHUP +/* + * We got the hangup signal. + * Re-read the configuration files. + */ +static void sig_hup(UNUSED int sig) +{ + reset_signal(SIGHUP, sig_hup); + + radius_signal_self(RADIUS_SIGNAL_SELF_HUP); +} +#endif |