int shutting_down;
int time_derived;
int time_adjusted;
int n_pending_dns = 0;
int n_pending_ntp = 0;
int ai_fam_pref = AF_UNSPEC;
int ntpver = 4;
double steplimit = -1;
SOCKET sock4 = -1; /* Socket for IPv4 */
SOCKET sock6 = -1; /* Socket for IPv6 */
/*
** BCAST *must* listen on port 123 (by default), so we can only
** use the UCST sockets (above) if they too are using port 123
*/
SOCKET bsock4 = -1; /* Broadcast Socket for IPv4 */
SOCKET bsock6 = -1; /* Broadcast Socket for IPv6 */
struct event_base *base;
struct event *ev_sock4;
struct event *ev_sock6;
struct event *ev_worker_timeout;
struct event *ev_xmt_timer;
/*
* The actual main function.
*/
int
sntp_main (
int argc,
char **argv,
const char *sntpVersion
)
{
int i;
int exitcode;
int optct;
struct event_config * evcfg;
/* Initialize logging system - sets up progname */
sntp_init_logging(argv[0]);
if (HAVE_OPT(LOGFILE))
open_logfile(OPT_ARG(LOGFILE));
msyslog(LOG_INFO, "%s", sntpVersion);
if (0 == argc && !HAVE_OPT(BROADCAST) && !HAVE_OPT(CONCURRENT)) {
printf("%s: Must supply at least one of -b hostname, -c hostname, or hostname.\n",
progname);
exit(EX_USAGE);
}
/*
** Eventually, we probably want:
** - separate bcst and ucst timeouts (why?)
** - multiple --timeout values in the commandline
*/
/* IPv6 available? */
if (isc_net_probeipv6() != ISC_R_SUCCESS) {
ai_fam_pref = AF_INET;
TRACE(1, ("No ipv6 support available, forcing ipv4\n"));
} else {
/* Check for options -4 and -6 */
if (HAVE_OPT(IPV4))
ai_fam_pref = AF_INET;
else if (HAVE_OPT(IPV6))
ai_fam_pref = AF_INET6;
}
/* TODO: Parse config file if declared */
/*
** Init the KOD system.
** For embedded systems with no writable filesystem,
** -K /dev/null can be used to disable KoD storage.
*/
kod_init_kod_db(OPT_ARG(KOD), FALSE);
/* HMS: Check and see what happens if KEYFILE doesn't exist */
auth_init(OPT_ARG(KEYFILE), &keys);
/*
** Considering employing a variable that prevents functions of doing
** anything until everything is initialized properly
**
** HMS: What exactly does the above mean?
*/
event_set_log_callback(&sntp_libevent_log_cb);
if (debug > 0)
event_enable_debug_mode();
#ifdef WORK_THREAD
evthread_use_pthreads();
/* we use libevent from main thread only, locks should be academic */
if (debug > 0)
evthread_enable_lock_debuging();
#endif
evcfg = event_config_new();
if (NULL == evcfg) {
printf("%s: event_config_new() failed!\n", progname);
return -1;
}
#ifndef HAVE_SOCKETPAIR
event_config_require_features(evcfg, EV_FEATURE_FDS);
#endif
/* all libevent calls are from main thread */
/* event_config_set_flag(evcfg, EVENT_BASE_FLAG_NOLOCK); */
base = event_base_new_with_config(evcfg);
event_config_free(evcfg);
if (NULL == base) {
printf("%s: event_base_new() failed!\n", progname);
return -1;
}
ZERO(hints);
hints.ai_family = ai_fam_pref;
hints.ai_flags = AI_CANONNAME | Z_AI_NUMERICSERV;
/*
** Unless we specify a socktype, we'll get at least two
** entries for each address: one for TCP and one for
** UDP. That's not what we want.
*/
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
name_sz = 1 + strlen(name);
octets = sizeof(*ctx) + name_sz; // Space for a ctx and the name
ctx = emalloc_zero(octets); // ctx at ctx[0]
name_copy = (char *)(ctx + 1); // Put the name at ctx[1]
memcpy(name_copy, name, name_sz); // copy the name to ctx[1]
ctx->name = name_copy; // point to it...
ctx->flags = flags;
ctx->timeout = response_tv;
ctx->key = NULL;
/* The following should arguably be passed in... */
if (ENABLED_OPT(AUTHENTICATION)) {
ctx->key_id = OPT_VALUE_AUTHENTICATION;
get_key(ctx->key_id, &ctx->key);
if (NULL == ctx->key) {
fprintf(stderr, "%s: Authentication with keyID %d requested, but no matching keyID found in <%s>!\n",
progname, ctx->key_id, OPT_ARG(KEYFILE));
exit(1);
}
} else {
ctx->key_id = -1;
}
LINK_SORT_SLIST(xmt_q, xctx, (xctx->sched < L_S_S_CUR()->sched),
link, xmt_ctx);
if (xmt_q == xctx) {
/*
* The new entry is the first scheduled. The timer is
* either not active or is set for the second xmt
* context in xmt_q.
*/
if (NULL == ev_xmt_timer)
ev_xmt_timer = event_new(base, INVALID_SOCKET,
EV_TIMEOUT,
&xmt_timer_cb, NULL);
if (NULL == ev_xmt_timer) {
msyslog(LOG_ERR,
"queue_xmt: event_new(base, -1, EV_TIMEOUT) failed!");
exit(1);
}
ZERO(delay);
if (xctx->sched > start_cb.tv_sec)
delay.tv_sec = xctx->sched - start_cb.tv_sec;
event_add(ev_xmt_timer, &delay);
TRACE(2, ("queue_xmt: xmt timer for %u usec\n",
(u_int)delay.tv_usec));
}
}
sent = sendpkt(sock, dst, &x_pkt, pkt_len);
if (sent) {
/* Save the packet we sent... */
memcpy(&spkt->x_pkt, &x_pkt, min(sizeof(spkt->x_pkt),
pkt_len));
spkt->stime = tv_xmt.tv_sec - JAN_1970;
default:
INSIST(!"spkt->dctx->flags neither UCST nor BCST");
break;
}
spkt->done = TRUE;
server = &spkt->addr;
msyslog(LOG_INFO, "%s no %cCST response after %d seconds",
hostnameaddr(spkt->dctx->name, server), xcst,
response_timeout);
dec_pending_ntp(spkt->dctx->name, server);
return;
}
/*
** check_kod
*/
int
check_kod(
const struct addrinfo * ai
)
{
char *hostname;
struct kod_entry *reason;
/* Is there a KoD on file for this address? */
hostname = addrinfo_to_str(ai);
TRACE(2, ("check_kod: checking <%s>\n", hostname));
if (search_entry(hostname, &reason)) {
printf("prior KoD for %s, skipping.\n",
hostname);
free(reason);
free(hostname);
return 1;
}
free(hostname);
return 0;
}
/*
** Socket readable/timeout Callback:
** Read in the packet
** Unicast:
** - close socket
** - decrement n_pending_ntp
** - If packet is good, set the time and "exit"
** Broadcast:
** - If packet is good, set the time and "exit"
*/
void
sock_cb(
evutil_socket_t fd,
short what,
void *ptr
)
{
sockaddr_u sender;
sockaddr_u * psau;
sent_pkt ** p_pktlist;
sent_pkt * spkt;
int rpktl;
int rc;
TRACE(2, ("sock_cb: process_pkt returned %d\n", rpktl));
/* If this is a Unicast packet, one down ... */
if (!spkt->done && (CTX_UCST & spkt->dctx->flags)) {
dec_pending_ntp(spkt->dctx->name, &spkt->addr);
spkt->done = TRUE;
}
/* If the packet is good, set the time and we're all done */
rc = handle_pkt(rpktl, &r_pkt, &spkt->addr, spkt->dctx->name);
if (0 != rc)
TRACE(1, ("sock_cb: handle_pkt() returned %d\n", rc));
check_exit_conditions();
}
/*
* check_exit_conditions()
*
* If sntp has a reply, ask the event loop to stop after this round of
* callbacks, unless --wait was used.
*/
void
check_exit_conditions(void)
{
if ((0 == n_pending_ntp && 0 == n_pending_dns) ||
(time_derived && !HAVE_OPT(WAIT))) {
event_base_loopexit(base, NULL);
shutting_down = TRUE;
} else {
TRACE(2, ("%d NTP and %d name queries pending\n",
n_pending_ntp, n_pending_dns));
}
}
/*
* sntp_addremove_fd() is invoked by the intres blocking worker code
* to read from a pipe, or to stop same.
*/
void sntp_addremove_fd(
int fd,
int is_pipe,
int remove_it
)
{
u_int idx;
blocking_child *c;
struct event * ev;
#ifdef HAVE_SOCKETPAIR
if (is_pipe) {
/* sntp only asks for EV_FEATURE_FDS without HAVE_SOCKETPAIR */
msyslog(LOG_ERR, "fatal: pipes not supported on systems with socketpair()");
exit(1);
}
#endif
c = NULL;
for (idx = 0; idx < blocking_children_alloc; idx++) {
c = blocking_children[idx];
if (NULL == c)
continue;
if (fd == c->resp_read_pipe)
break;
}
if (idx == blocking_children_alloc)
return;
if (remove_it) {
ev = c->resp_read_ctx;
c->resp_read_ctx = NULL;
event_del(ev);
event_free(ev);
/*
* intres_timeout_req(s) is invoked in the parent to schedule an idle
* timeout to fire in s seconds, if not reset earlier by a call to
* intres_timeout_req(0), which clears any pending timeout. When the
* timeout expires, worker_idle_timer_fired() is invoked (again, in the
* parent).
*
* sntp and ntpd each provide implementations adapted to their timers.
*/
void
intres_timeout_req(
u_int seconds /* 0 cancels */
)
{
struct timeval tv_to;
int
handle_pkt(
int rpktl,
struct pkt * rpkt,
sockaddr_u * host,
const char * hostname
)
{
char disptxt[32];
const char * addrtxt;
struct timeval tv_dst;
int cnt;
int sw_case;
int digits;
int stratum;
char * ref;
char * ts_str;
const char * leaptxt;
double offset;
double precision;
double synch_distance;
char * p_SNTP_PRETEND_TIME;
time_t pretend_time;
#if SIZEOF_TIME_T == 8
long long ll;
#else
long l;
#endif
ts_str = NULL;
if (rpktl > 0)
sw_case = 1;
else
sw_case = rpktl;
switch (sw_case) {
case SERVER_UNUSEABLE:
return -1;
break;
case PACKET_UNUSEABLE:
break;
case SERVER_AUTH_FAIL:
break;
case KOD_DEMOBILIZE:
/* Received a DENY or RESTR KOD packet */
addrtxt = stoa(host);
ref = (char *)&rpkt->refid;
add_entry(addrtxt, ref);
msyslog(LOG_WARNING, "KOD code %c%c%c%c from %s %s",
ref[0], ref[1], ref[2], ref[3], addrtxt, hostname);
break;
case KOD_RATE:
/*
** Hmm...
** We should probably call add_entry() with an
** expiration timestamp of several seconds in the future,
** and back-off even more if we get more RATE responses.
*/
break;
case 1:
TRACE(3, ("handle_pkt: %d bytes from %s %s\n",
rpktl, stoa(host), hostname));
gettimeofday_cached(base, &tv_dst);
p_SNTP_PRETEND_TIME = getenv("SNTP_PRETEND_TIME");
if (p_SNTP_PRETEND_TIME) {
pretend_time = 0;
#if SIZEOF_TIME_T == 4
if (1 == sscanf(p_SNTP_PRETEND_TIME, "%ld", &l))
pretend_time = (time_t)l;
#elif SIZEOF_TIME_T == 8
if (1 == sscanf(p_SNTP_PRETEND_TIME, "%lld", &ll))
pretend_time = (time_t)ll;
#else
# include "GRONK: unexpected value for SIZEOF_TIME_T"
#endif
if (0 != pretend_time)
tv_dst.tv_sec = pretend_time;
}
/*
** set_time applies 'offset' to the local clock.
*/
int
set_time(
double offset
)
{
int rc;
if (time_adjusted)
return EX_OK;
/*
** If we can step but we cannot slew, then step.
** If we can step or slew and and |offset| > steplimit, then step.
*/
if (ENABLED_OPT(STEP) &&
( !ENABLED_OPT(SLEW)
|| (ENABLED_OPT(SLEW) && (fabs(offset) > steplimit))
)) {
rc = step_systime(offset);
/* If there was a problem, can we rely on errno? */
if (1 == rc)
time_adjusted = TRUE;
return (time_adjusted)
? EX_OK
: 1;
/*
** In case of error, what should we use?
** EX_UNAVAILABLE?
** EX_OSERR?
** EX_NOPERM?
*/
}
if (ENABLED_OPT(SLEW)) {
rc = adj_systime(offset);
/* If there was a problem, can we rely on errno? */
if (1 == rc)
time_adjusted = TRUE;
return (time_adjusted)
? EX_OK
: 1;
/*
** In case of error, what should we use?
** EX_UNAVAILABLE?
** EX_OSERR?
** EX_NOPERM?
*/
}
return EX_SOFTWARE;
}
int
libevent_version_ok(void)
{
ev_uint32_t v_compile_maj;
ev_uint32_t v_run_maj;
v_compile_maj = LIBEVENT_VERSION_NUMBER & 0xffff0000;
v_run_maj = event_get_version_number() & 0xffff0000;
if (v_compile_maj != v_run_maj) {
fprintf(stderr,
"Incompatible libevent versions: have %s, built with %s\n",
event_get_version(),
LIBEVENT_VERSION);
return 0;
}
return 1;
}
/*
* gettimeofday_cached()
*
* Clones the event_base_gettimeofday_cached() interface but ensures the
* times are always on the gettimeofday() 1970 scale. Older libevent 2
* sometimes used gettimeofday(), sometimes the since-system-start
* clock_gettime(CLOCK_MONOTONIC), depending on the platform.
*
* It is not cleanly possible to tell which timescale older libevent is
* using.
*
* The strategy involves 1 hour thresholds chosen to be far longer than
* the duration of a round of libevent callbacks, which share a cached
* start-of-round time. First compare the last cached time with the
* current gettimeofday() time. If they are within one hour, libevent
* is using the proper timescale so leave the offset 0. Otherwise,
* compare libevent's cached time and the current time on the monotonic
* scale. If they are within an hour, libevent is using the monotonic
* scale so calculate the offset to add to such times to bring them to
* gettimeofday()'s scale.
*/
int
gettimeofday_cached(
struct event_base * b,
struct timeval * caller_tv
)
{
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
static struct event_base * cached_b;
static struct timeval cached;
static struct timeval adj_cached;
static struct timeval offset;
static int offset_ready;
struct timeval latest;
struct timeval systemt;
struct timespec ts;
struct timeval mono;
struct timeval diff;
int cgt_rc;
int gtod_rc;
event_base_gettimeofday_cached(b, &latest);
if (b == cached_b &&
!memcmp(&latest, &cached, sizeof(latest))) {
*caller_tv = adj_cached;
return 0;
}
cached = latest;
cached_b = b;
if (!offset_ready) {
cgt_rc = clock_gettime(CLOCK_MONOTONIC, &ts);
gtod_rc = gettimeofday(&systemt, NULL);
if (0 != gtod_rc) {
msyslog(LOG_ERR,
"%s: gettimeofday() error %m",
progname);
exit(1);
}
diff = sub_tval(systemt, latest);
if (debug > 1)
printf("system minus cached %+ld.%06ld\n",
(long)diff.tv_sec, (long)diff.tv_usec);
if (0 != cgt_rc || labs((long)diff.tv_sec) < 3600) {
/*
* Either use_monotonic == 0, or this libevent
* has been repaired. Leave offset at zero.
*/
} else {
mono.tv_sec = ts.tv_sec;
mono.tv_usec = ts.tv_nsec / 1000;
diff = sub_tval(latest, mono);
if (debug > 1)
printf("cached minus monotonic %+ld.%06ld\n",
(long)diff.tv_sec, (long)diff.tv_usec);
if (labs((long)diff.tv_sec) < 3600) {
/* older libevent2 using monotonic */
offset = sub_tval(systemt, mono);
TRACE(1, ("%s: Offsetting libevent CLOCK_MONOTONIC times by %+ld.%06ld\n",
"gettimeofday_cached",
(long)offset.tv_sec,
(long)offset.tv_usec));
}
}
offset_ready = TRUE;
}
adj_cached = add_tval(cached, offset);
*caller_tv = adj_cached;