xref: /netbsd-src/external/bsd/ntp/dist/ntpd/ntp_leapsec.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: ntp_leapsec.c,v 1.8 2024/08/18 20:47:17 christos Exp $	*/
28585484eSchristos 
38585484eSchristos /*
48585484eSchristos  * ntp_leapsec.c - leap second processing for NTPD
58585484eSchristos  *
68585484eSchristos  * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
78585484eSchristos  * The contents of 'html/copyright.html' apply.
88585484eSchristos  * ----------------------------------------------------------------------
98585484eSchristos  * This is an attempt to get the leap second handling into a dedicated
108585484eSchristos  * module to make the somewhat convoluted logic testable.
118585484eSchristos  */
128585484eSchristos 
138585484eSchristos #include <config.h>
148585484eSchristos #include <sys/types.h>
15b8ecfcfeSchristos #include <sys/stat.h>
168585484eSchristos #include <ctype.h>
178585484eSchristos 
18*eabc0478Schristos #include "ntp.h"
198585484eSchristos #include "ntp_stdlib.h"
208585484eSchristos #include "ntp_calendar.h"
218585484eSchristos #include "ntp_leapsec.h"
22b8ecfcfeSchristos #include "vint64ops.h"
23b8ecfcfeSchristos 
24b8ecfcfeSchristos #include "isc/sha1.h"
25b8ecfcfeSchristos 
26b8ecfcfeSchristos static const char * const logPrefix = "leapsecond file";
278585484eSchristos 
288585484eSchristos /* ---------------------------------------------------------------------
298585484eSchristos  * Our internal data structure
308585484eSchristos  */
318585484eSchristos #define MAX_HIST 10	/* history of leap seconds */
328585484eSchristos 
338585484eSchristos struct leap_info {
348585484eSchristos 	vint64   ttime;	/* transition time (after the step, ntp scale) */
358585484eSchristos 	uint32_t stime;	/* schedule limit (a month before transition)  */
368585484eSchristos 	int16_t  taiof;	/* TAI offset on and after the transition      */
378585484eSchristos 	uint8_t  dynls; /* dynamic: inserted on peer/clock request     */
388585484eSchristos };
398585484eSchristos typedef struct leap_info leap_info_t;
408585484eSchristos 
418585484eSchristos struct leap_head {
428585484eSchristos 	vint64   update; /* time of information update                 */
438585484eSchristos 	vint64   expire; /* table expiration time                      */
448585484eSchristos 	uint16_t size;	 /* number of infos in table	               */
458585484eSchristos 	int16_t  base_tai;	/* total leaps before first entry      */
468585484eSchristos 	int16_t  this_tai;	/* current TAI offset	               */
478585484eSchristos 	int16_t  next_tai;	/* TAI offset after 'when'             */
488585484eSchristos 	vint64   dtime;	 /* due time (current era end)                 */
498585484eSchristos 	vint64   ttime;	 /* nominal transition time (next era start)   */
508585484eSchristos 	vint64   stime;	 /* schedule time (when we take notice)        */
518585484eSchristos 	vint64   ebase;	 /* base time of this leap era                 */
528585484eSchristos 	uint8_t  dynls;	 /* next leap is dynamic (by peer request)     */
538585484eSchristos };
548585484eSchristos typedef struct leap_head leap_head_t;
558585484eSchristos 
568585484eSchristos struct leap_table {
578585484eSchristos 	leap_signature_t lsig;
588585484eSchristos 	leap_head_t	 head;
598585484eSchristos 	leap_info_t  	 info[MAX_HIST];
608585484eSchristos };
618585484eSchristos 
628585484eSchristos /* Where we store our tables */
638585484eSchristos static leap_table_t _ltab[2], *_lptr;
648585484eSchristos static int/*BOOL*/  _electric;
658585484eSchristos 
668585484eSchristos /* Forward decls of local helpers */
678585484eSchristos static int		add_range	(leap_table_t *, const leap_info_t *);
68*eabc0478Schristos static char *		get_line	(leapsec_reader, void *, char *,
69*eabc0478Schristos 					 size_t);
70*eabc0478Schristos static inline char *	skipws		(char *ptr);
718585484eSchristos static int		parsefail	(const char *cp, const char *ep);
728585484eSchristos static void		reload_limits	(leap_table_t *, const vint64 *);
735d681e99Schristos static void		fetch_leap_era	(leap_era_t *, const leap_table_t *,
745d681e99Schristos 					 const vint64 *);
75*eabc0478Schristos static int		betweenu32	(u_int32, u_int32, u_int32);
768585484eSchristos static void		reset_times	(leap_table_t *);
778585484eSchristos static int		leapsec_add	(leap_table_t *, const vint64 *, int);
78*eabc0478Schristos static int		leapsec_raw	(leap_table_t *, const vint64 *, int,
79*eabc0478Schristos 					 int);
805d681e99Schristos static const char *	lstostr		(const vint64 *ts);
818585484eSchristos 
828585484eSchristos /* =====================================================================
838585484eSchristos  * Get & Set the current leap table
848585484eSchristos  */
858585484eSchristos 
868585484eSchristos /* ------------------------------------------------------------------ */
878585484eSchristos leap_table_t *
888585484eSchristos leapsec_get_table(
898585484eSchristos 	int alternate)
908585484eSchristos {
918585484eSchristos 	leap_table_t *p1, *p2;
928585484eSchristos 
938585484eSchristos 	p1 = _lptr;
945d681e99Schristos 	if (p1 == &_ltab[0]) {
955d681e99Schristos 		p2 = &_ltab[1];
965d681e99Schristos 	} else if (p1 == &_ltab[1]) {
975d681e99Schristos 		p2 = &_ltab[0];
985d681e99Schristos 	} else {
995d681e99Schristos 		p1 = &_ltab[0];
1005d681e99Schristos 		p2 = &_ltab[1];
1015d681e99Schristos 		reset_times(p1);
1025d681e99Schristos 		reset_times(p2);
1035d681e99Schristos 		_lptr = p1;
1045d681e99Schristos 	}
1058585484eSchristos 	if (alternate) {
1068585484eSchristos 		memcpy(p2, p1, sizeof(leap_table_t));
1078585484eSchristos 		p1 = p2;
1088585484eSchristos 	}
1098585484eSchristos 
1108585484eSchristos 	return p1;
1118585484eSchristos }
1128585484eSchristos 
1138585484eSchristos /* ------------------------------------------------------------------ */
1148585484eSchristos int/*BOOL*/
1158585484eSchristos leapsec_set_table(
1168585484eSchristos 	leap_table_t * pt)
1178585484eSchristos {
1188585484eSchristos 	if (pt == &_ltab[0] || pt == &_ltab[1])
1198585484eSchristos 		_lptr = pt;
1208585484eSchristos 	return _lptr == pt;
1218585484eSchristos }
1228585484eSchristos 
1238585484eSchristos /* ------------------------------------------------------------------ */
1248585484eSchristos int/*BOOL*/
1258585484eSchristos leapsec_electric(
1268585484eSchristos 	int/*BOOL*/ on)
1278585484eSchristos {
1288585484eSchristos 	int res = _electric;
1298585484eSchristos 	if (on < 0)
1308585484eSchristos 		return res;
1318585484eSchristos 
1328585484eSchristos 	_electric = (on != 0);
1338585484eSchristos 	if (_electric == res)
1348585484eSchristos 		return res;
1358585484eSchristos 
1368585484eSchristos 	if (_lptr == &_ltab[0] || _lptr == &_ltab[1])
1378585484eSchristos 		reset_times(_lptr);
1388585484eSchristos 
1398585484eSchristos 	return res;
1408585484eSchristos }
1418585484eSchristos 
1428585484eSchristos /* =====================================================================
1438585484eSchristos  * API functions that operate on tables
1448585484eSchristos  */
1458585484eSchristos 
1468585484eSchristos /* ---------------------------------------------------------------------
1478585484eSchristos  * Clear all leap second data. Use it for init & cleanup
1488585484eSchristos  */
1498585484eSchristos void
1508585484eSchristos leapsec_clear(
1518585484eSchristos 	leap_table_t * pt)
1528585484eSchristos {
1538585484eSchristos 	memset(&pt->lsig, 0, sizeof(pt->lsig));
1548585484eSchristos 	memset(&pt->head, 0, sizeof(pt->head));
1558585484eSchristos 	reset_times(pt);
1568585484eSchristos }
1578585484eSchristos 
1588585484eSchristos /* ---------------------------------------------------------------------
1598585484eSchristos  * Load a leap second file and check expiration on the go
1608585484eSchristos  */
1618585484eSchristos int/*BOOL*/
1628585484eSchristos leapsec_load(
1638585484eSchristos 	leap_table_t *	pt,
1648585484eSchristos 	leapsec_reader	func,
1658585484eSchristos 	void *		farg,
166*eabc0478Schristos 	int		use_build_limit
167*eabc0478Schristos 	)
1688585484eSchristos {
169*eabc0478Schristos 	char		*cp, *ep, *endp, linebuf[50];
1708585484eSchristos 	vint64		ttime, limit;
1718585484eSchristos 	long		taiof;
1728585484eSchristos 	struct calendar	build;
1738585484eSchristos 
1748585484eSchristos 	leapsec_clear(pt);
1755d681e99Schristos 	if (use_build_limit && ntpcal_get_build_date(&build)) {
1765d681e99Schristos 		/* don't prune everything -- permit the last 10yrs
1775d681e99Schristos 		 * before build.
1785d681e99Schristos 		 */
1795d681e99Schristos 		build.year -= 10;
1808585484eSchristos 		limit = ntpcal_date_to_ntp64(&build);
1815d681e99Schristos 	} else {
1828585484eSchristos 		memset(&limit, 0, sizeof(limit));
1835d681e99Schristos 	}
1848585484eSchristos 
1858585484eSchristos 	while (get_line(func, farg, linebuf, sizeof(linebuf))) {
1868585484eSchristos 		cp = linebuf;
1878585484eSchristos 		if (*cp == '#') {
1888585484eSchristos 			cp++;
1898585484eSchristos 			if (*cp == '@') {
1908585484eSchristos 				cp = skipws(cp+1);
1918585484eSchristos 				pt->head.expire = strtouv64(cp, &ep, 10);
1928585484eSchristos 				if (parsefail(cp, ep))
1938585484eSchristos 					goto fail_read;
1948585484eSchristos 				pt->lsig.etime = pt->head.expire.D_s.lo;
1958585484eSchristos 			} else if (*cp == '$') {
1968585484eSchristos 				cp = skipws(cp+1);
1978585484eSchristos 				pt->head.update = strtouv64(cp, &ep, 10);
1988585484eSchristos 				if (parsefail(cp, ep))
1998585484eSchristos 					goto fail_read;
2008585484eSchristos 			}
2018585484eSchristos 		} else if (isdigit((u_char)*cp)) {
2028585484eSchristos 			ttime = strtouv64(cp, &ep, 10);
2038585484eSchristos 			if (parsefail(cp, ep))
2048585484eSchristos 				goto fail_read;
2058585484eSchristos 			cp = skipws(ep);
206*eabc0478Schristos 			taiof = strtol(cp, &endp, 10);
207*eabc0478Schristos 			if (   parsefail(cp, endp)
208*eabc0478Schristos 			    || taiof > INT16_MAX || taiof < INT16_MIN)
2098585484eSchristos 				goto fail_read;
2108585484eSchristos 			if (ucmpv64(&ttime, &limit) >= 0) {
2118585484eSchristos 				if (!leapsec_raw(pt, &ttime,
2128585484eSchristos 						 taiof, FALSE))
2138585484eSchristos 					goto fail_insn;
2148585484eSchristos 			} else {
2158585484eSchristos 				pt->head.base_tai = (int16_t)taiof;
2168585484eSchristos 			}
2178585484eSchristos 			pt->lsig.ttime = ttime.D_s.lo;
2188585484eSchristos 			pt->lsig.taiof = (int16_t)taiof;
2198585484eSchristos 		}
2208585484eSchristos 	}
2218585484eSchristos 	return TRUE;
2228585484eSchristos 
2238585484eSchristos fail_read:
2248585484eSchristos 	errno = EILSEQ;
2258585484eSchristos fail_insn:
2268585484eSchristos 	leapsec_clear(pt);
2278585484eSchristos 	return FALSE;
2288585484eSchristos }
2298585484eSchristos 
2308585484eSchristos /* ---------------------------------------------------------------------
2318585484eSchristos  * Dump a table in human-readable format. Use 'fprintf' and a FILE
2328585484eSchristos  * pointer if you want to get it printed into a stream.
2338585484eSchristos  */
2348585484eSchristos void
2358585484eSchristos leapsec_dump(
2368585484eSchristos 	const leap_table_t * pt  ,
2378585484eSchristos 	leapsec_dumper       func,
2388585484eSchristos 	void *               farg)
2398585484eSchristos {
2408585484eSchristos 	int             idx;
2418585484eSchristos 	vint64          ts;
2428585484eSchristos 	struct calendar atb, ttb;
2438585484eSchristos 
2448585484eSchristos 	ntpcal_ntp64_to_date(&ttb, &pt->head.expire);
2458585484eSchristos 	(*func)(farg, "leap table (%u entries) expires at %04u-%02u-%02u:\n",
2468585484eSchristos 		pt->head.size,
2478585484eSchristos 		ttb.year, ttb.month, ttb.monthday);
2488585484eSchristos 	idx = pt->head.size;
2498585484eSchristos 	while (idx-- != 0) {
2508585484eSchristos 		ts = pt->info[idx].ttime;
2518585484eSchristos 		ntpcal_ntp64_to_date(&ttb, &ts);
2528585484eSchristos 		ts = subv64u32(&ts, pt->info[idx].stime);
2538585484eSchristos 		ntpcal_ntp64_to_date(&atb, &ts);
2548585484eSchristos 
2558585484eSchristos 		(*func)(farg, "%04u-%02u-%02u [%c] (%04u-%02u-%02u) - %d\n",
2568585484eSchristos 			ttb.year, ttb.month, ttb.monthday,
2578585484eSchristos 			"-*"[pt->info[idx].dynls != 0],
2588585484eSchristos 			atb.year, atb.month, atb.monthday,
2598585484eSchristos 			pt->info[idx].taiof);
2608585484eSchristos 	}
2618585484eSchristos }
2628585484eSchristos 
2638585484eSchristos /* =====================================================================
2648585484eSchristos  * usecase driven API functions
2658585484eSchristos  */
2668585484eSchristos 
2678585484eSchristos int/*BOOL*/
2688585484eSchristos leapsec_query(
2698585484eSchristos 	leap_result_t * qr   ,
2708585484eSchristos 	uint32_t        ts32 ,
2718585484eSchristos 	const time_t *  pivot)
2728585484eSchristos {
2738585484eSchristos 	leap_table_t *   pt;
2748585484eSchristos 	vint64           ts64, last, next;
2758585484eSchristos 	uint32_t         due32;
2768585484eSchristos 	int              fired;
2778585484eSchristos 
2788585484eSchristos 	/* preset things we use later on... */
2798585484eSchristos 	fired = FALSE;
2808585484eSchristos 	ts64  = ntpcal_ntp_to_ntp(ts32, pivot);
2818585484eSchristos 	pt    = leapsec_get_table(FALSE);
2828585484eSchristos 	memset(qr, 0, sizeof(leap_result_t));
2838585484eSchristos 
2848585484eSchristos 	if (ucmpv64(&ts64, &pt->head.ebase) < 0) {
2858585484eSchristos 		/* Most likely after leap frame reset. Could also be a
2868585484eSchristos 		 * backstep of the system clock. Anyway, get the new
2878585484eSchristos 		 * leap era frame.
2888585484eSchristos 		 */
2898585484eSchristos 		reload_limits(pt, &ts64);
2908585484eSchristos 	} else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) {
2918585484eSchristos 		/* Boundary crossed in forward direction. This might
2928585484eSchristos 		 * indicate a leap transition, so we prepare for that
2938585484eSchristos 		 * case.
2948585484eSchristos 		 *
2958585484eSchristos 		 * Some operations below are actually NOPs in electric
2968585484eSchristos 		 * mode, but having only one code path that works for
2978585484eSchristos 		 * both modes is easier to maintain.
2985d681e99Schristos 		 *
2995d681e99Schristos 		 * There's another quirk we must keep looking out for:
3005d681e99Schristos 		 * If we just stepped the clock, the step might have
3015d681e99Schristos 		 * crossed a leap boundary. As with backward steps, we
3025d681e99Schristos 		 * do not want to raise the 'fired' event in that case.
3035d681e99Schristos 		 * So we raise the 'fired' event only if we're close to
3045d681e99Schristos 		 * the transition and just reload the limits otherwise.
3058585484eSchristos 		 */
3065d681e99Schristos 		last = addv64i32(&pt->head.dtime, 3); /* get boundary */
3075d681e99Schristos 		if (ucmpv64(&ts64, &last) >= 0) {
3085d681e99Schristos 			/* that was likely a query after a step */
3095d681e99Schristos 			reload_limits(pt, &ts64);
3105d681e99Schristos 		} else {
3115d681e99Schristos 			/* close enough for deeper examination */
3128585484eSchristos 			last = pt->head.ttime;
3138585484eSchristos 			qr->warped = (int16_t)(last.D_s.lo -
3148585484eSchristos 					       pt->head.dtime.D_s.lo);
3158585484eSchristos 			next = addv64i32(&ts64, qr->warped);
3168585484eSchristos 			reload_limits(pt, &next);
3178585484eSchristos 			fired = ucmpv64(&pt->head.ebase, &last) == 0;
3188585484eSchristos 			if (fired) {
3198585484eSchristos 				ts64 = next;
3208585484eSchristos 				ts32 = next.D_s.lo;
3218585484eSchristos 			} else {
3228585484eSchristos 				qr->warped = 0;
3238585484eSchristos 			}
3248585484eSchristos 		}
3255d681e99Schristos 	}
3268585484eSchristos 
3278585484eSchristos 	qr->tai_offs = pt->head.this_tai;
3285d681e99Schristos 	qr->ebase    = pt->head.ebase;
3295d681e99Schristos 	qr->ttime    = pt->head.ttime;
3308585484eSchristos 
3318585484eSchristos 	/* If before the next scheduling alert, we're done. */
3328585484eSchristos 	if (ucmpv64(&ts64, &pt->head.stime) < 0)
3338585484eSchristos 		return fired;
3348585484eSchristos 
3355d681e99Schristos 	/* now start to collect the remaining data */
3368585484eSchristos 	due32 = pt->head.dtime.D_s.lo;
3378585484eSchristos 
3388585484eSchristos 	qr->tai_diff  = pt->head.next_tai - pt->head.this_tai;
3398585484eSchristos 	qr->ddist     = due32 - ts32;
3408585484eSchristos 	qr->dynamic   = pt->head.dynls;
3418585484eSchristos 	qr->proximity = LSPROX_SCHEDULE;
3428585484eSchristos 
3438585484eSchristos 	/* if not in the last day before transition, we're done. */
3448585484eSchristos 	if (!betweenu32(due32 - SECSPERDAY, ts32, due32))
3458585484eSchristos 		return fired;
3468585484eSchristos 
3478585484eSchristos 	qr->proximity = LSPROX_ANNOUNCE;
3488585484eSchristos 	if (!betweenu32(due32 - 10, ts32, due32))
3498585484eSchristos 		return fired;
3508585484eSchristos 
3518585484eSchristos 	/* The last 10s before the transition. Prepare for action! */
3528585484eSchristos 	qr->proximity = LSPROX_ALERT;
3538585484eSchristos 	return fired;
3548585484eSchristos }
3558585484eSchristos 
3568585484eSchristos /* ------------------------------------------------------------------ */
3578585484eSchristos int/*BOOL*/
3585d681e99Schristos leapsec_query_era(
3595d681e99Schristos 	leap_era_t *   qr   ,
3605d681e99Schristos 	uint32_t       ntpts,
3615d681e99Schristos 	const time_t * pivot)
3625d681e99Schristos {
3635d681e99Schristos 	const leap_table_t * pt;
3645d681e99Schristos 	vint64               ts64;
3655d681e99Schristos 
3665d681e99Schristos 	pt   = leapsec_get_table(FALSE);
3675d681e99Schristos 	ts64 = ntpcal_ntp_to_ntp(ntpts, pivot);
3685d681e99Schristos 	fetch_leap_era(qr, pt, &ts64);
3695d681e99Schristos 	return TRUE;
3705d681e99Schristos }
3715d681e99Schristos 
3725d681e99Schristos /* ------------------------------------------------------------------ */
3735d681e99Schristos int/*BOOL*/
3748585484eSchristos leapsec_frame(
3758585484eSchristos         leap_result_t *qr)
3768585484eSchristos {
3778585484eSchristos 	const leap_table_t * pt;
3788585484eSchristos 
3798585484eSchristos         memset(qr, 0, sizeof(leap_result_t));
3808585484eSchristos 	pt = leapsec_get_table(FALSE);
3818585484eSchristos 
3828585484eSchristos 	qr->tai_offs = pt->head.this_tai;
3838585484eSchristos 	qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
3845d681e99Schristos 	qr->ebase    = pt->head.ebase;
3858585484eSchristos 	qr->ttime    = pt->head.ttime;
3868585484eSchristos 	qr->dynamic  = pt->head.dynls;
3878585484eSchristos 
3885d681e99Schristos 	return ucmpv64(&pt->head.ttime, &pt->head.stime) >= 0;
3898585484eSchristos }
3908585484eSchristos 
3918585484eSchristos /* ------------------------------------------------------------------ */
3928585484eSchristos /* Reset the current leap frame */
3938585484eSchristos void
3948585484eSchristos leapsec_reset_frame(void)
3958585484eSchristos {
3968585484eSchristos 	reset_times(leapsec_get_table(FALSE));
3978585484eSchristos }
3988585484eSchristos 
3998585484eSchristos /* ------------------------------------------------------------------ */
400*eabc0478Schristos /* load a file from a FILE pointer. Note: If vhash is true, load
401b8ecfcfeSchristos  * only after successful signature check. The stream must be seekable
402b8ecfcfeSchristos  * or this will fail.
403b8ecfcfeSchristos  */
4048585484eSchristos int/*BOOL*/
405b8ecfcfeSchristos leapsec_load_stream(
4068585484eSchristos 	FILE       * ifp  ,
407b8ecfcfeSchristos 	const char * fname,
408cdfa2a7eSchristos 	int/*BOOL*/  logall,
409cdfa2a7eSchristos 	int/*BOOL*/  vhash)
4108585484eSchristos {
4118585484eSchristos 	leap_table_t *pt;
412b8ecfcfeSchristos 	int           rcheck;
4138585484eSchristos 
414b8ecfcfeSchristos 	if (NULL == fname)
415b8ecfcfeSchristos 		fname = "<unknown>";
416b8ecfcfeSchristos 
417cdfa2a7eSchristos 	if (vhash) {
418*eabc0478Schristos 		rcheck = leapsec_validate((leapsec_reader)&getc, ifp);
419b8ecfcfeSchristos 		if (logall)
420b8ecfcfeSchristos 			switch (rcheck)
421b8ecfcfeSchristos 			{
422b8ecfcfeSchristos 			case LSVALID_GOODHASH:
423b8ecfcfeSchristos 				msyslog(LOG_NOTICE, "%s ('%s'): good hash signature",
424b8ecfcfeSchristos 					logPrefix, fname);
425b8ecfcfeSchristos 				break;
426b8ecfcfeSchristos 
427b8ecfcfeSchristos 			case LSVALID_NOHASH:
428b8ecfcfeSchristos 				msyslog(LOG_ERR, "%s ('%s'): no hash signature",
429b8ecfcfeSchristos 					logPrefix, fname);
430b8ecfcfeSchristos 				break;
431b8ecfcfeSchristos 			case LSVALID_BADHASH:
432b8ecfcfeSchristos 				msyslog(LOG_ERR, "%s ('%s'): signature mismatch",
433b8ecfcfeSchristos 					logPrefix, fname);
434b8ecfcfeSchristos 				break;
435b8ecfcfeSchristos 			case LSVALID_BADFORMAT:
436b8ecfcfeSchristos 				msyslog(LOG_ERR, "%s ('%s'): malformed hash signature",
437b8ecfcfeSchristos 					logPrefix, fname);
438b8ecfcfeSchristos 				break;
439b8ecfcfeSchristos 			default:
440b8ecfcfeSchristos 				msyslog(LOG_ERR, "%s ('%s'): unknown error code %d",
441b8ecfcfeSchristos 					logPrefix, fname, rcheck);
442b8ecfcfeSchristos 				break;
443b8ecfcfeSchristos 			}
444b8ecfcfeSchristos 		if (rcheck < 0)
445b8ecfcfeSchristos 			return FALSE;
446b8ecfcfeSchristos 		rewind(ifp);
447cdfa2a7eSchristos 	}
4488585484eSchristos 	pt = leapsec_get_table(TRUE);
449b8ecfcfeSchristos 	if (!leapsec_load(pt, (leapsec_reader)getc, ifp, TRUE)) {
450b8ecfcfeSchristos 		switch (errno) {
451b8ecfcfeSchristos 		case EINVAL:
452b8ecfcfeSchristos 			msyslog(LOG_ERR, "%s ('%s'): bad transition time",
453b8ecfcfeSchristos 				logPrefix, fname);
454b8ecfcfeSchristos 			break;
455b8ecfcfeSchristos 		case ERANGE:
456b8ecfcfeSchristos 			msyslog(LOG_ERR, "%s ('%s'): times not ascending",
457b8ecfcfeSchristos 				logPrefix, fname);
458b8ecfcfeSchristos 			break;
459b8ecfcfeSchristos 		default:
460b8ecfcfeSchristos 			msyslog(LOG_ERR, "%s ('%s'): parsing error",
461b8ecfcfeSchristos 				logPrefix, fname);
462b8ecfcfeSchristos 			break;
463b8ecfcfeSchristos 		}
464b8ecfcfeSchristos 		return FALSE;
465b8ecfcfeSchristos 	}
466b8ecfcfeSchristos 
467b8ecfcfeSchristos 	if (pt->head.size)
468b8ecfcfeSchristos 		msyslog(LOG_NOTICE, "%s ('%s'): loaded, expire=%s last=%s ofs=%d",
469b8ecfcfeSchristos 			logPrefix, fname, lstostr(&pt->head.expire),
470b8ecfcfeSchristos 			lstostr(&pt->info[0].ttime), pt->info[0].taiof);
471b8ecfcfeSchristos 	else
472b8ecfcfeSchristos 		msyslog(LOG_NOTICE,
473b8ecfcfeSchristos 			"%s ('%s'): loaded, expire=%s ofs=%d (no entries after build date)",
474b8ecfcfeSchristos 			logPrefix, fname, lstostr(&pt->head.expire),
475b8ecfcfeSchristos 			pt->head.base_tai);
476b8ecfcfeSchristos 
477b8ecfcfeSchristos 	return leapsec_set_table(pt);
478b8ecfcfeSchristos }
479b8ecfcfeSchristos 
480b8ecfcfeSchristos /* ------------------------------------------------------------------ */
481b8ecfcfeSchristos int/*BOOL*/
482b8ecfcfeSchristos leapsec_load_file(
483b8ecfcfeSchristos 	const char  * fname,
484b8ecfcfeSchristos 	struct stat * sb_old,
485b8ecfcfeSchristos 	int/*BOOL*/   force,
486cdfa2a7eSchristos 	int/*BOOL*/   logall,
487cdfa2a7eSchristos 	int/*BOOL*/   vhash)
488b8ecfcfeSchristos {
489b8ecfcfeSchristos 	FILE       * fp;
490b8ecfcfeSchristos 	struct stat  sb_new;
491b8ecfcfeSchristos 	int          rc;
492b8ecfcfeSchristos 
493b8ecfcfeSchristos 	/* just do nothing if there is no leap file */
494b8ecfcfeSchristos 	if ( !(fname && *fname) )
495b8ecfcfeSchristos 		return FALSE;
496b8ecfcfeSchristos 
497b8ecfcfeSchristos 	/* try to stat the leapfile */
498b8ecfcfeSchristos 	if (0 != stat(fname, &sb_new)) {
499b8ecfcfeSchristos 		if (logall)
500b8ecfcfeSchristos 			msyslog(LOG_ERR, "%s ('%s'): stat failed: %m",
501b8ecfcfeSchristos 				logPrefix, fname);
502b8ecfcfeSchristos 		return FALSE;
503b8ecfcfeSchristos 	}
504b8ecfcfeSchristos 
505b8ecfcfeSchristos 	/* silently skip to postcheck if no new file found */
506b8ecfcfeSchristos 	if (NULL != sb_old) {
507b8ecfcfeSchristos 		if (!force
508b8ecfcfeSchristos 		 && sb_old->st_mtime == sb_new.st_mtime
509b8ecfcfeSchristos 		 && sb_old->st_ctime == sb_new.st_ctime
510b8ecfcfeSchristos 		   )
511b8ecfcfeSchristos 			return FALSE;
512b8ecfcfeSchristos 		*sb_old = sb_new;
513b8ecfcfeSchristos 	}
514b8ecfcfeSchristos 
515b8ecfcfeSchristos 	/* try to open the leap file, complain if that fails
516b8ecfcfeSchristos 	 *
517b8ecfcfeSchristos 	 * [perlinger@ntp.org]
518b8ecfcfeSchristos 	 * coverity raises a TOCTOU (time-of-check/time-of-use) issue
519b8ecfcfeSchristos 	 * here, which is not entirely helpful: While there is indeed a
520b8ecfcfeSchristos 	 * possible race condition between the 'stat()' call above and
521b8ecfcfeSchristos 	 * the 'fopen)' call below, I intentionally want to omit the
522b8ecfcfeSchristos 	 * overhead of opening the file and calling 'fstat()', because
523b8ecfcfeSchristos 	 * in most cases the file would have be to closed anyway without
524b8ecfcfeSchristos 	 * reading the contents.  I chose to disable the coverity
525b8ecfcfeSchristos 	 * warning instead.
526b8ecfcfeSchristos 	 *
527b8ecfcfeSchristos 	 * So unless someone comes up with a reasonable argument why
528b8ecfcfeSchristos 	 * this could be a real issue, I'll just try to silence coverity
529b8ecfcfeSchristos 	 * on that topic.
530b8ecfcfeSchristos 	 */
531b8ecfcfeSchristos 	/* coverity[toctou] */
532b8ecfcfeSchristos 	if ((fp = fopen(fname, "r")) == NULL) {
533b8ecfcfeSchristos 		if (logall)
534b8ecfcfeSchristos 			msyslog(LOG_ERR,
535b8ecfcfeSchristos 				"%s ('%s'): open failed: %m",
536b8ecfcfeSchristos 				logPrefix, fname);
537b8ecfcfeSchristos 		return FALSE;
538b8ecfcfeSchristos 	}
539b8ecfcfeSchristos 
540cdfa2a7eSchristos 	rc = leapsec_load_stream(fp, fname, logall, vhash);
541b8ecfcfeSchristos 	fclose(fp);
542b8ecfcfeSchristos 	return rc;
5438585484eSchristos }
5448585484eSchristos 
5458585484eSchristos /* ------------------------------------------------------------------ */
5468585484eSchristos void
5478585484eSchristos leapsec_getsig(
5488585484eSchristos 	leap_signature_t * psig)
5498585484eSchristos {
5508585484eSchristos 	const leap_table_t * pt;
5518585484eSchristos 
5528585484eSchristos 	pt = leapsec_get_table(FALSE);
5538585484eSchristos 	memcpy(psig, &pt->lsig, sizeof(leap_signature_t));
5548585484eSchristos }
5558585484eSchristos 
5568585484eSchristos /* ------------------------------------------------------------------ */
5578585484eSchristos int/*BOOL*/
5588585484eSchristos leapsec_expired(
5598585484eSchristos 	uint32_t       when,
5608585484eSchristos 	const time_t * tpiv)
5618585484eSchristos {
5628585484eSchristos 	const leap_table_t * pt;
5638585484eSchristos 	vint64 limit;
5648585484eSchristos 
5658585484eSchristos 	pt = leapsec_get_table(FALSE);
5668585484eSchristos 	limit = ntpcal_ntp_to_ntp(when, tpiv);
5678585484eSchristos 	return ucmpv64(&limit, &pt->head.expire) >= 0;
5688585484eSchristos }
5698585484eSchristos 
5708585484eSchristos /* ------------------------------------------------------------------ */
5718585484eSchristos int32_t
5728585484eSchristos leapsec_daystolive(
5738585484eSchristos 	uint32_t       when,
5748585484eSchristos 	const time_t * tpiv)
5758585484eSchristos {
5768585484eSchristos 	const leap_table_t * pt;
5778585484eSchristos 	vint64 limit;
5788585484eSchristos 
5798585484eSchristos 	pt = leapsec_get_table(FALSE);
5808585484eSchristos 	limit = ntpcal_ntp_to_ntp(when, tpiv);
5818585484eSchristos 	limit = subv64(&pt->head.expire, &limit);
5828585484eSchristos 	return ntpcal_daysplit(&limit).hi;
5838585484eSchristos }
5848585484eSchristos 
5858585484eSchristos /* ------------------------------------------------------------------ */
5865d681e99Schristos #if 0 /* currently unused -- possibly revived later */
5878585484eSchristos int/*BOOL*/
5888585484eSchristos leapsec_add_fix(
5898585484eSchristos 	int            total,
5908585484eSchristos 	uint32_t       ttime,
5918585484eSchristos 	uint32_t       etime,
5928585484eSchristos 	const time_t * pivot)
5938585484eSchristos {
5948585484eSchristos 	time_t         tpiv;
5958585484eSchristos 	leap_table_t * pt;
5968585484eSchristos 	vint64         tt64, et64;
5978585484eSchristos 
5988585484eSchristos 	if (pivot == NULL) {
5998585484eSchristos 		time(&tpiv);
6008585484eSchristos 		pivot = &tpiv;
6018585484eSchristos 	}
6028585484eSchristos 
6038585484eSchristos 	et64 = ntpcal_ntp_to_ntp(etime, pivot);
6048585484eSchristos 	tt64 = ntpcal_ntp_to_ntp(ttime, pivot);
6058585484eSchristos 	pt   = leapsec_get_table(TRUE);
6068585484eSchristos 
6078585484eSchristos 	if (   ucmpv64(&et64, &pt->head.expire) <= 0
6088585484eSchristos 	   || !leapsec_raw(pt, &tt64, total, FALSE) )
6098585484eSchristos 		return FALSE;
6108585484eSchristos 
6118585484eSchristos 	pt->lsig.etime = etime;
6128585484eSchristos 	pt->lsig.ttime = ttime;
6138585484eSchristos 	pt->lsig.taiof = (int16_t)total;
6148585484eSchristos 
6158585484eSchristos 	pt->head.expire = et64;
6168585484eSchristos 
6178585484eSchristos 	return leapsec_set_table(pt);
6188585484eSchristos }
6195d681e99Schristos #endif
6208585484eSchristos 
6218585484eSchristos /* ------------------------------------------------------------------ */
6228585484eSchristos int/*BOOL*/
6238585484eSchristos leapsec_add_dyn(
6248585484eSchristos 	int            insert,
6258585484eSchristos 	uint32_t       ntpnow,
6268585484eSchristos 	const time_t * pivot )
6278585484eSchristos {
6288585484eSchristos 	leap_table_t * pt;
6298585484eSchristos 	vint64         now64;
6308585484eSchristos 
6318585484eSchristos 	pt = leapsec_get_table(TRUE);
6328585484eSchristos 	now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
633b8ecfcfeSchristos 	return (   leapsec_add(pt, &now64, (insert != 0))
634b8ecfcfeSchristos 		&& leapsec_set_table(pt));
6358585484eSchristos }
6368585484eSchristos 
6375d681e99Schristos /* ------------------------------------------------------------------ */
6385d681e99Schristos int/*BOOL*/
6395d681e99Schristos leapsec_autokey_tai(
6405d681e99Schristos 	int            tai_offset,
6415d681e99Schristos 	uint32_t       ntpnow    ,
6425d681e99Schristos 	const time_t * pivot     )
6435d681e99Schristos {
6445d681e99Schristos 	leap_table_t * pt;
6455d681e99Schristos 	leap_era_t     era;
6465d681e99Schristos 	vint64         now64;
6475d681e99Schristos 	int            idx;
6485d681e99Schristos 
6495d681e99Schristos 	(void)tai_offset;
6505d681e99Schristos 	pt = leapsec_get_table(FALSE);
6515d681e99Schristos 
6525d681e99Schristos 	/* Bail out if the basic offset is not zero and the putative
6535d681e99Schristos 	 * offset is bigger than 10s. That was in 1972 -- we don't want
6545d681e99Schristos 	 * to go back that far!
6555d681e99Schristos 	 */
6565d681e99Schristos 	if (pt->head.base_tai != 0 || tai_offset < 10)
6575d681e99Schristos 		return FALSE;
6585d681e99Schristos 
6595d681e99Schristos 	/* If there's already data in the table, check if an update is
6605d681e99Schristos 	 * possible. Update is impossible if there are static entries
6615d681e99Schristos 	 * (since this indicates a valid leapsecond file) or if we're
6625d681e99Schristos 	 * too close to a leapsecond transition: We do not know on what
6635d681e99Schristos 	 * side the transition the sender might have been, so we use a
6645d681e99Schristos 	 * dead zone around the transition.
6655d681e99Schristos 	 */
6665d681e99Schristos 
6675d681e99Schristos 	/* Check for static entries */
6685d681e99Schristos 	for (idx = 0; idx != pt->head.size; idx++)
6695d681e99Schristos 		if ( ! pt->info[idx].dynls)
6705d681e99Schristos 			return FALSE;
6715d681e99Schristos 
6725d681e99Schristos 	/* get the fulll time stamp and leap era for it */
6735d681e99Schristos 	now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
6745d681e99Schristos 	fetch_leap_era(&era, pt, &now64);
6755d681e99Schristos 
6765d681e99Schristos 	/* check the limits with 20s dead band */
6775d681e99Schristos 	era.ebase = addv64i32(&era.ebase,  20);
6785d681e99Schristos 	if (ucmpv64(&now64, &era.ebase) < 0)
6795d681e99Schristos 		return FALSE;
6805d681e99Schristos 
6815d681e99Schristos 	era.ttime = addv64i32(&era.ttime, -20);
6825d681e99Schristos 	if (ucmpv64(&now64, &era.ttime) > 0)
6835d681e99Schristos 		return FALSE;
6845d681e99Schristos 
6855d681e99Schristos 	/* Here we can proceed. Calculate the delta update. */
6865d681e99Schristos 	tai_offset -= era.taiof;
6875d681e99Schristos 
6885d681e99Schristos 	/* Shift the header info offsets. */
6895d681e99Schristos 	pt->head.base_tai += tai_offset;
6905d681e99Schristos 	pt->head.this_tai += tai_offset;
6915d681e99Schristos 	pt->head.next_tai += tai_offset;
6925d681e99Schristos 
6935d681e99Schristos 	/* Shift table entry offsets (if any) */
6945d681e99Schristos 	for (idx = 0; idx != pt->head.size; idx++)
6955d681e99Schristos 		pt->info[idx].taiof += tai_offset;
6965d681e99Schristos 
6975d681e99Schristos 	/* claim success... */
6985d681e99Schristos 	return TRUE;
6995d681e99Schristos }
7005d681e99Schristos 
7015d681e99Schristos 
7028585484eSchristos /* =====================================================================
7038585484eSchristos  * internal helpers
7048585484eSchristos  */
7058585484eSchristos 
7068585484eSchristos /* [internal] Reset / init the time window in the leap processor to
7078585484eSchristos  * force reload on next query. Since a leap transition cannot take place
7088585484eSchristos  * at an odd second, the value chosen avoids spurious leap transition
7098585484eSchristos  * triggers. Making all three times equal forces a reload. Using the
7108585484eSchristos  * maximum value for unsigned 64 bits makes finding the next leap frame
7118585484eSchristos  * a bit easier.
7128585484eSchristos  */
7138585484eSchristos static void
7148585484eSchristos reset_times(
7158585484eSchristos 	leap_table_t * pt)
7168585484eSchristos {
7178585484eSchristos 	memset(&pt->head.ebase, 0xFF, sizeof(vint64));
7188585484eSchristos 	pt->head.stime = pt->head.ebase;
7198585484eSchristos 	pt->head.ttime = pt->head.ebase;
7208585484eSchristos 	pt->head.dtime = pt->head.ebase;
7218585484eSchristos }
7228585484eSchristos 
7238585484eSchristos /* [internal] Add raw data to the table, removing old entries on the
7248585484eSchristos  * fly. This cannot fail currently.
7258585484eSchristos  */
7268585484eSchristos static int/*BOOL*/
7278585484eSchristos add_range(
7288585484eSchristos 	leap_table_t *      pt,
7298585484eSchristos 	const leap_info_t * pi)
7308585484eSchristos {
7318585484eSchristos 	/* If the table is full, make room by throwing out the oldest
7324eea345dSchristos 	 * entry. But remember the accumulated leap seconds!
7334eea345dSchristos 	 *
7344eea345dSchristos 	 * Setting the first entry is a bit tricky, too: Simply assuming
7354eea345dSchristos 	 * it is an insertion is wrong if the first entry is a dynamic
7364eea345dSchristos 	 * leap second removal. So we decide on the sign -- if the first
7374eea345dSchristos 	 * entry has a negative offset, we assume that it is a leap
7384eea345dSchristos 	 * second removal. In both cases the table base offset is set
7394eea345dSchristos 	 * accordingly to reflect the decision.
7404eea345dSchristos 	 *
7414eea345dSchristos 	 * In practice starting with a removal can only happen if the
7424eea345dSchristos 	 * first entry is a dynamic request without having a leap file
7434eea345dSchristos 	 * for the history proper.
7448585484eSchristos 	 */
7455d681e99Schristos 	if (pt->head.size == 0) {
7464eea345dSchristos 		if (pi->taiof >= 0)
7475d681e99Schristos 			pt->head.base_tai = pi->taiof - 1;
7484eea345dSchristos 		else
7494eea345dSchristos 			pt->head.base_tai = pi->taiof + 1;
7505d681e99Schristos 	} else if (pt->head.size >= MAX_HIST) {
7518585484eSchristos 		pt->head.size     = MAX_HIST - 1;
7528585484eSchristos 		pt->head.base_tai = pt->info[pt->head.size].taiof;
7538585484eSchristos 	}
7548585484eSchristos 
7558585484eSchristos 	/* make room in lower end and insert item */
7568585484eSchristos 	memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info));
7578585484eSchristos 	pt->info[0] = *pi;
7588585484eSchristos 	pt->head.size++;
7598585484eSchristos 
7608585484eSchristos 	/* invalidate the cached limit data -- we might have news ;-)
7618585484eSchristos 	 *
7628585484eSchristos 	 * This blocks a spurious transition detection. OTOH, if you add
7638585484eSchristos 	 * a value after the last query before a leap transition was
7648585484eSchristos 	 * expected to occur, this transition trigger is lost. But we
7658585484eSchristos 	 * can probably live with that.
7668585484eSchristos 	 */
7678585484eSchristos 	reset_times(pt);
7688585484eSchristos 	return TRUE;
7698585484eSchristos }
7708585484eSchristos 
7718585484eSchristos /* [internal] given a reader function, read characters into a buffer
7728585484eSchristos  * until either EOL or EOF is reached. Makes sure that the buffer is
7738585484eSchristos  * always NUL terminated, but silently truncates excessive data. The
7748585484eSchristos  * EOL-marker ('\n') is *not* stored in the buffer.
7758585484eSchristos  *
7768585484eSchristos  * Returns the pointer to the buffer, unless EOF was reached when trying
7778585484eSchristos  * to read the first character of a line.
7788585484eSchristos  */
7798585484eSchristos static char *
7808585484eSchristos get_line(
7818585484eSchristos 	leapsec_reader func,
7828585484eSchristos 	void *         farg,
7838585484eSchristos 	char *         buff,
7848585484eSchristos 	size_t         size)
7858585484eSchristos {
7868585484eSchristos 	int   ch;
7878585484eSchristos 	char *ptr;
7888585484eSchristos 
7898585484eSchristos 	/* if we cannot even store the delimiter, declare failure */
7908585484eSchristos 	if (buff == NULL || size == 0)
7918585484eSchristos 		return NULL;
7928585484eSchristos 
7938585484eSchristos 	ptr = buff;
7948585484eSchristos 	while (EOF != (ch = (*func)(farg)) && '\n' != ch)
7958585484eSchristos 		if (size > 1) {
7968585484eSchristos 			size--;
7978585484eSchristos 			*ptr++ = (char)ch;
7988585484eSchristos 		}
7998585484eSchristos 	/* discard trailing whitespace */
8008585484eSchristos 	while (ptr != buff && isspace((u_char)ptr[-1]))
8018585484eSchristos 		ptr--;
8028585484eSchristos 	*ptr = '\0';
8038585484eSchristos 	return (ptr == buff && ch == EOF) ? NULL : buff;
8048585484eSchristos }
8058585484eSchristos 
8068585484eSchristos /* [internal] skips whitespace characters from a character buffer. */
807*eabc0478Schristos static inline char *
8088585484eSchristos skipws(
809*eabc0478Schristos 	char *	ptr
810*eabc0478Schristos 	)
8118585484eSchristos {
812*eabc0478Schristos 	while (isspace((u_char)*ptr)) {
8138585484eSchristos 		ptr++;
814*eabc0478Schristos 	}
815*eabc0478Schristos 	return ptr;
8168585484eSchristos }
8178585484eSchristos 
8185d681e99Schristos /* [internal] check if a strtoXYZ ended at EOL or whitespace and
8198585484eSchristos  * converted something at all. Return TRUE if something went wrong.
8208585484eSchristos  */
8218585484eSchristos static int/*BOOL*/
8228585484eSchristos parsefail(
8238585484eSchristos 	const char * cp,
8248585484eSchristos 	const char * ep)
8258585484eSchristos {
8268585484eSchristos 	return (cp == ep)
8278585484eSchristos 	    || (*ep && *ep != '#' && !isspace((u_char)*ep));
8288585484eSchristos }
8298585484eSchristos 
8308585484eSchristos /* [internal] reload the table limits around the given time stamp. This
8318585484eSchristos  * is where the real work is done when it comes to table lookup and
8328585484eSchristos  * evaluation. Some care has been taken to have correct code for dealing
8338585484eSchristos  * with boundary conditions and empty tables.
8348585484eSchristos  *
8358585484eSchristos  * In electric mode, transition and trip time are the same. In dumb
8368585484eSchristos  * mode, the difference of the TAI offsets must be taken into account
8378585484eSchristos  * and trip time and transition time become different. The difference
8388585484eSchristos  * becomes the warping distance when the trip time is reached.
8398585484eSchristos  */
8408585484eSchristos static void
8418585484eSchristos reload_limits(
8428585484eSchristos 	leap_table_t * pt,
8438585484eSchristos 	const vint64 * ts)
8448585484eSchristos {
8458585484eSchristos 	int idx;
8468585484eSchristos 
8478585484eSchristos 	/* Get full time and search the true lower bound. Use a
8488585484eSchristos 	 * simple loop here, since the number of entries does
8498585484eSchristos 	 * not warrant a binary search. This also works for an empty
8508585484eSchristos 	 * table, so there is no shortcut for that case.
8518585484eSchristos 	 */
8528585484eSchristos 	for (idx = 0; idx != pt->head.size; idx++)
8538585484eSchristos 		if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
8548585484eSchristos 			break;
8558585484eSchristos 
8568585484eSchristos 	/* get time limits with proper bound conditions. Note that the
8578585484eSchristos 	 * bounds of the table will be observed even if the table is
8588585484eSchristos 	 * empty -- no undefined condition must arise from this code.
8598585484eSchristos 	 */
8608585484eSchristos 	if (idx >= pt->head.size) {
8618585484eSchristos 		memset(&pt->head.ebase, 0x00, sizeof(vint64));
8628585484eSchristos 		pt->head.this_tai = pt->head.base_tai;
8638585484eSchristos 	} else {
8648585484eSchristos 		pt->head.ebase    = pt->info[idx].ttime;
8658585484eSchristos 		pt->head.this_tai = pt->info[idx].taiof;
8668585484eSchristos 	}
8678585484eSchristos 	if (--idx >= 0) {
8688585484eSchristos 		pt->head.next_tai = pt->info[idx].taiof;
8698585484eSchristos 		pt->head.dynls    = pt->info[idx].dynls;
8708585484eSchristos 		pt->head.ttime    = pt->info[idx].ttime;
8718585484eSchristos 
8728585484eSchristos 		if (_electric)
8738585484eSchristos 			pt->head.dtime = pt->head.ttime;
8748585484eSchristos 		else
8758585484eSchristos 			pt->head.dtime = addv64i32(
8768585484eSchristos 				&pt->head.ttime,
8778585484eSchristos 				pt->head.next_tai - pt->head.this_tai);
8788585484eSchristos 
8798585484eSchristos 		pt->head.stime = subv64u32(
8808585484eSchristos 			&pt->head.ttime, pt->info[idx].stime);
8818585484eSchristos 
8828585484eSchristos 	} else {
8838585484eSchristos 		memset(&pt->head.ttime, 0xFF, sizeof(vint64));
8848585484eSchristos 		pt->head.stime    = pt->head.ttime;
8858585484eSchristos 		pt->head.dtime    = pt->head.ttime;
8868585484eSchristos 		pt->head.next_tai = pt->head.this_tai;
8878585484eSchristos 		pt->head.dynls    = 0;
8888585484eSchristos 	}
8898585484eSchristos }
8908585484eSchristos 
8915d681e99Schristos /* [internal] fetch the leap era for a given time stamp.
8925d681e99Schristos  * This is a cut-down version the algorithm used to reload the table
8935d681e99Schristos  * limits, but it does not update any global state and provides just the
8945d681e99Schristos  * era information for a given time stamp.
8955d681e99Schristos  */
8965d681e99Schristos static void
8975d681e99Schristos fetch_leap_era(
8985d681e99Schristos 	leap_era_t         * into,
8995d681e99Schristos 	const leap_table_t * pt  ,
9005d681e99Schristos 	const vint64       * ts  )
9015d681e99Schristos {
9025d681e99Schristos 	int idx;
9035d681e99Schristos 
9045d681e99Schristos 	/* Simple search loop, also works with empty table. */
9055d681e99Schristos 	for (idx = 0; idx != pt->head.size; idx++)
9065d681e99Schristos 		if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
9075d681e99Schristos 			break;
9085d681e99Schristos 	/* fetch era data, keeping an eye on boundary conditions */
9095d681e99Schristos 	if (idx >= pt->head.size) {
9105d681e99Schristos 		memset(&into->ebase, 0x00, sizeof(vint64));
9115d681e99Schristos 		into->taiof = pt->head.base_tai;
9125d681e99Schristos 	} else {
9135d681e99Schristos 		into->ebase = pt->info[idx].ttime;
9145d681e99Schristos 		into->taiof = pt->info[idx].taiof;
9155d681e99Schristos 	}
9165d681e99Schristos 	if (--idx >= 0)
9175d681e99Schristos 		into->ttime = pt->info[idx].ttime;
9185d681e99Schristos 	else
9195d681e99Schristos 		memset(&into->ttime, 0xFF, sizeof(vint64));
9205d681e99Schristos }
9215d681e99Schristos 
9228585484eSchristos /* [internal] Take a time stamp and create a leap second frame for
9238585484eSchristos  * it. This will schedule a leap second for the beginning of the next
9248585484eSchristos  * month, midnight UTC. The 'insert' argument tells if a leap second is
9258585484eSchristos  * added (!=0) or removed (==0). We do not handle multiple inserts
9268585484eSchristos  * (yet?)
9278585484eSchristos  *
9288585484eSchristos  * Returns 1 if the insert worked, 0 otherwise. (It's not possible to
9298585484eSchristos  * insert a leap second into the current history -- only appending
9308585484eSchristos  * towards the future is allowed!)
9318585484eSchristos  */
9328585484eSchristos static int/*BOOL*/
9338585484eSchristos leapsec_add(
9348585484eSchristos 	leap_table_t*  pt    ,
9358585484eSchristos 	const vint64 * now64 ,
9368585484eSchristos 	int            insert)
9378585484eSchristos {
9387476e6e4Schristos 	vint64		ttime, starttime;
9398585484eSchristos 	struct calendar	fts;
9408585484eSchristos 	leap_info_t	li;
9418585484eSchristos 
9425d681e99Schristos 	/* Check against the table expiration and the latest available
9438585484eSchristos 	 * leap entry. Do not permit inserts, only appends, and only if
9448585484eSchristos 	 * the extend the table beyond the expiration!
9458585484eSchristos 	 */
9468585484eSchristos 	if (   ucmpv64(now64, &pt->head.expire) < 0
9478585484eSchristos 	    || (pt->head.size && ucmpv64(now64, &pt->info[0].ttime) <= 0)) {
9488585484eSchristos 		errno = ERANGE;
9498585484eSchristos 		return FALSE;
9508585484eSchristos 	}
9518585484eSchristos 
9528585484eSchristos 	ntpcal_ntp64_to_date(&fts, now64);
9538585484eSchristos 	/* To guard against dangling leap flags: do not accept leap
9548585484eSchristos 	 * second request on the 1st hour of the 1st day of the month.
9558585484eSchristos 	 */
9568585484eSchristos 	if (fts.monthday == 1 && fts.hour == 0) {
9578585484eSchristos 		errno = EINVAL;
9588585484eSchristos 		return FALSE;
9598585484eSchristos 	}
9608585484eSchristos 
9618585484eSchristos 	/* Ok, do the remaining calculations */
9628585484eSchristos 	fts.monthday = 1;
9638585484eSchristos 	fts.hour     = 0;
9648585484eSchristos 	fts.minute   = 0;
9658585484eSchristos 	fts.second   = 0;
9667476e6e4Schristos 	starttime = ntpcal_date_to_ntp64(&fts);
9678585484eSchristos 	fts.month++;
9688585484eSchristos 	ttime = ntpcal_date_to_ntp64(&fts);
9698585484eSchristos 
9708585484eSchristos 	li.ttime = ttime;
9717476e6e4Schristos 	li.stime = ttime.D_s.lo - starttime.D_s.lo;
9728585484eSchristos 	li.taiof = (pt->head.size ? pt->info[0].taiof : pt->head.base_tai)
9738585484eSchristos 	         + (insert ? 1 : -1);
9748585484eSchristos 	li.dynls = 1;
9758585484eSchristos 	return add_range(pt, &li);
9768585484eSchristos }
9778585484eSchristos 
9788585484eSchristos /* [internal] Given a time stamp for a leap insertion (the exact begin
9798585484eSchristos  * of the new leap era), create new leap frame and put it into the
9808585484eSchristos  * table. This is the work horse for reading a leap file and getting a
9818585484eSchristos  * leap second update via authenticated network packet.
9828585484eSchristos  */
9838585484eSchristos int/*BOOL*/
9848585484eSchristos leapsec_raw(
9858585484eSchristos 	leap_table_t * pt,
9868585484eSchristos 	const vint64 * ttime,
9878585484eSchristos 	int            taiof,
9888585484eSchristos 	int            dynls)
9898585484eSchristos {
9907476e6e4Schristos 	vint64		starttime;
9918585484eSchristos 	struct calendar	fts;
9928585484eSchristos 	leap_info_t	li;
9938585484eSchristos 
9945d681e99Schristos 	/* Check that we either extend the table or get a duplicate of
9955d681e99Schristos 	 * the latest entry. The latter is a benevolent overwrite with
9965d681e99Schristos 	 * identical data and could happen if we get an autokey message
9975d681e99Schristos 	 * that extends the lifetime of the current leapsecond table.
9985d681e99Schristos 	 * Otherwise paranoia rulez!
9995d681e99Schristos 	 */
10005d681e99Schristos 	if (pt->head.size) {
10015d681e99Schristos 		int cmp = ucmpv64(ttime, &pt->info[0].ttime);
10025d681e99Schristos 		if (cmp == 0)
10035d681e99Schristos 			cmp -= (taiof != pt->info[0].taiof);
10045d681e99Schristos 		if (cmp < 0) {
10058585484eSchristos 			errno = ERANGE;
10068585484eSchristos 			return FALSE;
10078585484eSchristos 		}
10085d681e99Schristos 		if (cmp == 0)
10095d681e99Schristos 			return TRUE;
10105d681e99Schristos 	}
10118585484eSchristos 
10128585484eSchristos 	ntpcal_ntp64_to_date(&fts, ttime);
10138585484eSchristos 	/* If this does not match the exact month start, bail out. */
10148585484eSchristos 	if (fts.monthday != 1 || fts.hour || fts.minute || fts.second) {
10158585484eSchristos 		errno = EINVAL;
10168585484eSchristos 		return FALSE;
10178585484eSchristos 	}
10188585484eSchristos 	fts.month--; /* was in range 1..12, no overflow here! */
10197476e6e4Schristos 	starttime = ntpcal_date_to_ntp64(&fts);
10208585484eSchristos 	li.ttime = *ttime;
10217476e6e4Schristos 	li.stime = ttime->D_s.lo - starttime.D_s.lo;
10228585484eSchristos 	li.taiof = (int16_t)taiof;
10238585484eSchristos 	li.dynls = (dynls != 0);
10248585484eSchristos 	return add_range(pt, &li);
10258585484eSchristos }
10268585484eSchristos 
10278585484eSchristos /* [internal] Do a wrap-around save range inclusion check.
10288585484eSchristos  * Returns TRUE if x in [lo,hi[ (intervall open on right side) with full
10298585484eSchristos  * handling of an overflow / wrap-around.
10308585484eSchristos  */
10318585484eSchristos static int/*BOOL*/
10328585484eSchristos betweenu32(
10338585484eSchristos 	uint32_t lo,
10348585484eSchristos 	uint32_t x,
10358585484eSchristos 	uint32_t hi)
10368585484eSchristos {
10378585484eSchristos 	int rc;
1038b8ecfcfeSchristos 
10398585484eSchristos 	if (lo <= hi)
10408585484eSchristos 		rc = (lo <= x) && (x < hi);
10418585484eSchristos 	else
10428585484eSchristos 		rc = (lo <= x) || (x < hi);
10438585484eSchristos 	return rc;
10448585484eSchristos }
10458585484eSchristos 
1046b8ecfcfeSchristos /* =====================================================================
1047b8ecfcfeSchristos  * validation stuff
1048b8ecfcfeSchristos  */
1049b8ecfcfeSchristos 
1050b8ecfcfeSchristos typedef struct {
1051b8ecfcfeSchristos 	unsigned char hv[ISC_SHA1_DIGESTLENGTH];
1052b8ecfcfeSchristos } sha1_digest;
1053b8ecfcfeSchristos 
1054b8ecfcfeSchristos /* [internal] parse a digest line to get the hash signature
1055b8ecfcfeSchristos  * The NIST code creating the hash writes them out as 5 hex integers
1056b8ecfcfeSchristos  * without leading zeros. This makes reading them back as hex-encoded
1057b8ecfcfeSchristos  * BLOB impossible, because there might be less than 40 hex digits.
1058b8ecfcfeSchristos  *
1059b8ecfcfeSchristos  * The solution is to read the values back as integers, and then do the
1060b8ecfcfeSchristos  * byte twiddle necessary to get it into an array of 20 chars. The
1061b8ecfcfeSchristos  * drawback is that it permits any acceptable number syntax provided by
1062b8ecfcfeSchristos  * 'scanf()' and 'strtoul()', including optional signs and '0x'
1063b8ecfcfeSchristos  * prefixes.
1064b8ecfcfeSchristos  */
1065b8ecfcfeSchristos static int/*BOOL*/
1066b8ecfcfeSchristos do_leap_hash(
1067b8ecfcfeSchristos 	sha1_digest * mac,
1068b8ecfcfeSchristos 	char const  * cp )
1069b8ecfcfeSchristos {
1070b8ecfcfeSchristos 	int wi, di, num, len;
1071b8ecfcfeSchristos 	unsigned long tmp[5];
1072b8ecfcfeSchristos 
1073b8ecfcfeSchristos 	memset(mac, 0, sizeof(*mac));
1074b8ecfcfeSchristos 	num = sscanf(cp, " %lx %lx %lx %lx %lx%n",
1075b8ecfcfeSchristos 		     &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
1076b8ecfcfeSchristos 		     &len);
1077b8ecfcfeSchristos 	if (num != 5 || cp[len] > ' ')
1078b8ecfcfeSchristos 		return FALSE;
1079b8ecfcfeSchristos 
1080b8ecfcfeSchristos 	/* now do the byte twiddle */
1081b8ecfcfeSchristos 	for (wi=0; wi < 5; ++wi)
1082b8ecfcfeSchristos 		for (di=3; di >= 0; --di) {
10837476e6e4Schristos 			mac->hv[wi*4 + di] =
10847476e6e4Schristos 				(unsigned char)(tmp[wi] & 0x0FF);
1085b8ecfcfeSchristos 			tmp[wi] >>= 8;
1086b8ecfcfeSchristos 		}
1087b8ecfcfeSchristos 	return TRUE;
1088b8ecfcfeSchristos }
1089b8ecfcfeSchristos 
1090b8ecfcfeSchristos /* [internal] add the digits of a data line to the hash, stopping at the
1091b8ecfcfeSchristos  * next hash ('#') character.
1092b8ecfcfeSchristos  */
1093b8ecfcfeSchristos static void
1094b8ecfcfeSchristos do_hash_data(
1095b8ecfcfeSchristos 	isc_sha1_t * mdctx,
1096b8ecfcfeSchristos 	char const * cp   )
1097b8ecfcfeSchristos {
1098b8ecfcfeSchristos 	unsigned char  text[32]; // must be power of two!
1099b8ecfcfeSchristos 	unsigned int   tlen =  0;
1100b8ecfcfeSchristos 	unsigned char  ch;
1101b8ecfcfeSchristos 
1102b8ecfcfeSchristos 	while ('\0' != (ch = *cp++) && '#' != ch)
1103b8ecfcfeSchristos 		if (isdigit(ch)) {
1104b8ecfcfeSchristos 			text[tlen++] = ch;
1105b8ecfcfeSchristos 			tlen &= (sizeof(text)-1);
1106b8ecfcfeSchristos 			if (0 == tlen)
1107b8ecfcfeSchristos 				isc_sha1_update(
1108b8ecfcfeSchristos 					mdctx, text, sizeof(text));
1109b8ecfcfeSchristos 		}
1110b8ecfcfeSchristos 
1111b8ecfcfeSchristos 	if (0 < tlen)
1112b8ecfcfeSchristos 		isc_sha1_update(mdctx, text, tlen);
1113b8ecfcfeSchristos }
1114b8ecfcfeSchristos 
1115b8ecfcfeSchristos /* given a reader and a reader arg, calculate and validate the the hash
1116b8ecfcfeSchristos  * signature of a NIST leap second file.
1117b8ecfcfeSchristos  */
1118b8ecfcfeSchristos int
1119b8ecfcfeSchristos leapsec_validate(
1120b8ecfcfeSchristos 	leapsec_reader func,
1121b8ecfcfeSchristos 	void *         farg)
1122b8ecfcfeSchristos {
1123b8ecfcfeSchristos 	isc_sha1_t     mdctx;
1124b8ecfcfeSchristos 	sha1_digest    rdig, ldig; /* remote / local digests */
1125b8ecfcfeSchristos 	char           line[50];
1126b8ecfcfeSchristos 	int            hlseen = -1;
1127b8ecfcfeSchristos 
1128b8ecfcfeSchristos 	isc_sha1_init(&mdctx);
1129b8ecfcfeSchristos 	while (get_line(func, farg, line, sizeof(line))) {
1130b8ecfcfeSchristos 		if (!strncmp(line, "#h", 2))
1131b8ecfcfeSchristos 			hlseen = do_leap_hash(&rdig, line+2);
1132b8ecfcfeSchristos 		else if (!strncmp(line, "#@", 2))
1133b8ecfcfeSchristos 			do_hash_data(&mdctx, line+2);
1134b8ecfcfeSchristos 		else if (!strncmp(line, "#$", 2))
1135b8ecfcfeSchristos 			do_hash_data(&mdctx, line+2);
1136ea66d795Schristos 		else if (isdigit((unsigned char)line[0]))
1137b8ecfcfeSchristos 			do_hash_data(&mdctx, line);
1138b8ecfcfeSchristos 	}
1139b8ecfcfeSchristos 	isc_sha1_final(&mdctx, ldig.hv);
1140b8ecfcfeSchristos 	isc_sha1_invalidate(&mdctx);
1141b8ecfcfeSchristos 
1142b8ecfcfeSchristos 	if (0 > hlseen)
1143b8ecfcfeSchristos 		return LSVALID_NOHASH;
1144b8ecfcfeSchristos 	if (0 == hlseen)
1145b8ecfcfeSchristos 		return LSVALID_BADFORMAT;
1146b8ecfcfeSchristos 	if (0 != memcmp(&rdig, &ldig, sizeof(sha1_digest)))
1147b8ecfcfeSchristos 		return LSVALID_BADHASH;
1148b8ecfcfeSchristos 	return LSVALID_GOODHASH;
1149b8ecfcfeSchristos }
1150b8ecfcfeSchristos 
1151b8ecfcfeSchristos /*
1152b8ecfcfeSchristos  * lstostr - prettyprint NTP seconds
1153b8ecfcfeSchristos  */
11545d681e99Schristos static const char *
11555d681e99Schristos lstostr(
1156b8ecfcfeSchristos 	const vint64 * ts)
1157b8ecfcfeSchristos {
1158b8ecfcfeSchristos 	char *		buf;
1159b8ecfcfeSchristos 	struct calendar tm;
1160b8ecfcfeSchristos 
1161b8ecfcfeSchristos 	LIB_GETBUF(buf);
11625d681e99Schristos 
11635d681e99Schristos 	if ( ! (ts->d_s.hi >= 0 && ntpcal_ntp64_to_date(&tm, ts) >= 0))
11645d681e99Schristos 		snprintf(buf, LIB_BUFLENGTH, "%s", "9999-12-31T23:59:59Z");
11655d681e99Schristos 	else
11665d681e99Schristos 		snprintf(buf, LIB_BUFLENGTH, "%04d-%02d-%02dT%02d:%02d:%02dZ",
1167b8ecfcfeSchristos 			tm.year, tm.month, tm.monthday,
11685d681e99Schristos 			tm.hour, tm.minute, tm.second);
11695d681e99Schristos 
1170b8ecfcfeSchristos 	return buf;
1171b8ecfcfeSchristos }
1172b8ecfcfeSchristos 
11735d681e99Schristos /* reset the global state for unit tests */
11745d681e99Schristos void
11755d681e99Schristos leapsec_ut_pristine(void)
11765d681e99Schristos {
11775d681e99Schristos 	memset(_ltab, 0, sizeof(_ltab));
11785d681e99Schristos 	_lptr     = NULL;
11795d681e99Schristos 	_electric = 0;
11805d681e99Schristos }
11815d681e99Schristos 
1182b8ecfcfeSchristos 
1183b8ecfcfeSchristos 
11848585484eSchristos /* -*- that's all folks! -*- */
1185