#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";
static inline void*
noconst(
const void* ptr
)
{
union {
const void * cp;
void * vp;
} tmp;
tmp.cp = ptr;
return tmp.vp;
}
#define MAX_HIST 10
struct leap_info {
vint64 ttime;
uint32_t stime;
int16_t taiof;
uint8_t dynls;
};
typedef struct leap_info leap_info_t;
struct leap_head {
vint64 update;
vint64 expire;
uint16_t size;
int16_t base_tai;
int16_t this_tai;
int16_t next_tai;
vint64 dtime;
vint64 ttime;
vint64 stime;
vint64 ebase;
uint8_t dynls;
};
typedef struct leap_head leap_head_t;
struct leap_table {
leap_signature_t lsig;
leap_head_t head;
leap_info_t info[MAX_HIST];
};
static leap_table_t _ltab[2], *_lptr;
static int _electric;
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);
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
leapsec_set_table(
leap_table_t * pt)
{
if (pt == &_ltab[0] || pt == &_ltab[1])
_lptr = pt;
return _lptr == pt;
}
int
leapsec_electric(
int 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;
}
void
leapsec_clear(
leap_table_t * pt)
{
memset(&pt->lsig, 0, sizeof(pt->lsig));
memset(&pt->head, 0, sizeof(pt->head));
reset_times(pt);
}
int
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)) {
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;
}
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);
}
}
int
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;
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) {
reload_limits(pt, &ts64);
} else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) {
last = addv64i32(&pt->head.dtime, 3);
if (ucmpv64(&ts64, &last) >= 0) {
reload_limits(pt, &ts64);
} else {
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 (ucmpv64(&ts64, &pt->head.stime) < 0)
return fired;
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 (!betweenu32(due32 - SECSPERDAY, ts32, due32))
return fired;
qr->proximity = LSPROX_ANNOUNCE;
if (!betweenu32(due32 - 10, ts32, due32))
return fired;
qr->proximity = LSPROX_ALERT;
return fired;
}
int
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
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;
}
void
leapsec_reset_frame(void)
{
reset_times(leapsec_get_table(FALSE));
}
int
leapsec_load_stream(
FILE * ifp ,
const char * fname,
int 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
leapsec_load_file(
const char * fname,
struct stat * sb_old,
int force,
int logall)
{
FILE * fp;
struct stat sb_new;
int rc;
if ( !(fname && *fname) )
return FALSE;
if (0 != stat(fname, &sb_new)) {
if (logall)
msyslog(LOG_ERR, "%s ('%s'): stat failed: %m",
logPrefix, fname);
return FALSE;
}
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;
}
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
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
int
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
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
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);
if (pt->head.base_tai != 0 || tai_offset < 10)
return FALSE;
for (idx = 0; idx != pt->head.size; idx++)
if ( ! pt->info[idx].dynls)
return FALSE;
now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
fetch_leap_era(&era, pt, &now64);
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;
tai_offset -= era.taiof;
pt->head.base_tai += tai_offset;
pt->head.this_tai += tai_offset;
pt->head.next_tai += tai_offset;
for (idx = 0; idx != pt->head.size; idx++)
pt->info[idx].taiof += tai_offset;
return TRUE;
}
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;
}
static int
add_range(
leap_table_t * pt,
const leap_info_t * pi)
{
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;
}
memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info));
pt->info[0] = *pi;
pt->head.size++;
reset_times(pt);
return TRUE;
}
static char *
get_line(
leapsec_reader func,
void * farg,
char * buff,
size_t size)
{
int ch;
char *ptr;
if (buff == NULL || size == 0)
return NULL;
ptr = buff;
while (EOF != (ch = (*func)(farg)) && '\n' != ch)
if (size > 1) {
size--;
*ptr++ = (char)ch;
}
while (ptr != buff && isspace((u_char)ptr[-1]))
ptr--;
*ptr = '\0';
return (ptr == buff && ch == EOF) ? NULL : buff;
}
static char *
skipws(
const char *ptr)
{
while (isspace((u_char)*ptr))
ptr++;
return (char*)noconst(ptr);
}
static int
parsefail(
const char * cp,
const char * ep)
{
return (cp == ep)
|| (*ep && *ep != '#' && !isspace((u_char)*ep));
}
static void
reload_limits(
leap_table_t * pt,
const vint64 * ts)
{
int idx;
for (idx = 0; idx != pt->head.size; idx++)
if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
break;
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;
}
}
static void
fetch_leap_era(
leap_era_t * into,
const leap_table_t * pt ,
const vint64 * ts )
{
int idx;
for (idx = 0; idx != pt->head.size; idx++)
if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
break;
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));
}
static int
leapsec_add(
leap_table_t* pt ,
const vint64 * now64 ,
int insert)
{
vint64 ttime, starttime;
struct calendar fts;
leap_info_t li;
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);
if (fts.monthday == 1 && fts.hour == 0) {
errno = EINVAL;
return FALSE;
}
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);
}
int
leapsec_raw(
leap_table_t * pt,
const vint64 * ttime,
int taiof,
int dynls)
{
vint64 starttime;
struct calendar fts;
leap_info_t li;
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 (fts.monthday != 1 || fts.hour || fts.minute || fts.second) {
errno = EINVAL;
return FALSE;
}
fts.month--;
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);
}
static int
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;
}
typedef struct {
unsigned char hv[ISC_SHA1_DIGESTLENGTH];
} sha1_digest;
static int
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;
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;
}
static void
do_hash_data(
isc_sha1_t * mdctx,
char const * cp )
{
unsigned char text[32]; 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);
}
int
leapsec_validate(
leapsec_reader func,
void * farg)
{
isc_sha1_t mdctx;
sha1_digest rdig, ldig;
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;
}
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;
}
void
leapsec_ut_pristine(void)
{
memset(_ltab, 0, sizeof(_ltab));
_lptr = NULL;
_electric = 0;
}