The -U option can be used to start chronyd under a non-root user if it is provided with all capabilities and access to files, directories, and devices, needed to operate correctly in the specified configuration. It is not recommended in cases where the configuration is unknown.
652 lines
15 KiB
C
652 lines
15 KiB
C
/*
|
|
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 "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)
|
|
{
|
|
/* 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)
|
|
return;
|
|
|
|
if (!UTI_RemoveFile(NULL, pidfile, NULL))
|
|
;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
MAI_CleanupAndExit(void)
|
|
{
|
|
if (!initialised) exit(exit_status);
|
|
|
|
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();
|
|
|
|
exit(exit_status);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
signal_cleanup(int x)
|
|
{
|
|
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;
|
|
|
|
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 [-4|-6] [-n|-d] [-p|-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 = -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 (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: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;
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
/* ================================================== */
|