diff options
author | delphij <delphij@FreeBSD.org> | 2015-07-15 19:21:26 +0000 |
---|---|---|
committer | delphij <delphij@FreeBSD.org> | 2015-07-15 19:21:26 +0000 |
commit | 2a25cee78ab1d37e7d2bc40ae675646974d99f56 (patch) | |
tree | b0302ac4be59e104f4e1e54014561a1389397192 /contrib/ntp/ntpd/ntp_leapsec.c | |
parent | a0741a75537b2e0514472ac3b28afc55a7846c30 (diff) | |
download | FreeBSD-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.c | 1186 |
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! -*- */ |