summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c694
1 files changed, 694 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..c40b5e4
--- /dev/null
+++ b/main.c
@@ -0,0 +1,694 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) John G. Hasler 2009
+ * Copyright (C) Miroslav Lichvar 2012-2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The main program
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "main.h"
+#include "sched.h"
+#include "local.h"
+#include "sys.h"
+#include "ntp_io.h"
+#include "ntp_signd.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "nts_ke_server.h"
+#include "nts_ntp_server.h"
+#include "socket.h"
+#include "sources.h"
+#include "sourcestats.h"
+#include "reference.h"
+#include "logging.h"
+#include "conf.h"
+#include "cmdmon.h"
+#include "keys.h"
+#include "manual.h"
+#include "rtc.h"
+#include "refclock.h"
+#include "clientlog.h"
+#include "nameserv.h"
+#include "privops.h"
+#include "smooth.h"
+#include "tempcomp.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* Set when the initialisation chain has been completed. Prevents finalisation
+ * chain being run if a fatal error happened early. */
+
+static int initialised = 0;
+
+static int exit_status = 0;
+
+static int reload = 0;
+
+static REF_Mode ref_mode = REF_ModeNormal;
+
+/* ================================================== */
+
+static void
+do_platform_checks(void)
+{
+ struct timespec ts;
+
+ /* Require at least 32-bit integers, two's complement representation and
+ the usual implementation of conversion of unsigned integers */
+ assert(sizeof (int) >= 4);
+ assert(-1 == ~0);
+ assert((int32_t)4294967295U == (int32_t)-1);
+
+ /* Require time_t and tv_nsec in timespec to be signed */
+ ts.tv_sec = -1;
+ ts.tv_nsec = -1;
+ assert(ts.tv_sec < 0 && ts.tv_nsec < 0);
+}
+
+/* ================================================== */
+
+static void
+delete_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+
+ if (!pidfile)
+ return;
+
+ if (!UTI_RemoveFile(NULL, pidfile, NULL))
+ ;
+}
+
+/* ================================================== */
+
+void
+MAI_CleanupAndExit(void)
+{
+ if (!initialised) exit(exit_status);
+
+ LCL_CancelOffsetCorrection();
+ SRC_DumpSources();
+
+ /* Don't update clock when removing sources */
+ REF_SetMode(REF_ModeIgnore);
+
+ SMT_Finalise();
+ TMC_Finalise();
+ MNL_Finalise();
+ CLG_Finalise();
+ NKS_Finalise();
+ NNS_Finalise();
+ NSD_Finalise();
+ NSR_Finalise();
+ SST_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ CAM_Finalise();
+
+ KEY_Finalise();
+ RCL_Finalise();
+ SRC_Finalise();
+ REF_Finalise();
+ RTC_Finalise();
+ SYS_Finalise();
+
+ SCK_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ PRV_Finalise();
+
+ delete_pidfile();
+
+ CNF_Finalise();
+ HSH_Finalise();
+ LOG_Finalise();
+
+ UTI_ResetGetRandomFunctions();
+
+ exit(exit_status);
+}
+
+/* ================================================== */
+
+static void
+signal_cleanup(int x)
+{
+ SCH_QuitProgram();
+}
+
+/* ================================================== */
+
+static void
+quit_timeout(void *arg)
+{
+ LOG(LOGS_INFO, "Timeout reached");
+
+ /* Return with non-zero status if the clock is not synchronised */
+ exit_status = REF_GetOurStratum() >= NTP_MAX_STRATUM;
+ SCH_QuitProgram();
+}
+
+/* ================================================== */
+
+static void
+ntp_source_resolving_end(void)
+{
+ NSR_SetSourceResolvingEndHandler(NULL);
+
+ if (reload) {
+ /* Note, we want reload to come well after the initialisation from
+ the real time clock - this gives us a fighting chance that the
+ system-clock scale for the reloaded samples still has a
+ semblence of validity about it. */
+ SRC_ReloadSources();
+ }
+
+ SRC_RemoveDumpFiles();
+ RTC_StartMeasurements();
+ RCL_StartRefclocks();
+ NSR_StartSources();
+ NSR_AutoStartSources();
+
+ /* Special modes can end only when sources update their reachability.
+ Give up immediately if there are no active sources. */
+ if (ref_mode != REF_ModeNormal && !SRC_ActiveSources()) {
+ REF_SetUnsynchronised();
+ }
+}
+
+/* ================================================== */
+
+static void
+post_init_ntp_hook(void *anything)
+{
+ if (ref_mode == REF_ModeInitStepSlew) {
+ /* Remove the initstepslew sources and set normal mode */
+ NSR_RemoveAllSources();
+ ref_mode = REF_ModeNormal;
+ REF_SetMode(ref_mode);
+ }
+
+ /* Close the pipe to the foreground process so it can exit */
+ LOG_CloseParentFd();
+
+ CNF_AddSources();
+ CNF_AddBroadcasts();
+
+ NSR_SetSourceResolvingEndHandler(ntp_source_resolving_end);
+ NSR_ResolveSources();
+}
+
+/* ================================================== */
+
+static void
+reference_mode_end(int result)
+{
+ switch (ref_mode) {
+ case REF_ModeNormal:
+ case REF_ModeUpdateOnce:
+ case REF_ModePrintOnce:
+ exit_status = !result;
+ SCH_QuitProgram();
+ break;
+ case REF_ModeInitStepSlew:
+ /* Switch to the normal mode, the delay is used to prevent polling
+ interval shorter than the burst interval if some configured servers
+ were used also for initstepslew */
+ SCH_AddTimeoutByDelay(2.0, post_init_ntp_hook, NULL);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+static void
+post_init_rtc_hook(void *anything)
+{
+ if (CNF_GetInitSources() > 0) {
+ CNF_AddInitSources();
+ NSR_StartSources();
+ assert(REF_GetMode() != REF_ModeNormal);
+ /* Wait for mode end notification */
+ } else {
+ (post_init_ntp_hook)(NULL);
+ }
+}
+
+/* ================================================== */
+
+static void
+check_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+ FILE *in;
+ int pid, count;
+
+ if (!pidfile)
+ return;
+
+ in = UTI_OpenFile(NULL, pidfile, NULL, 'r', 0);
+ if (!in)
+ return;
+
+ count = fscanf(in, "%d", &pid);
+ fclose(in);
+
+ if (count != 1)
+ return;
+
+ if (getsid(pid) < 0)
+ return;
+
+ LOG_FATAL("Another chronyd may already be running (pid=%d), check %s",
+ pid, pidfile);
+}
+
+/* ================================================== */
+
+static void
+write_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+ FILE *out;
+
+ if (!pidfile)
+ return;
+
+ out = UTI_OpenFile(NULL, pidfile, NULL, 'W', 0644);
+ fprintf(out, "%d\n", (int)getpid());
+ fclose(out);
+}
+
+/* ================================================== */
+
+#define DEV_NULL "/dev/null"
+
+static void
+go_daemon(void)
+{
+ int pid, fd, pipefd[2];
+
+ /* Create pipe which will the daemon use to notify the grandparent
+ when it's initialised or send an error message */
+ if (pipe(pipefd)) {
+ LOG_FATAL("pipe() failed : %s", strerror(errno));
+ }
+
+ /* Does this preserve existing signal handlers? */
+ pid = fork();
+
+ if (pid < 0) {
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+ } else if (pid > 0) {
+ /* In the 'grandparent' */
+ char message[1024];
+ int r;
+
+ close(pipefd[1]);
+ r = read(pipefd[0], message, sizeof (message));
+ if (r) {
+ if (r > 0) {
+ /* Print the error message from the child */
+ message[sizeof (message) - 1] = '\0';
+ fprintf(stderr, "%s\n", message);
+ }
+ exit(1);
+ } else
+ exit(0);
+ } else {
+ close(pipefd[0]);
+
+ setsid();
+
+ /* Do 2nd fork, as-per recommended practice for launching daemons. */
+ pid = fork();
+
+ if (pid < 0) {
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+ } else if (pid > 0) {
+ exit(0); /* In the 'parent' */
+ } else {
+ /* In the child we want to leave running as the daemon */
+
+ /* Change current directory to / */
+ if (chdir("/") < 0) {
+ LOG_FATAL("chdir() failed : %s", strerror(errno));
+ }
+
+ /* Don't keep stdin/out/err from before. But don't close
+ the parent pipe yet. */
+ for (fd=0; fd<1024; fd++) {
+ if (fd != pipefd[1])
+ close(fd);
+ }
+
+ LOG_SetParentFd(pipefd[1]);
+
+ /* Open /dev/null as new stdin/out/err */
+ errno = 0;
+ if (open(DEV_NULL, O_RDONLY) != STDIN_FILENO ||
+ open(DEV_NULL, O_WRONLY) != STDOUT_FILENO ||
+ open(DEV_NULL, O_RDWR) != STDERR_FILENO)
+ LOG_FATAL("Could not open %s : %s", DEV_NULL, strerror(errno));
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+print_help(const char *progname)
+{
+ printf("Usage: %s [OPTION]... [DIRECTIVE]...\n\n"
+ "Options:\n"
+ " -4\t\tUse IPv4 addresses only\n"
+ " -6\t\tUse IPv6 addresses only\n"
+ " -f FILE\tSpecify configuration file (%s)\n"
+ " -n\t\tDon't run as daemon\n"
+ " -d\t\tDon't run as daemon and log to stderr\n"
+#if DEBUG > 0
+ " -d -d\t\tEnable debug messages\n"
+#endif
+ " -l FILE\tLog to file\n"
+ " -L LEVEL\tSet logging threshold (0)\n"
+ " -p\t\tPrint configuration and exit\n"
+ " -q\t\tSet clock and exit\n"
+ " -Q\t\tLog offset and exit\n"
+ " -r\t\tReload dump files\n"
+ " -R\t\tAdapt configuration for restart\n"
+ " -s\t\tSet clock from RTC\n"
+ " -t SECONDS\tExit after elapsed time\n"
+ " -u USER\tSpecify user (%s)\n"
+ " -U\t\tDon't check for root\n"
+ " -F LEVEL\tSet system call filter level (0)\n"
+ " -P PRIORITY\tSet process priority (0)\n"
+ " -m\t\tLock memory\n"
+ " -x\t\tDon't control clock\n"
+ " -v, --version\tPrint version and exit\n"
+ " -h, --help\tPrint usage and exit\n",
+ progname, DEFAULT_CONF_FILE, DEFAULT_USER);
+}
+
+/* ================================================== */
+
+static void
+print_version(void)
+{
+ printf("chronyd (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYD_FEATURES);
+}
+
+/* ================================================== */
+
+static int
+parse_int_arg(const char *arg)
+{
+ int i;
+
+ if (sscanf(arg, "%d", &i) != 1)
+ LOG_FATAL("Invalid argument %s", arg);
+ return i;
+}
+
+/* ================================================== */
+
+int main
+(int argc, char **argv)
+{
+ const char *conf_file = DEFAULT_CONF_FILE;
+ const char *progname = argv[0];
+ char *user = NULL, *log_file = NULL;
+ struct passwd *pw;
+ int opt, debug = 0, nofork = 0, address_family = IPADDR_UNSPEC;
+ int do_init_rtc = 0, restarted = 0, client_only = 0, timeout = -1;
+ int scfilter_level = 0, lock_memory = 0, sched_priority = 0;
+ int clock_control = 1, system_log = 1, log_severity = LOGS_INFO;
+ int user_check = 1, config_args = 0, print_config = 0;
+
+ do_platform_checks();
+
+ LOG_Initialise();
+
+ /* Parse long command-line options */
+ for (optind = 1; optind < argc; optind++) {
+ if (!strcmp("--help", argv[optind])) {
+ print_help(progname);
+ return 0;
+ } else if (!strcmp("--version", argv[optind])) {
+ print_version();
+ return 0;
+ }
+ }
+
+ optind = 1;
+
+ /* Parse short command-line options */
+ while ((opt = getopt(argc, argv, "46df:F:hl:L:mnpP:qQrRst:u:Uvx")) != -1) {
+ switch (opt) {
+ case '4':
+ case '6':
+ address_family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6;
+ break;
+ case 'd':
+ debug++;
+ nofork = 1;
+ system_log = 0;
+ break;
+ case 'f':
+ conf_file = optarg;
+ break;
+ case 'F':
+ scfilter_level = parse_int_arg(optarg);
+ break;
+ case 'l':
+ log_file = optarg;
+ break;
+ case 'L':
+ log_severity = parse_int_arg(optarg);
+ break;
+ case 'm':
+ lock_memory = 1;
+ break;
+ case 'n':
+ nofork = 1;
+ break;
+ case 'p':
+ print_config = 1;
+ user_check = 0;
+ nofork = 1;
+ system_log = 0;
+ log_severity = LOGS_WARN;
+ break;
+ case 'P':
+ sched_priority = parse_int_arg(optarg);
+ break;
+ case 'q':
+ ref_mode = REF_ModeUpdateOnce;
+ nofork = 1;
+ client_only = 0;
+ system_log = 0;
+ break;
+ case 'Q':
+ ref_mode = REF_ModePrintOnce;
+ nofork = 1;
+ client_only = 1;
+ user_check = 0;
+ clock_control = 0;
+ system_log = 0;
+ break;
+ case 'r':
+ reload = 1;
+ break;
+ case 'R':
+ restarted = 1;
+ break;
+ case 's':
+ do_init_rtc = 1;
+ break;
+ case 't':
+ timeout = parse_int_arg(optarg);
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ case 'U':
+ user_check = 0;
+ break;
+ case 'v':
+ print_version();
+ return 0;
+ case 'x':
+ clock_control = 0;
+ break;
+ default:
+ print_help(progname);
+ return opt != 'h';
+ }
+ }
+
+ if (user_check && getuid() != 0)
+ LOG_FATAL("Not superuser");
+
+ /* Turn into a daemon */
+ if (!nofork) {
+ go_daemon();
+ }
+
+ if (log_file) {
+ LOG_OpenFileLog(log_file);
+ } else if (system_log) {
+ LOG_OpenSystemLog();
+ }
+
+ LOG_SetMinSeverity(debug >= 2 ? LOGS_DEBUG : log_severity);
+
+ LOG(LOGS_INFO, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES);
+
+ DNS_SetAddressFamily(address_family);
+
+ CNF_Initialise(restarted, client_only);
+ if (print_config)
+ CNF_EnablePrint();
+
+ /* Parse the config file or the remaining command line arguments */
+ config_args = argc - optind;
+ if (!config_args) {
+ CNF_ReadFile(conf_file);
+ } else {
+ for (; optind < argc; optind++)
+ CNF_ParseLine(NULL, config_args + optind - argc + 1, argv[optind]);
+ }
+
+ if (print_config)
+ return 0;
+
+ /* Check whether another chronyd may already be running */
+ check_pidfile();
+
+ if (!user)
+ user = CNF_GetUser();
+
+ pw = getpwnam(user);
+ if (!pw)
+ LOG_FATAL("Could not get user/group ID of %s", user);
+
+ /* Create directories for sockets, log files, and dump files */
+ CNF_CreateDirs(pw->pw_uid, pw->pw_gid);
+
+ /* Write our pidfile to prevent other instances from running */
+ write_pidfile();
+
+ PRV_Initialise();
+ LCL_Initialise();
+ SCH_Initialise();
+ SCK_Initialise(address_family);
+
+ /* Start helper processes if needed */
+ NKS_PreInitialise(pw->pw_uid, pw->pw_gid, scfilter_level);
+
+ SYS_Initialise(clock_control);
+ RTC_Initialise(do_init_rtc);
+ SRC_Initialise();
+ RCL_Initialise();
+ KEY_Initialise();
+
+ /* Open privileged ports before dropping root */
+ CAM_Initialise();
+ NIO_Initialise();
+ NCR_Initialise();
+ CNF_SetupAccessRestrictions();
+
+ /* Command-line switch must have priority */
+ if (!sched_priority) {
+ sched_priority = CNF_GetSchedPriority();
+ }
+ if (sched_priority) {
+ SYS_SetScheduler(sched_priority);
+ }
+
+ if (lock_memory || CNF_GetLockMemory()) {
+ SYS_LockMemory();
+ }
+
+ /* Drop root privileges if the specified user has a non-zero UID */
+ if (!geteuid() && (pw->pw_uid || pw->pw_gid))
+ SYS_DropRoot(pw->pw_uid, pw->pw_gid, SYS_MAIN_PROCESS);
+
+ if (!geteuid())
+ LOG(LOGS_WARN, "Running with root privileges");
+
+ REF_Initialise();
+ SST_Initialise();
+ NSR_Initialise();
+ NSD_Initialise();
+ NNS_Initialise();
+ NKS_Initialise();
+ CLG_Initialise();
+ MNL_Initialise();
+ TMC_Initialise();
+ SMT_Initialise();
+
+ /* From now on, it is safe to do finalisation on exit */
+ initialised = 1;
+
+ UTI_SetQuitSignalsHandler(signal_cleanup, 1);
+
+ CAM_OpenUnixSocket();
+
+ if (scfilter_level)
+ SYS_EnableSystemCallFilter(scfilter_level, SYS_MAIN_PROCESS);
+
+ if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) {
+ ref_mode = REF_ModeInitStepSlew;
+ }
+
+ REF_SetModeEndHandler(reference_mode_end);
+ REF_SetMode(ref_mode);
+
+ if (timeout >= 0)
+ SCH_AddTimeoutByDelay(timeout, quit_timeout, NULL);
+
+ if (do_init_rtc) {
+ RTC_TimeInit(post_init_rtc_hook, NULL);
+ } else {
+ post_init_rtc_hook(NULL);
+ }
+
+ /* The program normally runs under control of the main loop in
+ the scheduler. */
+ SCH_MainLoop();
+
+ LOG(LOGS_INFO, "chronyd exiting");
+
+ MAI_CleanupAndExit();
+
+ return 0;
+}
+
+/* ================================================== */