Blob Blame History Raw
/*
  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-2018
 * 
 * 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();

  if (!pidfile[0])
    return;

  /* 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_GetDumpDir()[0] != '\0') {
    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();
  HSH_Finalise();
  LOG_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);
  }
}

/* ================================================== */

static void
check_pidfile(void)
{
  const char *pidfile = CNF_GetPidFile();
  FILE *in;
  int pid, count;
  
  in = fopen(pidfile, "r");
  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[0])
    return;

  out = fopen(pidfile, "w");
  if (!out) {
    LOG_FATAL("Could not open %s : %s", pidfile, strerror(errno));
  } else {
    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 [-4|-6] [-n|-d] [-q|-Q] [-r] [-R] [-s] [-t TIMEOUT] [-f FILE|COMMAND...]\n",
             progname);
}

/* ================================================== */

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 = 0;
  int scfilter_level = 0, lock_memory = 0, sched_priority = 0;
  int clock_control = 1, system_log = 1;
  int config_args = 0;

  do_platform_checks();

  LOG_Initialise();

  /* Parse (undocumented) 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:mnP:qQrRst:u:vx")) != -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 'm':
        lock_memory = 1;
        break;
      case 'n':
        nofork = 1;
        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;
        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 'v':
        print_version();
        return 0;
      case 'x':
        clock_control = 0;
        break;
      default:
        print_help(progname);
        return opt != 'h';
    }
  }

  if (getuid() && !client_only)
    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_SetDebugLevel(debug);
  
  LOG(LOGS_INFO, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES);

  DNS_SetAddressFamily(address_family);

  CNF_Initialise(restarted, client_only);

  /* 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]);
  }

  /* 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();
  SYS_Initialise(clock_control);
  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();
  }

  /* 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);

  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, 1);

  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 > 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;
}

/* ================================================== */