/* 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-2016 * * 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 "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) { /* 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); } /* ================================================== */ static void delete_pidfile(void) { const char *pidfile = CNF_GetPidFile(); /* Don't care if this fails, there's not a lot we can do */ unlink(pidfile); } /* ================================================== */ void MAI_CleanupAndExit(void) { if (!initialised) exit(exit_status); if (CNF_GetDumpOnExit()) { SRC_DumpSources(); } /* Don't update clock when removing sources */ REF_SetMode(REF_ModeIgnore); SMT_Finalise(); TMC_Finalise(); MNL_Finalise(); CLG_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(); SCH_Finalise(); LCL_Finalise(); PRV_Finalise(); delete_pidfile(); CNF_Finalise(); LOG_Finalise(); HSH_Finalise(); exit(exit_status); } /* ================================================== */ static void signal_cleanup(int x) { if (!initialised) exit(0); SCH_QuitProgram(); } /* ================================================== */ static void quit_timeout(void *arg) { /* 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 immediatelly 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); } } /* ================================================== */ /* Return 1 if the process exists on the system. */ static int does_process_exist(int pid) { int status; status = getsid(pid); if (status >= 0) { return 1; } else { return 0; } } /* ================================================== */ static int maybe_another_chronyd_running(int *other_pid) { const char *pidfile = CNF_GetPidFile(); FILE *in; int pid, count; *other_pid = 0; in = fopen(pidfile, "r"); if (!in) return 0; count = fscanf(in, "%d", &pid); fclose(in); if (count != 1) return 0; *other_pid = pid; return does_process_exist(pid); } /* ================================================== */ static void write_lockfile(void) { const char *pidfile = CNF_GetPidFile(); FILE *out; out = fopen(pidfile, "w"); if (!out) { LOG_FATAL(LOGF_Main, "could not open lockfile %s for writing", pidfile); } else { fprintf(out, "%d\n", (int)getpid()); fclose(out); } } /* ================================================== */ 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(LOGF_Main, "Could not detach, pipe failed : %s", strerror(errno)); } /* Does this preserve existing signal handlers? */ pid = fork(); if (pid < 0) { LOG_FATAL(LOGF_Main, "Could not detach, 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(LOGF_Main, "Could not detach, 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(LOGF_Main, "Could not chdir to / : %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]); } } } /* ================================================== */ int main (int argc, char **argv) { const char *conf_file = DEFAULT_CONF_FILE; const char *progname = argv[0]; char *user = NULL; struct passwd *pw; int debug = 0, nofork = 0, address_family = IPADDR_UNSPEC; int do_init_rtc = 0, restarted = 0, timeout = 0; int other_pid; int scfilter_level = 0, lock_memory = 0, sched_priority = 0; int system_log = 1; int config_args = 0; do_platform_checks(); LOG_Initialise(); /* Parse command line options */ while (++argv, (--argc)>0) { if (!strcmp("-f", *argv)) { ++argv, --argc; conf_file = *argv; } else if (!strcmp("-P", *argv)) { ++argv, --argc; if (argc == 0 || sscanf(*argv, "%d", &sched_priority) != 1) { LOG_FATAL(LOGF_Main, "Bad scheduler priority"); } } else if (!strcmp("-m", *argv)) { lock_memory = 1; } else if (!strcmp("-r", *argv)) { reload = 1; } else if (!strcmp("-R", *argv)) { restarted = 1; } else if (!strcmp("-u", *argv)) { ++argv, --argc; if (argc == 0) { LOG_FATAL(LOGF_Main, "Missing user name"); } else { user = *argv; } } else if (!strcmp("-F", *argv)) { ++argv, --argc; if (argc == 0 || sscanf(*argv, "%d", &scfilter_level) != 1) LOG_FATAL(LOGF_Main, "Bad syscall filter level"); } else if (!strcmp("-s", *argv)) { do_init_rtc = 1; } else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) { /* This write to the terminal is OK, it comes before we turn into a daemon */ printf("chronyd (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYD_FEATURES); return 0; } else if (!strcmp("-n", *argv)) { nofork = 1; } else if (!strcmp("-d", *argv)) { debug++; nofork = 1; system_log = 0; } else if (!strcmp("-q", *argv)) { ref_mode = REF_ModeUpdateOnce; nofork = 1; system_log = 0; } else if (!strcmp("-Q", *argv)) { ref_mode = REF_ModePrintOnce; nofork = 1; system_log = 0; } else if (!strcmp("-t", *argv)) { ++argv, --argc; if (argc == 0 || sscanf(*argv, "%d", &timeout) != 1 || timeout <= 0) LOG_FATAL(LOGF_Main, "Bad timeout"); } else if (!strcmp("-4", *argv)) { address_family = IPADDR_INET4; } else if (!strcmp("-6", *argv)) { address_family = IPADDR_INET6; } else if (!strcmp("-h", *argv) || !strcmp("--help", *argv)) { printf("Usage: %s [-4|-6] [-n|-d] [-q|-Q] [-r] [-R] [-s] [-t TIMEOUT] [-f FILE|COMMAND...]\n", progname); return 0; } else if (*argv[0] == '-') { LOG_FATAL(LOGF_Main, "Unrecognized command line option [%s]", *argv); } else { /* Process remaining arguments and configuration lines */ config_args = argc; break; } } if (getuid() != 0) { /* This write to the terminal is OK, it comes before we turn into a daemon */ fprintf(stderr,"Not superuser\n"); return 1; } /* Turn into a daemon */ if (!nofork) { go_daemon(); } if (system_log) { LOG_OpenSystemLog(); } LOG_SetDebugLevel(debug); LOG(LOGS_INFO, LOGF_Main, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES); DNS_SetAddressFamily(address_family); CNF_Initialise(restarted); /* Parse the config file or the remaining command line arguments */ if (!config_args) { CNF_ReadFile(conf_file); } else { do { CNF_ParseLine(NULL, config_args - argc + 1, *argv); } while (++argv, --argc); } /* Check whether another chronyd may already be running. Do this after * forking, so that message logging goes to the right place (i.e. syslog), in * case this chronyd is being run from a boot script. */ if (maybe_another_chronyd_running(&other_pid)) { LOG_FATAL(LOGF_Main, "Another chronyd may already be running (pid=%d), check lockfile (%s)", other_pid, CNF_GetPidFile()); } /* Write our lockfile to prevent other chronyds running. This has *GOT* to * be done *AFTER* the daemon-creation fork() */ write_lockfile(); PRV_Initialise(); LCL_Initialise(); SCH_Initialise(); SYS_Initialise(); RTC_Initialise(do_init_rtc); SRC_Initialise(); RCL_Initialise(); KEY_Initialise(); /* Open privileged ports before dropping root */ CAM_Initialise(address_family); NIO_Initialise(address_family); 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(); } if (!user) { user = CNF_GetUser(); } if ((pw = getpwnam(user)) == NULL) LOG_FATAL(LOGF_Main, "Could not get %s uid/gid", user); /* Create all directories before dropping root */ CNF_CreateDirs(pw->pw_uid, pw->pw_gid); /* Drop root privileges if the user has non-zero uid or gid */ if (pw->pw_uid || pw->pw_gid) SYS_DropRoot(pw->pw_uid, pw->pw_gid); REF_Initialise(); SST_Initialise(); NSR_Initialise(); NSD_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); CAM_OpenUnixSocket(); if (scfilter_level) SYS_EnableSystemCallFilter(scfilter_level); if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) { ref_mode = REF_ModeInitStepSlew; } REF_SetModeEndHandler(reference_mode_end); REF_SetMode(ref_mode); if (timeout) 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, LOGF_Main, "chronyd exiting"); MAI_CleanupAndExit(); return 0; } /* ================================================== */