summaryrefslogtreecommitdiffstats
path: root/contrib/ntp/ntpd/ntp_leapsec.c
diff options
context:
space:
mode:
authordelphij <delphij@FreeBSD.org>2015-07-15 19:21:26 +0000
committerdelphij <delphij@FreeBSD.org>2015-07-15 19:21:26 +0000
commit2a25cee78ab1d37e7d2bc40ae675646974d99f56 (patch)
treeb0302ac4be59e104f4e1e54014561a1389397192 /contrib/ntp/ntpd/ntp_leapsec.c
parenta0741a75537b2e0514472ac3b28afc55a7846c30 (diff)
downloadFreeBSD-src-2a25cee78ab1d37e7d2bc40ae675646974d99f56.zip
FreeBSD-src-2a25cee78ab1d37e7d2bc40ae675646974d99f56.tar.gz
MFC r280849,280915-280916,281015-281016,282097,282408,282415,283542,
284864,285169-285170,285435: ntp 4.2.8p3. Relnotes: yes Approved by: re (?)
Diffstat (limited to 'contrib/ntp/ntpd/ntp_leapsec.c')
-rw-r--r--contrib/ntp/ntpd/ntp_leapsec.c1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/contrib/ntp/ntpd/ntp_leapsec.c b/contrib/ntp/ntpd/ntp_leapsec.c
new file mode 100644
index 0000000..7a652f5
--- /dev/null
+++ b/contrib/ntp/ntpd/ntp_leapsec.c
@@ -0,0 +1,1186 @@
+/*
+ * ntp_leapsec.c - leap second processing for NTPD
+ *
+ * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
+ * The contents of 'html/copyright.html' apply.
+ * ----------------------------------------------------------------------
+ * This is an attempt to get the leap second handling into a dedicated
+ * module to make the somewhat convoluted logic testable.
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#include "ntp_types.h"
+#include "ntp_fp.h"
+#include "ntp_stdlib.h"
+#include "ntp_calendar.h"
+#include "ntp_leapsec.h"
+#include "ntp.h"
+#include "vint64ops.h"
+#include "lib_strbuf.h"
+
+#include "isc/sha1.h"
+
+static const char * const logPrefix = "leapsecond file";
+
+/* ---------------------------------------------------------------------
+ * GCC is rather sticky with its 'const' attribute. We have to do it more
+ * explicit than with a cast if we want to get rid of a CONST qualifier.
+ * Greetings from the PASCAL world, where casting was only possible via
+ * untagged unions...
+ */
+static inline void*
+noconst(
+ const void* ptr
+ )
+{
+ union {
+ const void * cp;
+ void * vp;
+ } tmp;
+ tmp.cp = ptr;
+ return tmp.vp;
+}
+
+/* ---------------------------------------------------------------------
+ * Our internal data structure
+ */
+#define MAX_HIST 10 /* history of leap seconds */
+
+struct leap_info {
+ vint64 ttime; /* transition time (after the step, ntp scale) */
+ uint32_t stime; /* schedule limit (a month before transition) */
+ int16_t taiof; /* TAI offset on and after the transition */
+ uint8_t dynls; /* dynamic: inserted on peer/clock request */
+};
+typedef struct leap_info leap_info_t;
+
+struct leap_head {
+ vint64 update; /* time of information update */
+ vint64 expire; /* table expiration time */
+ uint16_t size; /* number of infos in table */
+ int16_t base_tai; /* total leaps before first entry */
+ int16_t this_tai; /* current TAI offset */
+ int16_t next_tai; /* TAI offset after 'when' */
+ vint64 dtime; /* due time (current era end) */
+ vint64 ttime; /* nominal transition time (next era start) */
+ vint64 stime; /* schedule time (when we take notice) */
+ vint64 ebase; /* base time of this leap era */
+ uint8_t dynls; /* next leap is dynamic (by peer request) */
+};
+typedef struct leap_head leap_head_t;
+
+struct leap_table {
+ leap_signature_t lsig;
+ leap_head_t head;
+ leap_info_t info[MAX_HIST];
+};
+
+/* Where we store our tables */
+static leap_table_t _ltab[2], *_lptr;
+static int/*BOOL*/ _electric;
+
+/* Forward decls of local helpers */
+static int add_range(leap_table_t*, const leap_info_t*);
+static char * get_line(leapsec_reader, void*, char*, size_t);
+static char * skipws(const char*);
+static int parsefail(const char * cp, const char * ep);
+static void reload_limits(leap_table_t*, const vint64*);
+static void fetch_leap_era(leap_era_t*, const leap_table_t*,
+ const vint64*);
+static int betweenu32(uint32_t, uint32_t, uint32_t);
+static void reset_times(leap_table_t*);
+static int leapsec_add(leap_table_t*, const vint64*, int);
+static int leapsec_raw(leap_table_t*, const vint64 *, int, int);
+static const char * lstostr(const vint64 * ts);
+
+/* =====================================================================
+ * Get & Set the current leap table
+ */
+
+/* ------------------------------------------------------------------ */
+leap_table_t *
+leapsec_get_table(
+ int alternate)
+{
+ leap_table_t *p1, *p2;
+
+ p1 = _lptr;
+ if (p1 == &_ltab[0]) {
+ p2 = &_ltab[1];
+ } else if (p1 == &_ltab[1]) {
+ p2 = &_ltab[0];
+ } else {
+ p1 = &_ltab[0];
+ p2 = &_ltab[1];
+ reset_times(p1);
+ reset_times(p2);
+ _lptr = p1;
+ }
+ if (alternate) {
+ memcpy(p2, p1, sizeof(leap_table_t));
+ p1 = p2;
+ }
+
+ return p1;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_set_table(
+ leap_table_t * pt)
+{
+ if (pt == &_ltab[0] || pt == &_ltab[1])
+ _lptr = pt;
+ return _lptr == pt;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_electric(
+ int/*BOOL*/ on)
+{
+ int res = _electric;
+ if (on < 0)
+ return res;
+
+ _electric = (on != 0);
+ if (_electric == res)
+ return res;
+
+ if (_lptr == &_ltab[0] || _lptr == &_ltab[1])
+ reset_times(_lptr);
+
+ return res;
+}
+
+/* =====================================================================
+ * API functions that operate on tables
+ */
+
+/* ---------------------------------------------------------------------
+ * Clear all leap second data. Use it for init & cleanup
+ */
+void
+leapsec_clear(
+ leap_table_t * pt)
+{
+ memset(&pt->lsig, 0, sizeof(pt->lsig));
+ memset(&pt->head, 0, sizeof(pt->head));
+ reset_times(pt);
+}
+
+/* ---------------------------------------------------------------------
+ * Load a leap second file and check expiration on the go
+ */
+int/*BOOL*/
+leapsec_load(
+ leap_table_t * pt ,
+ leapsec_reader func,
+ void * farg,
+ int use_build_limit)
+{
+ char *cp, *ep, linebuf[50];
+ vint64 ttime, limit;
+ long taiof;
+ struct calendar build;
+
+ leapsec_clear(pt);
+ if (use_build_limit && ntpcal_get_build_date(&build)) {
+ /* don't prune everything -- permit the last 10yrs
+ * before build.
+ */
+ build.year -= 10;
+ limit = ntpcal_date_to_ntp64(&build);
+ } else {
+ memset(&limit, 0, sizeof(limit));
+ }
+
+ while (get_line(func, farg, linebuf, sizeof(linebuf))) {
+ cp = linebuf;
+ if (*cp == '#') {
+ cp++;
+ if (*cp == '@') {
+ cp = skipws(cp+1);
+ pt->head.expire = strtouv64(cp, &ep, 10);
+ if (parsefail(cp, ep))
+ goto fail_read;
+ pt->lsig.etime = pt->head.expire.D_s.lo;
+ } else if (*cp == '$') {
+ cp = skipws(cp+1);
+ pt->head.update = strtouv64(cp, &ep, 10);
+ if (parsefail(cp, ep))
+ goto fail_read;
+ }
+ } else if (isdigit((u_char)*cp)) {
+ ttime = strtouv64(cp, &ep, 10);
+ if (parsefail(cp, ep))
+ goto fail_read;
+ cp = skipws(ep);
+ taiof = strtol(cp, &ep, 10);
+ if ( parsefail(cp, ep)
+ || taiof > SHRT_MAX || taiof < SHRT_MIN)
+ goto fail_read;
+ if (ucmpv64(&ttime, &limit) >= 0) {
+ if (!leapsec_raw(pt, &ttime,
+ taiof, FALSE))
+ goto fail_insn;
+ } else {
+ pt->head.base_tai = (int16_t)taiof;
+ }
+ pt->lsig.ttime = ttime.D_s.lo;
+ pt->lsig.taiof = (int16_t)taiof;
+ }
+ }
+ return TRUE;
+
+fail_read:
+ errno = EILSEQ;
+fail_insn:
+ leapsec_clear(pt);
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------
+ * Dump a table in human-readable format. Use 'fprintf' and a FILE
+ * pointer if you want to get it printed into a stream.
+ */
+void
+leapsec_dump(
+ const leap_table_t * pt ,
+ leapsec_dumper func,
+ void * farg)
+{
+ int idx;
+ vint64 ts;
+ struct calendar atb, ttb;
+
+ ntpcal_ntp64_to_date(&ttb, &pt->head.expire);
+ (*func)(farg, "leap table (%u entries) expires at %04u-%02u-%02u:\n",
+ pt->head.size,
+ ttb.year, ttb.month, ttb.monthday);
+ idx = pt->head.size;
+ while (idx-- != 0) {
+ ts = pt->info[idx].ttime;
+ ntpcal_ntp64_to_date(&ttb, &ts);
+ ts = subv64u32(&ts, pt->info[idx].stime);
+ ntpcal_ntp64_to_date(&atb, &ts);
+
+ (*func)(farg, "%04u-%02u-%02u [%c] (%04u-%02u-%02u) - %d\n",
+ ttb.year, ttb.month, ttb.monthday,
+ "-*"[pt->info[idx].dynls != 0],
+ atb.year, atb.month, atb.monthday,
+ pt->info[idx].taiof);
+ }
+}
+
+/* =====================================================================
+ * usecase driven API functions
+ */
+
+int/*BOOL*/
+leapsec_query(
+ leap_result_t * qr ,
+ uint32_t ts32 ,
+ const time_t * pivot)
+{
+ leap_table_t * pt;
+ vint64 ts64, last, next;
+ uint32_t due32;
+ int fired;
+
+ /* preset things we use later on... */
+ fired = FALSE;
+ ts64 = ntpcal_ntp_to_ntp(ts32, pivot);
+ pt = leapsec_get_table(FALSE);
+ memset(qr, 0, sizeof(leap_result_t));
+
+ if (ucmpv64(&ts64, &pt->head.ebase) < 0) {
+ /* Most likely after leap frame reset. Could also be a
+ * backstep of the system clock. Anyway, get the new
+ * leap era frame.
+ */
+ reload_limits(pt, &ts64);
+ } else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) {
+ /* Boundary crossed in forward direction. This might
+ * indicate a leap transition, so we prepare for that
+ * case.
+ *
+ * Some operations below are actually NOPs in electric
+ * mode, but having only one code path that works for
+ * both modes is easier to maintain.
+ *
+ * There's another quirk we must keep looking out for:
+ * If we just stepped the clock, the step might have
+ * crossed a leap boundary. As with backward steps, we
+ * do not want to raise the 'fired' event in that case.
+ * So we raise the 'fired' event only if we're close to
+ * the transition and just reload the limits otherwise.
+ */
+ last = addv64i32(&pt->head.dtime, 3); /* get boundary */
+ if (ucmpv64(&ts64, &last) >= 0) {
+ /* that was likely a query after a step */
+ reload_limits(pt, &ts64);
+ } else {
+ /* close enough for deeper examination */
+ last = pt->head.ttime;
+ qr->warped = (int16_t)(last.D_s.lo -
+ pt->head.dtime.D_s.lo);
+ next = addv64i32(&ts64, qr->warped);
+ reload_limits(pt, &next);
+ fired = ucmpv64(&pt->head.ebase, &last) == 0;
+ if (fired) {
+ ts64 = next;
+ ts32 = next.D_s.lo;
+ } else {
+ qr->warped = 0;
+ }
+ }
+ }
+
+ qr->tai_offs = pt->head.this_tai;
+ qr->ebase = pt->head.ebase;
+ qr->ttime = pt->head.ttime;
+
+ /* If before the next scheduling alert, we're done. */
+ if (ucmpv64(&ts64, &pt->head.stime) < 0)
+ return fired;
+
+ /* now start to collect the remaining data */
+ due32 = pt->head.dtime.D_s.lo;
+
+ qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
+ qr->ddist = due32 - ts32;
+ qr->dynamic = pt->head.dynls;
+ qr->proximity = LSPROX_SCHEDULE;
+
+ /* if not in the last day before transition, we're done. */
+ if (!betweenu32(due32 - SECSPERDAY, ts32, due32))
+ return fired;
+
+ qr->proximity = LSPROX_ANNOUNCE;
+ if (!betweenu32(due32 - 10, ts32, due32))
+ return fired;
+
+ /* The last 10s before the transition. Prepare for action! */
+ qr->proximity = LSPROX_ALERT;
+ return fired;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_query_era(
+ leap_era_t * qr ,
+ uint32_t ntpts,
+ const time_t * pivot)
+{
+ const leap_table_t * pt;
+ vint64 ts64;
+
+ pt = leapsec_get_table(FALSE);
+ ts64 = ntpcal_ntp_to_ntp(ntpts, pivot);
+ fetch_leap_era(qr, pt, &ts64);
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_frame(
+ leap_result_t *qr)
+{
+ const leap_table_t * pt;
+
+ memset(qr, 0, sizeof(leap_result_t));
+ pt = leapsec_get_table(FALSE);
+
+ qr->tai_offs = pt->head.this_tai;
+ qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
+ qr->ebase = pt->head.ebase;
+ qr->ttime = pt->head.ttime;
+ qr->dynamic = pt->head.dynls;
+
+ return ucmpv64(&pt->head.ttime, &pt->head.stime) >= 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* Reset the current leap frame */
+void
+leapsec_reset_frame(void)
+{
+ reset_times(leapsec_get_table(FALSE));
+}
+
+/* ------------------------------------------------------------------ */
+/* load a file from a FILE pointer. Note: If hcheck is true, load
+ * only after successful signature check. The stream must be seekable
+ * or this will fail.
+ */
+int/*BOOL*/
+leapsec_load_stream(
+ FILE * ifp ,
+ const char * fname,
+ int/*BOOL*/ logall)
+{
+ leap_table_t *pt;
+ int rcheck;
+
+ if (NULL == fname)
+ fname = "<unknown>";
+
+ rcheck = leapsec_validate((leapsec_reader)getc, ifp);
+ if (logall)
+ switch (rcheck)
+ {
+ case LSVALID_GOODHASH:
+ msyslog(LOG_NOTICE, "%s ('%s'): good hash signature",
+ logPrefix, fname);
+ break;
+
+ case LSVALID_NOHASH:
+ msyslog(LOG_ERR, "%s ('%s'): no hash signature",
+ logPrefix, fname);
+ break;
+ case LSVALID_BADHASH:
+ msyslog(LOG_ERR, "%s ('%s'): signature mismatch",
+ logPrefix, fname);
+ break;
+ case LSVALID_BADFORMAT:
+ msyslog(LOG_ERR, "%s ('%s'): malformed hash signature",
+ logPrefix, fname);
+ break;
+ default:
+ msyslog(LOG_ERR, "%s ('%s'): unknown error code %d",
+ logPrefix, fname, rcheck);
+ break;
+ }
+ if (rcheck < 0)
+ return FALSE;
+
+ rewind(ifp);
+ pt = leapsec_get_table(TRUE);
+ if (!leapsec_load(pt, (leapsec_reader)getc, ifp, TRUE)) {
+ switch (errno) {
+ case EINVAL:
+ msyslog(LOG_ERR, "%s ('%s'): bad transition time",
+ logPrefix, fname);
+ break;
+ case ERANGE:
+ msyslog(LOG_ERR, "%s ('%s'): times not ascending",
+ logPrefix, fname);
+ break;
+ default:
+ msyslog(LOG_ERR, "%s ('%s'): parsing error",
+ logPrefix, fname);
+ break;
+ }
+ return FALSE;
+ }
+
+ if (pt->head.size)
+ msyslog(LOG_NOTICE, "%s ('%s'): loaded, expire=%s last=%s ofs=%d",
+ logPrefix, fname, lstostr(&pt->head.expire),
+ lstostr(&pt->info[0].ttime), pt->info[0].taiof);
+ else
+ msyslog(LOG_NOTICE,
+ "%s ('%s'): loaded, expire=%s ofs=%d (no entries after build date)",
+ logPrefix, fname, lstostr(&pt->head.expire),
+ pt->head.base_tai);
+
+ return leapsec_set_table(pt);
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_load_file(
+ const char * fname,
+ struct stat * sb_old,
+ int/*BOOL*/ force,
+ int/*BOOL*/ logall)
+{
+ FILE * fp;
+ struct stat sb_new;
+ int rc;
+
+ /* just do nothing if there is no leap file */
+ if ( !(fname && *fname) )
+ return FALSE;
+
+ /* try to stat the leapfile */
+ if (0 != stat(fname, &sb_new)) {
+ if (logall)
+ msyslog(LOG_ERR, "%s ('%s'): stat failed: %m",
+ logPrefix, fname);
+ return FALSE;
+ }
+
+ /* silently skip to postcheck if no new file found */
+ if (NULL != sb_old) {
+ if (!force
+ && sb_old->st_mtime == sb_new.st_mtime
+ && sb_old->st_ctime == sb_new.st_ctime
+ )
+ return FALSE;
+ *sb_old = sb_new;
+ }
+
+ /* try to open the leap file, complain if that fails
+ *
+ * [perlinger@ntp.org]
+ * coverity raises a TOCTOU (time-of-check/time-of-use) issue
+ * here, which is not entirely helpful: While there is indeed a
+ * possible race condition between the 'stat()' call above and
+ * the 'fopen)' call below, I intentionally want to omit the
+ * overhead of opening the file and calling 'fstat()', because
+ * in most cases the file would have be to closed anyway without
+ * reading the contents. I chose to disable the coverity
+ * warning instead.
+ *
+ * So unless someone comes up with a reasonable argument why
+ * this could be a real issue, I'll just try to silence coverity
+ * on that topic.
+ */
+ /* coverity[toctou] */
+ if ((fp = fopen(fname, "r")) == NULL) {
+ if (logall)
+ msyslog(LOG_ERR,
+ "%s ('%s'): open failed: %m",
+ logPrefix, fname);
+ return FALSE;
+ }
+
+ rc = leapsec_load_stream(fp, fname, logall);
+ fclose(fp);
+ return rc;
+}
+
+/* ------------------------------------------------------------------ */
+void
+leapsec_getsig(
+ leap_signature_t * psig)
+{
+ const leap_table_t * pt;
+
+ pt = leapsec_get_table(FALSE);
+ memcpy(psig, &pt->lsig, sizeof(leap_signature_t));
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_expired(
+ uint32_t when,
+ const time_t * tpiv)
+{
+ const leap_table_t * pt;
+ vint64 limit;
+
+ pt = leapsec_get_table(FALSE);
+ limit = ntpcal_ntp_to_ntp(when, tpiv);
+ return ucmpv64(&limit, &pt->head.expire) >= 0;
+}
+
+/* ------------------------------------------------------------------ */
+int32_t
+leapsec_daystolive(
+ uint32_t when,
+ const time_t * tpiv)
+{
+ const leap_table_t * pt;
+ vint64 limit;
+
+ pt = leapsec_get_table(FALSE);
+ limit = ntpcal_ntp_to_ntp(when, tpiv);
+ limit = subv64(&pt->head.expire, &limit);
+ return ntpcal_daysplit(&limit).hi;
+}
+
+/* ------------------------------------------------------------------ */
+#if 0 /* currently unused -- possibly revived later */
+int/*BOOL*/
+leapsec_add_fix(
+ int total,
+ uint32_t ttime,
+ uint32_t etime,
+ const time_t * pivot)
+{
+ time_t tpiv;
+ leap_table_t * pt;
+ vint64 tt64, et64;
+
+ if (pivot == NULL) {
+ time(&tpiv);
+ pivot = &tpiv;
+ }
+
+ et64 = ntpcal_ntp_to_ntp(etime, pivot);
+ tt64 = ntpcal_ntp_to_ntp(ttime, pivot);
+ pt = leapsec_get_table(TRUE);
+
+ if ( ucmpv64(&et64, &pt->head.expire) <= 0
+ || !leapsec_raw(pt, &tt64, total, FALSE) )
+ return FALSE;
+
+ pt->lsig.etime = etime;
+ pt->lsig.ttime = ttime;
+ pt->lsig.taiof = (int16_t)total;
+
+ pt->head.expire = et64;
+
+ return leapsec_set_table(pt);
+}
+#endif
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_add_dyn(
+ int insert,
+ uint32_t ntpnow,
+ const time_t * pivot )
+{
+ leap_table_t * pt;
+ vint64 now64;
+
+ pt = leapsec_get_table(TRUE);
+ now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
+ return ( leapsec_add(pt, &now64, (insert != 0))
+ && leapsec_set_table(pt));
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_autokey_tai(
+ int tai_offset,
+ uint32_t ntpnow ,
+ const time_t * pivot )
+{
+ leap_table_t * pt;
+ leap_era_t era;
+ vint64 now64;
+ int idx;
+
+ (void)tai_offset;
+ pt = leapsec_get_table(FALSE);
+
+ /* Bail out if the basic offset is not zero and the putative
+ * offset is bigger than 10s. That was in 1972 -- we don't want
+ * to go back that far!
+ */
+ if (pt->head.base_tai != 0 || tai_offset < 10)
+ return FALSE;
+
+ /* If there's already data in the table, check if an update is
+ * possible. Update is impossible if there are static entries
+ * (since this indicates a valid leapsecond file) or if we're
+ * too close to a leapsecond transition: We do not know on what
+ * side the transition the sender might have been, so we use a
+ * dead zone around the transition.
+ */
+
+ /* Check for static entries */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if ( ! pt->info[idx].dynls)
+ return FALSE;
+
+ /* get the fulll time stamp and leap era for it */
+ now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
+ fetch_leap_era(&era, pt, &now64);
+
+ /* check the limits with 20s dead band */
+ era.ebase = addv64i32(&era.ebase, 20);
+ if (ucmpv64(&now64, &era.ebase) < 0)
+ return FALSE;
+
+ era.ttime = addv64i32(&era.ttime, -20);
+ if (ucmpv64(&now64, &era.ttime) > 0)
+ return FALSE;
+
+ /* Here we can proceed. Calculate the delta update. */
+ tai_offset -= era.taiof;
+
+ /* Shift the header info offsets. */
+ pt->head.base_tai += tai_offset;
+ pt->head.this_tai += tai_offset;
+ pt->head.next_tai += tai_offset;
+
+ /* Shift table entry offsets (if any) */
+ for (idx = 0; idx != pt->head.size; idx++)
+ pt->info[idx].taiof += tai_offset;
+
+ /* claim success... */
+ return TRUE;
+}
+
+
+/* =====================================================================
+ * internal helpers
+ */
+
+/* [internal] Reset / init the time window in the leap processor to
+ * force reload on next query. Since a leap transition cannot take place
+ * at an odd second, the value chosen avoids spurious leap transition
+ * triggers. Making all three times equal forces a reload. Using the
+ * maximum value for unsigned 64 bits makes finding the next leap frame
+ * a bit easier.
+ */
+static void
+reset_times(
+ leap_table_t * pt)
+{
+ memset(&pt->head.ebase, 0xFF, sizeof(vint64));
+ pt->head.stime = pt->head.ebase;
+ pt->head.ttime = pt->head.ebase;
+ pt->head.dtime = pt->head.ebase;
+}
+
+/* [internal] Add raw data to the table, removing old entries on the
+ * fly. This cannot fail currently.
+ */
+static int/*BOOL*/
+add_range(
+ leap_table_t * pt,
+ const leap_info_t * pi)
+{
+ /* If the table is full, make room by throwing out the oldest
+ * entry. But remember the accumulated leap seconds! Likewise,
+ * assume a positive leap insertion if this is the first entry
+ * in the table. This is not necessarily the best of all ideas,
+ * but it helps a great deal if a system does not have a leap
+ * table and gets updated from an upstream server.
+ */
+ if (pt->head.size == 0) {
+ pt->head.base_tai = pi->taiof - 1;
+ } else if (pt->head.size >= MAX_HIST) {
+ pt->head.size = MAX_HIST - 1;
+ pt->head.base_tai = pt->info[pt->head.size].taiof;
+ }
+
+ /* make room in lower end and insert item */
+ memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info));
+ pt->info[0] = *pi;
+ pt->head.size++;
+
+ /* invalidate the cached limit data -- we might have news ;-)
+ *
+ * This blocks a spurious transition detection. OTOH, if you add
+ * a value after the last query before a leap transition was
+ * expected to occur, this transition trigger is lost. But we
+ * can probably live with that.
+ */
+ reset_times(pt);
+ return TRUE;
+}
+
+/* [internal] given a reader function, read characters into a buffer
+ * until either EOL or EOF is reached. Makes sure that the buffer is
+ * always NUL terminated, but silently truncates excessive data. The
+ * EOL-marker ('\n') is *not* stored in the buffer.
+ *
+ * Returns the pointer to the buffer, unless EOF was reached when trying
+ * to read the first character of a line.
+ */
+static char *
+get_line(
+ leapsec_reader func,
+ void * farg,
+ char * buff,
+ size_t size)
+{
+ int ch;
+ char *ptr;
+
+ /* if we cannot even store the delimiter, declare failure */
+ if (buff == NULL || size == 0)
+ return NULL;
+
+ ptr = buff;
+ while (EOF != (ch = (*func)(farg)) && '\n' != ch)
+ if (size > 1) {
+ size--;
+ *ptr++ = (char)ch;
+ }
+ /* discard trailing whitespace */
+ while (ptr != buff && isspace((u_char)ptr[-1]))
+ ptr--;
+ *ptr = '\0';
+ return (ptr == buff && ch == EOF) ? NULL : buff;
+}
+
+/* [internal] skips whitespace characters from a character buffer. */
+static char *
+skipws(
+ const char *ptr)
+{
+ while (isspace((u_char)*ptr))
+ ptr++;
+ return (char*)noconst(ptr);
+}
+
+/* [internal] check if a strtoXYZ ended at EOL or whitespace and
+ * converted something at all. Return TRUE if something went wrong.
+ */
+static int/*BOOL*/
+parsefail(
+ const char * cp,
+ const char * ep)
+{
+ return (cp == ep)
+ || (*ep && *ep != '#' && !isspace((u_char)*ep));
+}
+
+/* [internal] reload the table limits around the given time stamp. This
+ * is where the real work is done when it comes to table lookup and
+ * evaluation. Some care has been taken to have correct code for dealing
+ * with boundary conditions and empty tables.
+ *
+ * In electric mode, transition and trip time are the same. In dumb
+ * mode, the difference of the TAI offsets must be taken into account
+ * and trip time and transition time become different. The difference
+ * becomes the warping distance when the trip time is reached.
+ */
+static void
+reload_limits(
+ leap_table_t * pt,
+ const vint64 * ts)
+{
+ int idx;
+
+ /* Get full time and search the true lower bound. Use a
+ * simple loop here, since the number of entries does
+ * not warrant a binary search. This also works for an empty
+ * table, so there is no shortcut for that case.
+ */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
+ break;
+
+ /* get time limits with proper bound conditions. Note that the
+ * bounds of the table will be observed even if the table is
+ * empty -- no undefined condition must arise from this code.
+ */
+ if (idx >= pt->head.size) {
+ memset(&pt->head.ebase, 0x00, sizeof(vint64));
+ pt->head.this_tai = pt->head.base_tai;
+ } else {
+ pt->head.ebase = pt->info[idx].ttime;
+ pt->head.this_tai = pt->info[idx].taiof;
+ }
+ if (--idx >= 0) {
+ pt->head.next_tai = pt->info[idx].taiof;
+ pt->head.dynls = pt->info[idx].dynls;
+ pt->head.ttime = pt->info[idx].ttime;
+
+ if (_electric)
+ pt->head.dtime = pt->head.ttime;
+ else
+ pt->head.dtime = addv64i32(
+ &pt->head.ttime,
+ pt->head.next_tai - pt->head.this_tai);
+
+ pt->head.stime = subv64u32(
+ &pt->head.ttime, pt->info[idx].stime);
+
+ } else {
+ memset(&pt->head.ttime, 0xFF, sizeof(vint64));
+ pt->head.stime = pt->head.ttime;
+ pt->head.dtime = pt->head.ttime;
+ pt->head.next_tai = pt->head.this_tai;
+ pt->head.dynls = 0;
+ }
+}
+
+/* [internal] fetch the leap era for a given time stamp.
+ * This is a cut-down version the algorithm used to reload the table
+ * limits, but it does not update any global state and provides just the
+ * era information for a given time stamp.
+ */
+static void
+fetch_leap_era(
+ leap_era_t * into,
+ const leap_table_t * pt ,
+ const vint64 * ts )
+{
+ int idx;
+
+ /* Simple search loop, also works with empty table. */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
+ break;
+ /* fetch era data, keeping an eye on boundary conditions */
+ if (idx >= pt->head.size) {
+ memset(&into->ebase, 0x00, sizeof(vint64));
+ into->taiof = pt->head.base_tai;
+ } else {
+ into->ebase = pt->info[idx].ttime;
+ into->taiof = pt->info[idx].taiof;
+ }
+ if (--idx >= 0)
+ into->ttime = pt->info[idx].ttime;
+ else
+ memset(&into->ttime, 0xFF, sizeof(vint64));
+}
+
+/* [internal] Take a time stamp and create a leap second frame for
+ * it. This will schedule a leap second for the beginning of the next
+ * month, midnight UTC. The 'insert' argument tells if a leap second is
+ * added (!=0) or removed (==0). We do not handle multiple inserts
+ * (yet?)
+ *
+ * Returns 1 if the insert worked, 0 otherwise. (It's not possible to
+ * insert a leap second into the current history -- only appending
+ * towards the future is allowed!)
+ */
+static int/*BOOL*/
+leapsec_add(
+ leap_table_t* pt ,
+ const vint64 * now64 ,
+ int insert)
+{
+ vint64 ttime, starttime;
+ struct calendar fts;
+ leap_info_t li;
+
+ /* Check against the table expiration and the latest available
+ * leap entry. Do not permit inserts, only appends, and only if
+ * the extend the table beyond the expiration!
+ */
+ if ( ucmpv64(now64, &pt->head.expire) < 0
+ || (pt->head.size && ucmpv64(now64, &pt->info[0].ttime) <= 0)) {
+ errno = ERANGE;
+ return FALSE;
+ }
+
+ ntpcal_ntp64_to_date(&fts, now64);
+ /* To guard against dangling leap flags: do not accept leap
+ * second request on the 1st hour of the 1st day of the month.
+ */
+ if (fts.monthday == 1 && fts.hour == 0) {
+ errno = EINVAL;
+ return FALSE;
+ }
+
+ /* Ok, do the remaining calculations */
+ fts.monthday = 1;
+ fts.hour = 0;
+ fts.minute = 0;
+ fts.second = 0;
+ starttime = ntpcal_date_to_ntp64(&fts);
+ fts.month++;
+ ttime = ntpcal_date_to_ntp64(&fts);
+
+ li.ttime = ttime;
+ li.stime = ttime.D_s.lo - starttime.D_s.lo;
+ li.taiof = (pt->head.size ? pt->info[0].taiof : pt->head.base_tai)
+ + (insert ? 1 : -1);
+ li.dynls = 1;
+ return add_range(pt, &li);
+}
+
+/* [internal] Given a time stamp for a leap insertion (the exact begin
+ * of the new leap era), create new leap frame and put it into the
+ * table. This is the work horse for reading a leap file and getting a
+ * leap second update via authenticated network packet.
+ */
+int/*BOOL*/
+leapsec_raw(
+ leap_table_t * pt,
+ const vint64 * ttime,
+ int taiof,
+ int dynls)
+{
+ vint64 starttime;
+ struct calendar fts;
+ leap_info_t li;
+
+ /* Check that we either extend the table or get a duplicate of
+ * the latest entry. The latter is a benevolent overwrite with
+ * identical data and could happen if we get an autokey message
+ * that extends the lifetime of the current leapsecond table.
+ * Otherwise paranoia rulez!
+ */
+ if (pt->head.size) {
+ int cmp = ucmpv64(ttime, &pt->info[0].ttime);
+ if (cmp == 0)
+ cmp -= (taiof != pt->info[0].taiof);
+ if (cmp < 0) {
+ errno = ERANGE;
+ return FALSE;
+ }
+ if (cmp == 0)
+ return TRUE;
+ }
+
+ ntpcal_ntp64_to_date(&fts, ttime);
+ /* If this does not match the exact month start, bail out. */
+ if (fts.monthday != 1 || fts.hour || fts.minute || fts.second) {
+ errno = EINVAL;
+ return FALSE;
+ }
+ fts.month--; /* was in range 1..12, no overflow here! */
+ starttime = ntpcal_date_to_ntp64(&fts);
+ li.ttime = *ttime;
+ li.stime = ttime->D_s.lo - starttime.D_s.lo;
+ li.taiof = (int16_t)taiof;
+ li.dynls = (dynls != 0);
+ return add_range(pt, &li);
+}
+
+/* [internal] Do a wrap-around save range inclusion check.
+ * Returns TRUE if x in [lo,hi[ (intervall open on right side) with full
+ * handling of an overflow / wrap-around.
+ */
+static int/*BOOL*/
+betweenu32(
+ uint32_t lo,
+ uint32_t x,
+ uint32_t hi)
+{
+ int rc;
+
+ if (lo <= hi)
+ rc = (lo <= x) && (x < hi);
+ else
+ rc = (lo <= x) || (x < hi);
+ return rc;
+}
+
+/* =====================================================================
+ * validation stuff
+ */
+
+typedef struct {
+ unsigned char hv[ISC_SHA1_DIGESTLENGTH];
+} sha1_digest;
+
+/* [internal] parse a digest line to get the hash signature
+ * The NIST code creating the hash writes them out as 5 hex integers
+ * without leading zeros. This makes reading them back as hex-encoded
+ * BLOB impossible, because there might be less than 40 hex digits.
+ *
+ * The solution is to read the values back as integers, and then do the
+ * byte twiddle necessary to get it into an array of 20 chars. The
+ * drawback is that it permits any acceptable number syntax provided by
+ * 'scanf()' and 'strtoul()', including optional signs and '0x'
+ * prefixes.
+ */
+static int/*BOOL*/
+do_leap_hash(
+ sha1_digest * mac,
+ char const * cp )
+{
+ int wi, di, num, len;
+ unsigned long tmp[5];
+
+ memset(mac, 0, sizeof(*mac));
+ num = sscanf(cp, " %lx %lx %lx %lx %lx%n",
+ &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
+ &len);
+ if (num != 5 || cp[len] > ' ')
+ return FALSE;
+
+ /* now do the byte twiddle */
+ for (wi=0; wi < 5; ++wi)
+ for (di=3; di >= 0; --di) {
+ mac->hv[wi*4 + di] =
+ (unsigned char)(tmp[wi] & 0x0FF);
+ tmp[wi] >>= 8;
+ }
+ return TRUE;
+}
+
+/* [internal] add the digits of a data line to the hash, stopping at the
+ * next hash ('#') character.
+ */
+static void
+do_hash_data(
+ isc_sha1_t * mdctx,
+ char const * cp )
+{
+ unsigned char text[32]; // must be power of two!
+ unsigned int tlen = 0;
+ unsigned char ch;
+
+ while ('\0' != (ch = *cp++) && '#' != ch)
+ if (isdigit(ch)) {
+ text[tlen++] = ch;
+ tlen &= (sizeof(text)-1);
+ if (0 == tlen)
+ isc_sha1_update(
+ mdctx, text, sizeof(text));
+ }
+
+ if (0 < tlen)
+ isc_sha1_update(mdctx, text, tlen);
+}
+
+/* given a reader and a reader arg, calculate and validate the the hash
+ * signature of a NIST leap second file.
+ */
+int
+leapsec_validate(
+ leapsec_reader func,
+ void * farg)
+{
+ isc_sha1_t mdctx;
+ sha1_digest rdig, ldig; /* remote / local digests */
+ char line[50];
+ int hlseen = -1;
+
+ isc_sha1_init(&mdctx);
+ while (get_line(func, farg, line, sizeof(line))) {
+ if (!strncmp(line, "#h", 2))
+ hlseen = do_leap_hash(&rdig, line+2);
+ else if (!strncmp(line, "#@", 2))
+ do_hash_data(&mdctx, line+2);
+ else if (!strncmp(line, "#$", 2))
+ do_hash_data(&mdctx, line+2);
+ else if (isdigit((unsigned char)line[0]))
+ do_hash_data(&mdctx, line);
+ }
+ isc_sha1_final(&mdctx, ldig.hv);
+ isc_sha1_invalidate(&mdctx);
+
+ if (0 > hlseen)
+ return LSVALID_NOHASH;
+ if (0 == hlseen)
+ return LSVALID_BADFORMAT;
+ if (0 != memcmp(&rdig, &ldig, sizeof(sha1_digest)))
+ return LSVALID_BADHASH;
+ return LSVALID_GOODHASH;
+}
+
+/*
+ * lstostr - prettyprint NTP seconds
+ */
+static const char *
+lstostr(
+ const vint64 * ts)
+{
+ char * buf;
+ struct calendar tm;
+
+ LIB_GETBUF(buf);
+
+ if ( ! (ts->d_s.hi >= 0 && ntpcal_ntp64_to_date(&tm, ts) >= 0))
+ snprintf(buf, LIB_BUFLENGTH, "%s", "9999-12-31T23:59:59Z");
+ else
+ snprintf(buf, LIB_BUFLENGTH, "%04d-%02d-%02dT%02d:%02d:%02dZ",
+ tm.year, tm.month, tm.monthday,
+ tm.hour, tm.minute, tm.second);
+
+ return buf;
+}
+
+/* reset the global state for unit tests */
+void
+leapsec_ut_pristine(void)
+{
+ memset(_ltab, 0, sizeof(_ltab));
+ _lptr = NULL;
+ _electric = 0;
+}
+
+
+
+/* -*- that's all folks! -*- */
OpenPOWER on IntegriCloud