/* * tlsdated.c - invoke tlsdate when necessary. * Copyright (c) 2012 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * We invoke tlsdate once at system startup, then we start trying to invoke * tlsdate when a new network route appears. We try a few times after each route * comes up. As soon as we get a successful tlsdate run, we save that timestamp * to disk, then linger to wait for system shutdown. At system shutdown * (indicated by us getting SIGTERM), we save our timestamp to disk. */ #include "config.h" #include <assert.h> #include <errno.h> #include <grp.h> /* setgroups */ #include <fcntl.h> #include <limits.h> #include <linux/rtc.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/wait.h> #include <time.h> #include <unistd.h> #include <event2/event.h> #include "src/conf.h" #include "src/routeup.h" #include "src/util.h" #include "src/tlsdate.h" #include "src/dbus.h" #include "src/platform.h" static const char kTlsdatedOpts[] = "hwrpt:d:T:D:c:a:lsvbm:j:f:x:Uu:g:G:"; const char *kCacheDir = DEFAULT_DAEMON_CACHEDIR; int is_sane_time (time_t ts) { return ts > RECENT_COMPILE_DATE && ts < TLSDATED_MAX_DATE; } /* * Load a time value out of the file named by path. Returns 0 if successful, * -1 if not. The file contains the time in seconds since epoch in host byte * order. */ int load_disk_timestamp (const char *path, time_t * t) { int fd = platform->file_open (path, 0 /* RDONLY */, 1 /* CLOEXEC */); time_t tmpt = 0; if (fd < 0) { perror ("Can't open %s for reading", path); return -1; } if (platform->file_read(fd, &tmpt, sizeof(tmpt))) { perror ("Can't read seconds from %s", path); platform->file_close (fd); return -1; } platform->file_close (fd); if (!is_sane_time (tmpt)) { error ("Disk timestamp is not sane: %ld", tmpt); return -1; } *t = tmpt; return 0; } void usage (const char *progn) { printf ("Usage: %s [flags...] [--] [tlsdate command...]\n", progn); printf (" -w don't set hwclock\n"); printf (" -p dry run (don't really set time)\n"); printf (" -r use stdin instead of netlink for routes\n"); printf (" -t <n> try n times to synchronize the time\n"); printf (" -d <n> delay n seconds between tries\n"); printf (" -T <n> give subprocess n chances to exit\n"); printf (" -D <n> delay n seconds between wait attempts\n"); printf (" -c <path> set the cache directory\n"); printf (" -a <n> run at most every n seconds in steady state\n"); printf (" -m <n> run at most once every n seconds in steady state\n"); printf (" -j <n> add up to n seconds jitter to steady state checks\n"); printf (" -l don't load disk timestamps\n"); printf (" -s don't save disk timestamps\n"); printf (" -U don't use DBus if supported\n"); printf (" -u <user> user to change to\n"); printf (" -g <grp> group to change to\n"); printf (" -G <grps> comma-separated list of supplementary groups\n"); printf (" -v be verbose\n"); printf (" -b use verbose debugging\n"); printf (" -x <h> set proxy for subprocs to h\n"); printf (" -h this\n"); } void set_conf_defaults (struct opts *opts) { static char *kDefaultArgv[] = { (char *) DEFAULT_TLSDATE, (char *) "-H", (char *) DEFAULT_HOST, NULL }; opts->user = UNPRIV_USER; opts->group = UNPRIV_GROUP; opts->supp_groups = NULL; opts->max_tries = MAX_TRIES; opts->min_steady_state_interval = STEADY_STATE_INTERVAL; opts->wait_between_tries = WAIT_BETWEEN_TRIES; opts->subprocess_tries = SUBPROCESS_TRIES; opts->subprocess_wait_between_tries = SUBPROCESS_WAIT_BETWEEN_TRIES; opts->steady_state_interval = STEADY_STATE_INTERVAL; opts->continuity_interval = CONTINUITY_INTERVAL; opts->base_path = kCacheDir; opts->base_argv = kDefaultArgv; opts->argv = NULL; opts->should_dbus = 1; opts->should_sync_hwclock = DEFAULT_SYNC_HWCLOCK; opts->should_load_disk = DEFAULT_LOAD_FROM_DISK; opts->should_save_disk = DEFAULT_SAVE_TO_DISK; opts->should_netlink = DEFAULT_USE_NETLINK; opts->dry_run = DEFAULT_DRY_RUN; opts->jitter = 0; opts->conf_file = NULL; opts->sources = NULL; opts->cur_source = NULL; opts->proxy = NULL; opts->leap = 0; } void parse_argv (struct opts *opts, int argc, char *argv[]) { int opt; while ((opt = getopt (argc, argv, kTlsdatedOpts)) != -1) { switch (opt) { case 'w': opts->should_sync_hwclock = 0; break; case 'r': opts->should_netlink = 0; break; case 'U': opts->should_dbus = 0; break; case 'p': opts->dry_run = 1; break; case 't': opts->max_tries = atoi (optarg); break; case 'd': opts->wait_between_tries = atoi (optarg); break; case 'T': opts->subprocess_tries = atoi (optarg); break; case 'D': opts->subprocess_wait_between_tries = atoi (optarg); break; case 'c': opts->base_path = optarg; break; case 'a': opts->steady_state_interval = atoi (optarg); break; case 'l': opts->should_load_disk = 0; break; case 's': opts->should_save_disk = 0; break; case 'v': verbose = 1; break; case 'b': verbose_debug = 1; break; case 'm': opts->min_steady_state_interval = atoi (optarg); break; case 'j': opts->jitter = atoi (optarg); break; case 'f': opts->conf_file = optarg; break; case 'x': opts->proxy = optarg; break; case 'u': opts->user = optarg; break; case 'g': opts->group = optarg; break; case 'G': opts->supp_groups = optarg; break; case 'h': default: usage (argv[0]); exit (1); } } if (optind < argc) opts->base_argv = argv + optind; /* Validate arguments */ } static void add_source_to_conf (struct opts *opts, char *host, char *port, char *proxy) { struct source *s; struct source *source = (struct source *) calloc (1, sizeof *source); if (!source) fatal ("out of memory for source"); source->host = strdup (host); if (!source->host) fatal ("out of memory for host"); source->port = strdup (port); if (!source->port) fatal ("out of memory for port"); if (proxy) { source->proxy = strdup (proxy); if (!source->proxy) fatal ("out of memory for proxy"); } if (!opts->sources) { opts->sources = source; source->id = 0; } else { for (s = opts->sources; s->next; s = s->next) ; source->id = s->id + 1; s->next = source; } } static struct conf_entry * parse_source (struct opts *opts, struct conf_entry *conf) { char *host = NULL; char *port = NULL; char *proxy = NULL; /* a source entry: * source * host <host> * port <port> * [proxy <proxy>] * end */ assert (!strcmp (conf->key, "source")); conf = conf->next; while (conf && strcmp (conf->key, "end")) { if (!strcmp (conf->key, "host")) host = conf->value; else if (!strcmp (conf->key, "port")) port = conf->value; else if (!strcmp (conf->key, "proxy")) proxy = conf->value; else fatal ("malformed config: '%s' in source stanza", conf->key); conf = conf->next; } if (!conf) fatal ("unclosed source stanza"); if (!host || !port) fatal ("incomplete source stanza (needs host, port)"); add_source_to_conf (opts, host, port, proxy); return conf; } void load_conf (struct opts *opts) { FILE *f; struct conf_entry *conf, *e; char *conf_file = opts->conf_file; if (!opts->conf_file) conf_file = (char *) DEFAULT_CONF_FILE; f = fopen (conf_file, "r"); if (!f) { if (opts->conf_file) { pfatal ("can't open conf file '%s'", opts->conf_file); } else { pinfo ("can't open conf file '%s'", conf_file); return; } } conf = conf_parse (f); if (!conf) pfatal ("can't parse config file"); for (e = conf; e; e = e->next) { if (!strcmp (e->key, "max-tries") && e->value) { opts->max_tries = atoi (e->value); } else if (!strcmp (e->key, "min-steady-state-interval") && e->value) { opts->min_steady_state_interval = atoi (e->value); } else if (!strcmp (e->key, "wait-between-tries") && e->value) { opts->wait_between_tries = atoi (e->value); } else if (!strcmp (e->key, "subprocess-tries") && e->value) { opts->subprocess_tries = atoi (e->value); } else if (!strcmp (e->key, "subprocess-wait-between-tries") && e->value) { opts->subprocess_wait_between_tries = atoi (e->value); } else if (!strcmp (e->key, "steady-state-interval") && e->value) { opts->steady_state_interval = atoi (e->value); } else if (!strcmp (e->key, "base-path") && e->value) { opts->base_path = strdup (e->value); if (!opts->base_path) fatal ("out of memory for base path"); } else if (!strcmp (e->key, "should-sync-hwclock")) { opts->should_sync_hwclock = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "should-load-disk")) { opts->should_load_disk = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "should-save-disk")) { opts->should_save_disk = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "should-netlink")) { opts->should_netlink = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "dry-run")) { opts->dry_run = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "jitter") && e->value) { opts->jitter = atoi (e->value); } else if (!strcmp (e->key, "verbose")) { verbose = e->value ? !strcmp (e->value, "yes") : 1; } else if (!strcmp (e->key, "source")) { e = parse_source (opts, e); } else if (!strcmp (e->key, "leap")) { opts->leap = e->value ? !strcmp (e->value, "yes") : 1; } } } void check_conf (struct state *state) { struct opts *opts = &state->opts; if (!opts->max_tries) fatal ("-t argument must be nonzero"); if (!opts->wait_between_tries) fatal ("-d argument must be nonzero"); if (!opts->steady_state_interval) fatal ("-a argument must be nonzero"); int ret = snprintf (state->timestamp_path, sizeof (state->timestamp_path), "%s/timestamp", opts->base_path); if (ret < 0 || ((size_t) ret) >= sizeof (state->timestamp_path)) fatal ("supplied base path is too long: '%s'", opts->base_path); if (opts->jitter >= opts->steady_state_interval) fatal ("jitter must be less than steady state interval (%d >= %d)", opts->jitter, opts->steady_state_interval); } int cleanup_main (struct state *state) { int i; for (i = 0; i < E_MAX; ++i) { struct event *e = state->events[i]; if (e) { int fd = event_get_fd (e); if (fd >= 0 && ! (event_get_events (e) & EV_SIGNAL)) close (fd); event_free (e); } } /* The other half was closed above. */ platform->file_close (state->tlsdate_monitor_fd); if (state->tlsdate_pid) { platform->process_signal (state->tlsdate_pid, SIGKILL); platform->process_wait (state->tlsdate_pid, NULL, 0 /* !forever */); } /* Best effort to tear it down if it is still alive. */ close(state->setter_notify_fd); close(state->setter_save_fd); if (state->setter_pid) { platform->process_signal (state->setter_pid, SIGKILL); platform->process_wait (state->setter_pid, NULL, 0 /* !forever */); } /* TODO(wad) Add dbus_cleanup() */ if (state->base) event_base_free (state->base); memset(state, 0, sizeof(*state)); info ("tlsdated clean up finished; exiting!"); terminate_syslog (); return 0; } #ifdef TLSDATED_MAIN static const char ** parse_supp_groups (char *arg) { size_t i; char *scan; const char **supp_groups; for (i = 1, scan = arg; (scan = strchr (scan, ',')); i++, scan++) ; supp_groups = (const char **) calloc (i + 1, sizeof (const char *)); if (!supp_groups) die ("Failed to allocate memory for supplementary group names\n"); for (i = 0; (supp_groups[i] = strsep (&arg, ",")); i++) ; return supp_groups; } int API main (int argc, char *argv[], char *envp[]) { const char **supp_groups = NULL; initalize_syslog (); struct state state; /* TODO(wad) EVENT_BASE_FLAG_PRECISE_TIMER | EVENT_BASE_FLAG_PRECISE_TIMER */ struct event_base *base = event_base_new(); if (!base) { fatal ("could not allocated new event base"); } /* Add three priority levels: * 0 - time saving. Must be done before any other events are handled. * 1 - network synchronization events * 2 - any other events (wake, platform, etc) */ event_base_priority_init (base, MAX_EVENT_PRIORITIES); memset (&state, 0, sizeof (state)); set_conf_defaults (&state.opts); parse_argv (&state.opts, argc, argv); check_conf (&state); load_conf (&state.opts); check_conf (&state); if (!state.opts.sources) add_source_to_conf (&state.opts, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_PROXY); state.base = base; state.envp = envp; state.backoff = state.opts.wait_between_tries; /* TODO(wad) move this into setup_time_setter */ /* grab a handle to /dev/rtc for time-setter. */ if (state.opts.should_sync_hwclock && platform->rtc_open(&state.hwclock)) { pinfo ("can't open hwclock fd"); state.opts.should_sync_hwclock = 0; } /* install the SIGCHLD handler for the setter and tlsdate */ if (setup_sigchld_event (&state, 1)) { error ("Failed to setup SIGCHLD event"); goto out; } /* fork off the privileged helper */ verb ("spawning time setting helper . . ."); if (setup_time_setter (&state)) { error ("could not fork privileged coprocess"); goto out; } /* release the hwclock now that the time-setter is running. */ if (state.opts.should_sync_hwclock) { platform->rtc_close (&state.hwclock); } /* drop privileges before touching any untrusted data */ if (state.opts.supp_groups) supp_groups = parse_supp_groups (state.opts.supp_groups); drop_privs_to (state.opts.user, state.opts.group, supp_groups); free (supp_groups); /* register a signal handler to save time at shutdown */ if (state.opts.should_save_disk) { struct event *event = event_new (base, SIGTERM, EV_SIGNAL|EV_PERSIST, action_sigterm, &state); if (!event) fatal ("Failed to create SIGTERM event"); event_priority_set (event, PRI_SAVE); event_add (event, NULL); } if (state.opts.should_dbus && init_dbus (&state)) { error ("Failed to initialize DBus"); goto out; } /* Register the tlsdate event before any listeners could show up. */ state.events[E_TLSDATE] = event_new (base, -1, EV_TIMEOUT, action_run_tlsdate, &state); if (!state.events[E_TLSDATE]) { error ("Failed to create tlsdate event"); goto out; } event_priority_set (state.events[E_TLSDATE], PRI_NET); /* The timeout and fd will be filled in per-call. */ if (setup_tlsdate_status (&state)) { error ("Failed to create tlsdate status event"); goto out; } /* TODO(wad) Could use a timeout on this to catch setter death? */ /* EV_READ is for truncation/EPIPE notification */ state.events[E_SAVE] = event_new (base, state.setter_save_fd, EV_READ|EV_WRITE, action_sync_and_save, &state); if (!state.events[E_SAVE]) { error ("Failed to create sync & save event"); goto out; } event_priority_set (state.events[E_SAVE], PRI_SAVE); /* Start by grabbing the system time. */ state.last_sync_type = SYNC_TYPE_RTC; state.last_time = time (NULL); /* If possible, grab disk time and check the two. */ if (state.opts.should_load_disk) { time_t disk_time = state.last_time; if (!load_disk_timestamp (state.timestamp_path, &disk_time)) { verb ("disk timestamp available: yes (%ld)", disk_time); if (!is_sane_time (state.last_time) || state.last_time < disk_time) { state.last_sync_type = SYNC_TYPE_DISK; state.last_time = disk_time; } } else { verb ("disk timestamp available: no"); } } if (!is_sane_time (state.last_time)) { state.last_sync_type = SYNC_TYPE_BUILD; state.last_time = RECENT_COMPILE_DATE + 1; } /* Save and announce the initial time source. */ trigger_event (&state, E_SAVE, -1); verb ("tlsdated parasitic time synchronization initialized"); info ("initial time sync type: %s", sync_type_str (state.last_sync_type)); /* Initialize platform specific loop behavior */ if (platform_init_cros (&state)) { error ("Failed to initialize platform code"); goto out; } if (setup_event_route_up (&state)) { error ("Failed to setup route up monitoring"); goto out; } if (setup_event_timer_sync (&state)) { error ("Failed to setup a timer event"); goto out; } if (setup_event_timer_continuity (&state)) { error ("Failed to setup continuity timer"); goto out; } /* Add a forced sync event to the event list. */ action_kickoff_time_sync (-1, EV_TIMEOUT, &state); verb ("Entering dispatch . . ."); event_base_dispatch (base); verb ("tlsdated event dispatch terminating gracefully"); out: return cleanup_main (&state); } #endif /* !TLSDATED_MAIN */