xref: /netbsd-src/external/bsd/ntp/dist/ntpd/ntp_leapsec.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
1 /*	$NetBSD: ntp_leapsec.c,v 1.8 2024/08/18 20:47:17 christos Exp $	*/
2 
3 /*
4  * ntp_leapsec.c - leap second processing for NTPD
5  *
6  * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
7  * The contents of 'html/copyright.html' apply.
8  * ----------------------------------------------------------------------
9  * This is an attempt to get the leap second handling into a dedicated
10  * module to make the somewhat convoluted logic testable.
11  */
12 
13 #include <config.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <ctype.h>
17 
18 #include "ntp.h"
19 #include "ntp_stdlib.h"
20 #include "ntp_calendar.h"
21 #include "ntp_leapsec.h"
22 #include "vint64ops.h"
23 
24 #include "isc/sha1.h"
25 
26 static const char * const logPrefix = "leapsecond file";
27 
28 /* ---------------------------------------------------------------------
29  * Our internal data structure
30  */
31 #define MAX_HIST 10	/* history of leap seconds */
32 
33 struct leap_info {
34 	vint64   ttime;	/* transition time (after the step, ntp scale) */
35 	uint32_t stime;	/* schedule limit (a month before transition)  */
36 	int16_t  taiof;	/* TAI offset on and after the transition      */
37 	uint8_t  dynls; /* dynamic: inserted on peer/clock request     */
38 };
39 typedef struct leap_info leap_info_t;
40 
41 struct leap_head {
42 	vint64   update; /* time of information update                 */
43 	vint64   expire; /* table expiration time                      */
44 	uint16_t size;	 /* number of infos in table	               */
45 	int16_t  base_tai;	/* total leaps before first entry      */
46 	int16_t  this_tai;	/* current TAI offset	               */
47 	int16_t  next_tai;	/* TAI offset after 'when'             */
48 	vint64   dtime;	 /* due time (current era end)                 */
49 	vint64   ttime;	 /* nominal transition time (next era start)   */
50 	vint64   stime;	 /* schedule time (when we take notice)        */
51 	vint64   ebase;	 /* base time of this leap era                 */
52 	uint8_t  dynls;	 /* next leap is dynamic (by peer request)     */
53 };
54 typedef struct leap_head leap_head_t;
55 
56 struct leap_table {
57 	leap_signature_t lsig;
58 	leap_head_t	 head;
59 	leap_info_t  	 info[MAX_HIST];
60 };
61 
62 /* Where we store our tables */
63 static leap_table_t _ltab[2], *_lptr;
64 static int/*BOOL*/  _electric;
65 
66 /* Forward decls of local helpers */
67 static int		add_range	(leap_table_t *, const leap_info_t *);
68 static char *		get_line	(leapsec_reader, void *, char *,
69 					 size_t);
70 static inline char *	skipws		(char *ptr);
71 static int		parsefail	(const char *cp, const char *ep);
72 static void		reload_limits	(leap_table_t *, const vint64 *);
73 static void		fetch_leap_era	(leap_era_t *, const leap_table_t *,
74 					 const vint64 *);
75 static int		betweenu32	(u_int32, u_int32, u_int32);
76 static void		reset_times	(leap_table_t *);
77 static int		leapsec_add	(leap_table_t *, const vint64 *, int);
78 static int		leapsec_raw	(leap_table_t *, const vint64 *, int,
79 					 int);
80 static const char *	lstostr		(const vint64 *ts);
81 
82 /* =====================================================================
83  * Get & Set the current leap table
84  */
85 
86 /* ------------------------------------------------------------------ */
87 leap_table_t *
88 leapsec_get_table(
89 	int alternate)
90 {
91 	leap_table_t *p1, *p2;
92 
93 	p1 = _lptr;
94 	if (p1 == &_ltab[0]) {
95 		p2 = &_ltab[1];
96 	} else if (p1 == &_ltab[1]) {
97 		p2 = &_ltab[0];
98 	} else {
99 		p1 = &_ltab[0];
100 		p2 = &_ltab[1];
101 		reset_times(p1);
102 		reset_times(p2);
103 		_lptr = p1;
104 	}
105 	if (alternate) {
106 		memcpy(p2, p1, sizeof(leap_table_t));
107 		p1 = p2;
108 	}
109 
110 	return p1;
111 }
112 
113 /* ------------------------------------------------------------------ */
114 int/*BOOL*/
115 leapsec_set_table(
116 	leap_table_t * pt)
117 {
118 	if (pt == &_ltab[0] || pt == &_ltab[1])
119 		_lptr = pt;
120 	return _lptr == pt;
121 }
122 
123 /* ------------------------------------------------------------------ */
124 int/*BOOL*/
125 leapsec_electric(
126 	int/*BOOL*/ on)
127 {
128 	int res = _electric;
129 	if (on < 0)
130 		return res;
131 
132 	_electric = (on != 0);
133 	if (_electric == res)
134 		return res;
135 
136 	if (_lptr == &_ltab[0] || _lptr == &_ltab[1])
137 		reset_times(_lptr);
138 
139 	return res;
140 }
141 
142 /* =====================================================================
143  * API functions that operate on tables
144  */
145 
146 /* ---------------------------------------------------------------------
147  * Clear all leap second data. Use it for init & cleanup
148  */
149 void
150 leapsec_clear(
151 	leap_table_t * pt)
152 {
153 	memset(&pt->lsig, 0, sizeof(pt->lsig));
154 	memset(&pt->head, 0, sizeof(pt->head));
155 	reset_times(pt);
156 }
157 
158 /* ---------------------------------------------------------------------
159  * Load a leap second file and check expiration on the go
160  */
161 int/*BOOL*/
162 leapsec_load(
163 	leap_table_t *	pt,
164 	leapsec_reader	func,
165 	void *		farg,
166 	int		use_build_limit
167 	)
168 {
169 	char		*cp, *ep, *endp, linebuf[50];
170 	vint64		ttime, limit;
171 	long		taiof;
172 	struct calendar	build;
173 
174 	leapsec_clear(pt);
175 	if (use_build_limit && ntpcal_get_build_date(&build)) {
176 		/* don't prune everything -- permit the last 10yrs
177 		 * before build.
178 		 */
179 		build.year -= 10;
180 		limit = ntpcal_date_to_ntp64(&build);
181 	} else {
182 		memset(&limit, 0, sizeof(limit));
183 	}
184 
185 	while (get_line(func, farg, linebuf, sizeof(linebuf))) {
186 		cp = linebuf;
187 		if (*cp == '#') {
188 			cp++;
189 			if (*cp == '@') {
190 				cp = skipws(cp+1);
191 				pt->head.expire = strtouv64(cp, &ep, 10);
192 				if (parsefail(cp, ep))
193 					goto fail_read;
194 				pt->lsig.etime = pt->head.expire.D_s.lo;
195 			} else if (*cp == '$') {
196 				cp = skipws(cp+1);
197 				pt->head.update = strtouv64(cp, &ep, 10);
198 				if (parsefail(cp, ep))
199 					goto fail_read;
200 			}
201 		} else if (isdigit((u_char)*cp)) {
202 			ttime = strtouv64(cp, &ep, 10);
203 			if (parsefail(cp, ep))
204 				goto fail_read;
205 			cp = skipws(ep);
206 			taiof = strtol(cp, &endp, 10);
207 			if (   parsefail(cp, endp)
208 			    || taiof > INT16_MAX || taiof < INT16_MIN)
209 				goto fail_read;
210 			if (ucmpv64(&ttime, &limit) >= 0) {
211 				if (!leapsec_raw(pt, &ttime,
212 						 taiof, FALSE))
213 					goto fail_insn;
214 			} else {
215 				pt->head.base_tai = (int16_t)taiof;
216 			}
217 			pt->lsig.ttime = ttime.D_s.lo;
218 			pt->lsig.taiof = (int16_t)taiof;
219 		}
220 	}
221 	return TRUE;
222 
223 fail_read:
224 	errno = EILSEQ;
225 fail_insn:
226 	leapsec_clear(pt);
227 	return FALSE;
228 }
229 
230 /* ---------------------------------------------------------------------
231  * Dump a table in human-readable format. Use 'fprintf' and a FILE
232  * pointer if you want to get it printed into a stream.
233  */
234 void
235 leapsec_dump(
236 	const leap_table_t * pt  ,
237 	leapsec_dumper       func,
238 	void *               farg)
239 {
240 	int             idx;
241 	vint64          ts;
242 	struct calendar atb, ttb;
243 
244 	ntpcal_ntp64_to_date(&ttb, &pt->head.expire);
245 	(*func)(farg, "leap table (%u entries) expires at %04u-%02u-%02u:\n",
246 		pt->head.size,
247 		ttb.year, ttb.month, ttb.monthday);
248 	idx = pt->head.size;
249 	while (idx-- != 0) {
250 		ts = pt->info[idx].ttime;
251 		ntpcal_ntp64_to_date(&ttb, &ts);
252 		ts = subv64u32(&ts, pt->info[idx].stime);
253 		ntpcal_ntp64_to_date(&atb, &ts);
254 
255 		(*func)(farg, "%04u-%02u-%02u [%c] (%04u-%02u-%02u) - %d\n",
256 			ttb.year, ttb.month, ttb.monthday,
257 			"-*"[pt->info[idx].dynls != 0],
258 			atb.year, atb.month, atb.monthday,
259 			pt->info[idx].taiof);
260 	}
261 }
262 
263 /* =====================================================================
264  * usecase driven API functions
265  */
266 
267 int/*BOOL*/
268 leapsec_query(
269 	leap_result_t * qr   ,
270 	uint32_t        ts32 ,
271 	const time_t *  pivot)
272 {
273 	leap_table_t *   pt;
274 	vint64           ts64, last, next;
275 	uint32_t         due32;
276 	int              fired;
277 
278 	/* preset things we use later on... */
279 	fired = FALSE;
280 	ts64  = ntpcal_ntp_to_ntp(ts32, pivot);
281 	pt    = leapsec_get_table(FALSE);
282 	memset(qr, 0, sizeof(leap_result_t));
283 
284 	if (ucmpv64(&ts64, &pt->head.ebase) < 0) {
285 		/* Most likely after leap frame reset. Could also be a
286 		 * backstep of the system clock. Anyway, get the new
287 		 * leap era frame.
288 		 */
289 		reload_limits(pt, &ts64);
290 	} else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) {
291 		/* Boundary crossed in forward direction. This might
292 		 * indicate a leap transition, so we prepare for that
293 		 * case.
294 		 *
295 		 * Some operations below are actually NOPs in electric
296 		 * mode, but having only one code path that works for
297 		 * both modes is easier to maintain.
298 		 *
299 		 * There's another quirk we must keep looking out for:
300 		 * If we just stepped the clock, the step might have
301 		 * crossed a leap boundary. As with backward steps, we
302 		 * do not want to raise the 'fired' event in that case.
303 		 * So we raise the 'fired' event only if we're close to
304 		 * the transition and just reload the limits otherwise.
305 		 */
306 		last = addv64i32(&pt->head.dtime, 3); /* get boundary */
307 		if (ucmpv64(&ts64, &last) >= 0) {
308 			/* that was likely a query after a step */
309 			reload_limits(pt, &ts64);
310 		} else {
311 			/* close enough for deeper examination */
312 			last = pt->head.ttime;
313 			qr->warped = (int16_t)(last.D_s.lo -
314 					       pt->head.dtime.D_s.lo);
315 			next = addv64i32(&ts64, qr->warped);
316 			reload_limits(pt, &next);
317 			fired = ucmpv64(&pt->head.ebase, &last) == 0;
318 			if (fired) {
319 				ts64 = next;
320 				ts32 = next.D_s.lo;
321 			} else {
322 				qr->warped = 0;
323 			}
324 		}
325 	}
326 
327 	qr->tai_offs = pt->head.this_tai;
328 	qr->ebase    = pt->head.ebase;
329 	qr->ttime    = pt->head.ttime;
330 
331 	/* If before the next scheduling alert, we're done. */
332 	if (ucmpv64(&ts64, &pt->head.stime) < 0)
333 		return fired;
334 
335 	/* now start to collect the remaining data */
336 	due32 = pt->head.dtime.D_s.lo;
337 
338 	qr->tai_diff  = pt->head.next_tai - pt->head.this_tai;
339 	qr->ddist     = due32 - ts32;
340 	qr->dynamic   = pt->head.dynls;
341 	qr->proximity = LSPROX_SCHEDULE;
342 
343 	/* if not in the last day before transition, we're done. */
344 	if (!betweenu32(due32 - SECSPERDAY, ts32, due32))
345 		return fired;
346 
347 	qr->proximity = LSPROX_ANNOUNCE;
348 	if (!betweenu32(due32 - 10, ts32, due32))
349 		return fired;
350 
351 	/* The last 10s before the transition. Prepare for action! */
352 	qr->proximity = LSPROX_ALERT;
353 	return fired;
354 }
355 
356 /* ------------------------------------------------------------------ */
357 int/*BOOL*/
358 leapsec_query_era(
359 	leap_era_t *   qr   ,
360 	uint32_t       ntpts,
361 	const time_t * pivot)
362 {
363 	const leap_table_t * pt;
364 	vint64               ts64;
365 
366 	pt   = leapsec_get_table(FALSE);
367 	ts64 = ntpcal_ntp_to_ntp(ntpts, pivot);
368 	fetch_leap_era(qr, pt, &ts64);
369 	return TRUE;
370 }
371 
372 /* ------------------------------------------------------------------ */
373 int/*BOOL*/
374 leapsec_frame(
375         leap_result_t *qr)
376 {
377 	const leap_table_t * pt;
378 
379         memset(qr, 0, sizeof(leap_result_t));
380 	pt = leapsec_get_table(FALSE);
381 
382 	qr->tai_offs = pt->head.this_tai;
383 	qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
384 	qr->ebase    = pt->head.ebase;
385 	qr->ttime    = pt->head.ttime;
386 	qr->dynamic  = pt->head.dynls;
387 
388 	return ucmpv64(&pt->head.ttime, &pt->head.stime) >= 0;
389 }
390 
391 /* ------------------------------------------------------------------ */
392 /* Reset the current leap frame */
393 void
394 leapsec_reset_frame(void)
395 {
396 	reset_times(leapsec_get_table(FALSE));
397 }
398 
399 /* ------------------------------------------------------------------ */
400 /* load a file from a FILE pointer. Note: If vhash is true, load
401  * only after successful signature check. The stream must be seekable
402  * or this will fail.
403  */
404 int/*BOOL*/
405 leapsec_load_stream(
406 	FILE       * ifp  ,
407 	const char * fname,
408 	int/*BOOL*/  logall,
409 	int/*BOOL*/  vhash)
410 {
411 	leap_table_t *pt;
412 	int           rcheck;
413 
414 	if (NULL == fname)
415 		fname = "<unknown>";
416 
417 	if (vhash) {
418 		rcheck = leapsec_validate((leapsec_reader)&getc, ifp);
419 		if (logall)
420 			switch (rcheck)
421 			{
422 			case LSVALID_GOODHASH:
423 				msyslog(LOG_NOTICE, "%s ('%s'): good hash signature",
424 					logPrefix, fname);
425 				break;
426 
427 			case LSVALID_NOHASH:
428 				msyslog(LOG_ERR, "%s ('%s'): no hash signature",
429 					logPrefix, fname);
430 				break;
431 			case LSVALID_BADHASH:
432 				msyslog(LOG_ERR, "%s ('%s'): signature mismatch",
433 					logPrefix, fname);
434 				break;
435 			case LSVALID_BADFORMAT:
436 				msyslog(LOG_ERR, "%s ('%s'): malformed hash signature",
437 					logPrefix, fname);
438 				break;
439 			default:
440 				msyslog(LOG_ERR, "%s ('%s'): unknown error code %d",
441 					logPrefix, fname, rcheck);
442 				break;
443 			}
444 		if (rcheck < 0)
445 			return FALSE;
446 		rewind(ifp);
447 	}
448 	pt = leapsec_get_table(TRUE);
449 	if (!leapsec_load(pt, (leapsec_reader)getc, ifp, TRUE)) {
450 		switch (errno) {
451 		case EINVAL:
452 			msyslog(LOG_ERR, "%s ('%s'): bad transition time",
453 				logPrefix, fname);
454 			break;
455 		case ERANGE:
456 			msyslog(LOG_ERR, "%s ('%s'): times not ascending",
457 				logPrefix, fname);
458 			break;
459 		default:
460 			msyslog(LOG_ERR, "%s ('%s'): parsing error",
461 				logPrefix, fname);
462 			break;
463 		}
464 		return FALSE;
465 	}
466 
467 	if (pt->head.size)
468 		msyslog(LOG_NOTICE, "%s ('%s'): loaded, expire=%s last=%s ofs=%d",
469 			logPrefix, fname, lstostr(&pt->head.expire),
470 			lstostr(&pt->info[0].ttime), pt->info[0].taiof);
471 	else
472 		msyslog(LOG_NOTICE,
473 			"%s ('%s'): loaded, expire=%s ofs=%d (no entries after build date)",
474 			logPrefix, fname, lstostr(&pt->head.expire),
475 			pt->head.base_tai);
476 
477 	return leapsec_set_table(pt);
478 }
479 
480 /* ------------------------------------------------------------------ */
481 int/*BOOL*/
482 leapsec_load_file(
483 	const char  * fname,
484 	struct stat * sb_old,
485 	int/*BOOL*/   force,
486 	int/*BOOL*/   logall,
487 	int/*BOOL*/   vhash)
488 {
489 	FILE       * fp;
490 	struct stat  sb_new;
491 	int          rc;
492 
493 	/* just do nothing if there is no leap file */
494 	if ( !(fname && *fname) )
495 		return FALSE;
496 
497 	/* try to stat the leapfile */
498 	if (0 != stat(fname, &sb_new)) {
499 		if (logall)
500 			msyslog(LOG_ERR, "%s ('%s'): stat failed: %m",
501 				logPrefix, fname);
502 		return FALSE;
503 	}
504 
505 	/* silently skip to postcheck if no new file found */
506 	if (NULL != sb_old) {
507 		if (!force
508 		 && sb_old->st_mtime == sb_new.st_mtime
509 		 && sb_old->st_ctime == sb_new.st_ctime
510 		   )
511 			return FALSE;
512 		*sb_old = sb_new;
513 	}
514 
515 	/* try to open the leap file, complain if that fails
516 	 *
517 	 * [perlinger@ntp.org]
518 	 * coverity raises a TOCTOU (time-of-check/time-of-use) issue
519 	 * here, which is not entirely helpful: While there is indeed a
520 	 * possible race condition between the 'stat()' call above and
521 	 * the 'fopen)' call below, I intentionally want to omit the
522 	 * overhead of opening the file and calling 'fstat()', because
523 	 * in most cases the file would have be to closed anyway without
524 	 * reading the contents.  I chose to disable the coverity
525 	 * warning instead.
526 	 *
527 	 * So unless someone comes up with a reasonable argument why
528 	 * this could be a real issue, I'll just try to silence coverity
529 	 * on that topic.
530 	 */
531 	/* coverity[toctou] */
532 	if ((fp = fopen(fname, "r")) == NULL) {
533 		if (logall)
534 			msyslog(LOG_ERR,
535 				"%s ('%s'): open failed: %m",
536 				logPrefix, fname);
537 		return FALSE;
538 	}
539 
540 	rc = leapsec_load_stream(fp, fname, logall, vhash);
541 	fclose(fp);
542 	return rc;
543 }
544 
545 /* ------------------------------------------------------------------ */
546 void
547 leapsec_getsig(
548 	leap_signature_t * psig)
549 {
550 	const leap_table_t * pt;
551 
552 	pt = leapsec_get_table(FALSE);
553 	memcpy(psig, &pt->lsig, sizeof(leap_signature_t));
554 }
555 
556 /* ------------------------------------------------------------------ */
557 int/*BOOL*/
558 leapsec_expired(
559 	uint32_t       when,
560 	const time_t * tpiv)
561 {
562 	const leap_table_t * pt;
563 	vint64 limit;
564 
565 	pt = leapsec_get_table(FALSE);
566 	limit = ntpcal_ntp_to_ntp(when, tpiv);
567 	return ucmpv64(&limit, &pt->head.expire) >= 0;
568 }
569 
570 /* ------------------------------------------------------------------ */
571 int32_t
572 leapsec_daystolive(
573 	uint32_t       when,
574 	const time_t * tpiv)
575 {
576 	const leap_table_t * pt;
577 	vint64 limit;
578 
579 	pt = leapsec_get_table(FALSE);
580 	limit = ntpcal_ntp_to_ntp(when, tpiv);
581 	limit = subv64(&pt->head.expire, &limit);
582 	return ntpcal_daysplit(&limit).hi;
583 }
584 
585 /* ------------------------------------------------------------------ */
586 #if 0 /* currently unused -- possibly revived later */
587 int/*BOOL*/
588 leapsec_add_fix(
589 	int            total,
590 	uint32_t       ttime,
591 	uint32_t       etime,
592 	const time_t * pivot)
593 {
594 	time_t         tpiv;
595 	leap_table_t * pt;
596 	vint64         tt64, et64;
597 
598 	if (pivot == NULL) {
599 		time(&tpiv);
600 		pivot = &tpiv;
601 	}
602 
603 	et64 = ntpcal_ntp_to_ntp(etime, pivot);
604 	tt64 = ntpcal_ntp_to_ntp(ttime, pivot);
605 	pt   = leapsec_get_table(TRUE);
606 
607 	if (   ucmpv64(&et64, &pt->head.expire) <= 0
608 	   || !leapsec_raw(pt, &tt64, total, FALSE) )
609 		return FALSE;
610 
611 	pt->lsig.etime = etime;
612 	pt->lsig.ttime = ttime;
613 	pt->lsig.taiof = (int16_t)total;
614 
615 	pt->head.expire = et64;
616 
617 	return leapsec_set_table(pt);
618 }
619 #endif
620 
621 /* ------------------------------------------------------------------ */
622 int/*BOOL*/
623 leapsec_add_dyn(
624 	int            insert,
625 	uint32_t       ntpnow,
626 	const time_t * pivot )
627 {
628 	leap_table_t * pt;
629 	vint64         now64;
630 
631 	pt = leapsec_get_table(TRUE);
632 	now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
633 	return (   leapsec_add(pt, &now64, (insert != 0))
634 		&& leapsec_set_table(pt));
635 }
636 
637 /* ------------------------------------------------------------------ */
638 int/*BOOL*/
639 leapsec_autokey_tai(
640 	int            tai_offset,
641 	uint32_t       ntpnow    ,
642 	const time_t * pivot     )
643 {
644 	leap_table_t * pt;
645 	leap_era_t     era;
646 	vint64         now64;
647 	int            idx;
648 
649 	(void)tai_offset;
650 	pt = leapsec_get_table(FALSE);
651 
652 	/* Bail out if the basic offset is not zero and the putative
653 	 * offset is bigger than 10s. That was in 1972 -- we don't want
654 	 * to go back that far!
655 	 */
656 	if (pt->head.base_tai != 0 || tai_offset < 10)
657 		return FALSE;
658 
659 	/* If there's already data in the table, check if an update is
660 	 * possible. Update is impossible if there are static entries
661 	 * (since this indicates a valid leapsecond file) or if we're
662 	 * too close to a leapsecond transition: We do not know on what
663 	 * side the transition the sender might have been, so we use a
664 	 * dead zone around the transition.
665 	 */
666 
667 	/* Check for static entries */
668 	for (idx = 0; idx != pt->head.size; idx++)
669 		if ( ! pt->info[idx].dynls)
670 			return FALSE;
671 
672 	/* get the fulll time stamp and leap era for it */
673 	now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
674 	fetch_leap_era(&era, pt, &now64);
675 
676 	/* check the limits with 20s dead band */
677 	era.ebase = addv64i32(&era.ebase,  20);
678 	if (ucmpv64(&now64, &era.ebase) < 0)
679 		return FALSE;
680 
681 	era.ttime = addv64i32(&era.ttime, -20);
682 	if (ucmpv64(&now64, &era.ttime) > 0)
683 		return FALSE;
684 
685 	/* Here we can proceed. Calculate the delta update. */
686 	tai_offset -= era.taiof;
687 
688 	/* Shift the header info offsets. */
689 	pt->head.base_tai += tai_offset;
690 	pt->head.this_tai += tai_offset;
691 	pt->head.next_tai += tai_offset;
692 
693 	/* Shift table entry offsets (if any) */
694 	for (idx = 0; idx != pt->head.size; idx++)
695 		pt->info[idx].taiof += tai_offset;
696 
697 	/* claim success... */
698 	return TRUE;
699 }
700 
701 
702 /* =====================================================================
703  * internal helpers
704  */
705 
706 /* [internal] Reset / init the time window in the leap processor to
707  * force reload on next query. Since a leap transition cannot take place
708  * at an odd second, the value chosen avoids spurious leap transition
709  * triggers. Making all three times equal forces a reload. Using the
710  * maximum value for unsigned 64 bits makes finding the next leap frame
711  * a bit easier.
712  */
713 static void
714 reset_times(
715 	leap_table_t * pt)
716 {
717 	memset(&pt->head.ebase, 0xFF, sizeof(vint64));
718 	pt->head.stime = pt->head.ebase;
719 	pt->head.ttime = pt->head.ebase;
720 	pt->head.dtime = pt->head.ebase;
721 }
722 
723 /* [internal] Add raw data to the table, removing old entries on the
724  * fly. This cannot fail currently.
725  */
726 static int/*BOOL*/
727 add_range(
728 	leap_table_t *      pt,
729 	const leap_info_t * pi)
730 {
731 	/* If the table is full, make room by throwing out the oldest
732 	 * entry. But remember the accumulated leap seconds!
733 	 *
734 	 * Setting the first entry is a bit tricky, too: Simply assuming
735 	 * it is an insertion is wrong if the first entry is a dynamic
736 	 * leap second removal. So we decide on the sign -- if the first
737 	 * entry has a negative offset, we assume that it is a leap
738 	 * second removal. In both cases the table base offset is set
739 	 * accordingly to reflect the decision.
740 	 *
741 	 * In practice starting with a removal can only happen if the
742 	 * first entry is a dynamic request without having a leap file
743 	 * for the history proper.
744 	 */
745 	if (pt->head.size == 0) {
746 		if (pi->taiof >= 0)
747 			pt->head.base_tai = pi->taiof - 1;
748 		else
749 			pt->head.base_tai = pi->taiof + 1;
750 	} else if (pt->head.size >= MAX_HIST) {
751 		pt->head.size     = MAX_HIST - 1;
752 		pt->head.base_tai = pt->info[pt->head.size].taiof;
753 	}
754 
755 	/* make room in lower end and insert item */
756 	memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info));
757 	pt->info[0] = *pi;
758 	pt->head.size++;
759 
760 	/* invalidate the cached limit data -- we might have news ;-)
761 	 *
762 	 * This blocks a spurious transition detection. OTOH, if you add
763 	 * a value after the last query before a leap transition was
764 	 * expected to occur, this transition trigger is lost. But we
765 	 * can probably live with that.
766 	 */
767 	reset_times(pt);
768 	return TRUE;
769 }
770 
771 /* [internal] given a reader function, read characters into a buffer
772  * until either EOL or EOF is reached. Makes sure that the buffer is
773  * always NUL terminated, but silently truncates excessive data. The
774  * EOL-marker ('\n') is *not* stored in the buffer.
775  *
776  * Returns the pointer to the buffer, unless EOF was reached when trying
777  * to read the first character of a line.
778  */
779 static char *
780 get_line(
781 	leapsec_reader func,
782 	void *         farg,
783 	char *         buff,
784 	size_t         size)
785 {
786 	int   ch;
787 	char *ptr;
788 
789 	/* if we cannot even store the delimiter, declare failure */
790 	if (buff == NULL || size == 0)
791 		return NULL;
792 
793 	ptr = buff;
794 	while (EOF != (ch = (*func)(farg)) && '\n' != ch)
795 		if (size > 1) {
796 			size--;
797 			*ptr++ = (char)ch;
798 		}
799 	/* discard trailing whitespace */
800 	while (ptr != buff && isspace((u_char)ptr[-1]))
801 		ptr--;
802 	*ptr = '\0';
803 	return (ptr == buff && ch == EOF) ? NULL : buff;
804 }
805 
806 /* [internal] skips whitespace characters from a character buffer. */
807 static inline char *
808 skipws(
809 	char *	ptr
810 	)
811 {
812 	while (isspace((u_char)*ptr)) {
813 		ptr++;
814 	}
815 	return ptr;
816 }
817 
818 /* [internal] check if a strtoXYZ ended at EOL or whitespace and
819  * converted something at all. Return TRUE if something went wrong.
820  */
821 static int/*BOOL*/
822 parsefail(
823 	const char * cp,
824 	const char * ep)
825 {
826 	return (cp == ep)
827 	    || (*ep && *ep != '#' && !isspace((u_char)*ep));
828 }
829 
830 /* [internal] reload the table limits around the given time stamp. This
831  * is where the real work is done when it comes to table lookup and
832  * evaluation. Some care has been taken to have correct code for dealing
833  * with boundary conditions and empty tables.
834  *
835  * In electric mode, transition and trip time are the same. In dumb
836  * mode, the difference of the TAI offsets must be taken into account
837  * and trip time and transition time become different. The difference
838  * becomes the warping distance when the trip time is reached.
839  */
840 static void
841 reload_limits(
842 	leap_table_t * pt,
843 	const vint64 * ts)
844 {
845 	int idx;
846 
847 	/* Get full time and search the true lower bound. Use a
848 	 * simple loop here, since the number of entries does
849 	 * not warrant a binary search. This also works for an empty
850 	 * table, so there is no shortcut for that case.
851 	 */
852 	for (idx = 0; idx != pt->head.size; idx++)
853 		if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
854 			break;
855 
856 	/* get time limits with proper bound conditions. Note that the
857 	 * bounds of the table will be observed even if the table is
858 	 * empty -- no undefined condition must arise from this code.
859 	 */
860 	if (idx >= pt->head.size) {
861 		memset(&pt->head.ebase, 0x00, sizeof(vint64));
862 		pt->head.this_tai = pt->head.base_tai;
863 	} else {
864 		pt->head.ebase    = pt->info[idx].ttime;
865 		pt->head.this_tai = pt->info[idx].taiof;
866 	}
867 	if (--idx >= 0) {
868 		pt->head.next_tai = pt->info[idx].taiof;
869 		pt->head.dynls    = pt->info[idx].dynls;
870 		pt->head.ttime    = pt->info[idx].ttime;
871 
872 		if (_electric)
873 			pt->head.dtime = pt->head.ttime;
874 		else
875 			pt->head.dtime = addv64i32(
876 				&pt->head.ttime,
877 				pt->head.next_tai - pt->head.this_tai);
878 
879 		pt->head.stime = subv64u32(
880 			&pt->head.ttime, pt->info[idx].stime);
881 
882 	} else {
883 		memset(&pt->head.ttime, 0xFF, sizeof(vint64));
884 		pt->head.stime    = pt->head.ttime;
885 		pt->head.dtime    = pt->head.ttime;
886 		pt->head.next_tai = pt->head.this_tai;
887 		pt->head.dynls    = 0;
888 	}
889 }
890 
891 /* [internal] fetch the leap era for a given time stamp.
892  * This is a cut-down version the algorithm used to reload the table
893  * limits, but it does not update any global state and provides just the
894  * era information for a given time stamp.
895  */
896 static void
897 fetch_leap_era(
898 	leap_era_t         * into,
899 	const leap_table_t * pt  ,
900 	const vint64       * ts  )
901 {
902 	int idx;
903 
904 	/* Simple search loop, also works with empty table. */
905 	for (idx = 0; idx != pt->head.size; idx++)
906 		if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
907 			break;
908 	/* fetch era data, keeping an eye on boundary conditions */
909 	if (idx >= pt->head.size) {
910 		memset(&into->ebase, 0x00, sizeof(vint64));
911 		into->taiof = pt->head.base_tai;
912 	} else {
913 		into->ebase = pt->info[idx].ttime;
914 		into->taiof = pt->info[idx].taiof;
915 	}
916 	if (--idx >= 0)
917 		into->ttime = pt->info[idx].ttime;
918 	else
919 		memset(&into->ttime, 0xFF, sizeof(vint64));
920 }
921 
922 /* [internal] Take a time stamp and create a leap second frame for
923  * it. This will schedule a leap second for the beginning of the next
924  * month, midnight UTC. The 'insert' argument tells if a leap second is
925  * added (!=0) or removed (==0). We do not handle multiple inserts
926  * (yet?)
927  *
928  * Returns 1 if the insert worked, 0 otherwise. (It's not possible to
929  * insert a leap second into the current history -- only appending
930  * towards the future is allowed!)
931  */
932 static int/*BOOL*/
933 leapsec_add(
934 	leap_table_t*  pt    ,
935 	const vint64 * now64 ,
936 	int            insert)
937 {
938 	vint64		ttime, starttime;
939 	struct calendar	fts;
940 	leap_info_t	li;
941 
942 	/* Check against the table expiration and the latest available
943 	 * leap entry. Do not permit inserts, only appends, and only if
944 	 * the extend the table beyond the expiration!
945 	 */
946 	if (   ucmpv64(now64, &pt->head.expire) < 0
947 	    || (pt->head.size && ucmpv64(now64, &pt->info[0].ttime) <= 0)) {
948 		errno = ERANGE;
949 		return FALSE;
950 	}
951 
952 	ntpcal_ntp64_to_date(&fts, now64);
953 	/* To guard against dangling leap flags: do not accept leap
954 	 * second request on the 1st hour of the 1st day of the month.
955 	 */
956 	if (fts.monthday == 1 && fts.hour == 0) {
957 		errno = EINVAL;
958 		return FALSE;
959 	}
960 
961 	/* Ok, do the remaining calculations */
962 	fts.monthday = 1;
963 	fts.hour     = 0;
964 	fts.minute   = 0;
965 	fts.second   = 0;
966 	starttime = ntpcal_date_to_ntp64(&fts);
967 	fts.month++;
968 	ttime = ntpcal_date_to_ntp64(&fts);
969 
970 	li.ttime = ttime;
971 	li.stime = ttime.D_s.lo - starttime.D_s.lo;
972 	li.taiof = (pt->head.size ? pt->info[0].taiof : pt->head.base_tai)
973 	         + (insert ? 1 : -1);
974 	li.dynls = 1;
975 	return add_range(pt, &li);
976 }
977 
978 /* [internal] Given a time stamp for a leap insertion (the exact begin
979  * of the new leap era), create new leap frame and put it into the
980  * table. This is the work horse for reading a leap file and getting a
981  * leap second update via authenticated network packet.
982  */
983 int/*BOOL*/
984 leapsec_raw(
985 	leap_table_t * pt,
986 	const vint64 * ttime,
987 	int            taiof,
988 	int            dynls)
989 {
990 	vint64		starttime;
991 	struct calendar	fts;
992 	leap_info_t	li;
993 
994 	/* Check that we either extend the table or get a duplicate of
995 	 * the latest entry. The latter is a benevolent overwrite with
996 	 * identical data and could happen if we get an autokey message
997 	 * that extends the lifetime of the current leapsecond table.
998 	 * Otherwise paranoia rulez!
999 	 */
1000 	if (pt->head.size) {
1001 		int cmp = ucmpv64(ttime, &pt->info[0].ttime);
1002 		if (cmp == 0)
1003 			cmp -= (taiof != pt->info[0].taiof);
1004 		if (cmp < 0) {
1005 			errno = ERANGE;
1006 			return FALSE;
1007 		}
1008 		if (cmp == 0)
1009 			return TRUE;
1010 	}
1011 
1012 	ntpcal_ntp64_to_date(&fts, ttime);
1013 	/* If this does not match the exact month start, bail out. */
1014 	if (fts.monthday != 1 || fts.hour || fts.minute || fts.second) {
1015 		errno = EINVAL;
1016 		return FALSE;
1017 	}
1018 	fts.month--; /* was in range 1..12, no overflow here! */
1019 	starttime = ntpcal_date_to_ntp64(&fts);
1020 	li.ttime = *ttime;
1021 	li.stime = ttime->D_s.lo - starttime.D_s.lo;
1022 	li.taiof = (int16_t)taiof;
1023 	li.dynls = (dynls != 0);
1024 	return add_range(pt, &li);
1025 }
1026 
1027 /* [internal] Do a wrap-around save range inclusion check.
1028  * Returns TRUE if x in [lo,hi[ (intervall open on right side) with full
1029  * handling of an overflow / wrap-around.
1030  */
1031 static int/*BOOL*/
1032 betweenu32(
1033 	uint32_t lo,
1034 	uint32_t x,
1035 	uint32_t hi)
1036 {
1037 	int rc;
1038 
1039 	if (lo <= hi)
1040 		rc = (lo <= x) && (x < hi);
1041 	else
1042 		rc = (lo <= x) || (x < hi);
1043 	return rc;
1044 }
1045 
1046 /* =====================================================================
1047  * validation stuff
1048  */
1049 
1050 typedef struct {
1051 	unsigned char hv[ISC_SHA1_DIGESTLENGTH];
1052 } sha1_digest;
1053 
1054 /* [internal] parse a digest line to get the hash signature
1055  * The NIST code creating the hash writes them out as 5 hex integers
1056  * without leading zeros. This makes reading them back as hex-encoded
1057  * BLOB impossible, because there might be less than 40 hex digits.
1058  *
1059  * The solution is to read the values back as integers, and then do the
1060  * byte twiddle necessary to get it into an array of 20 chars. The
1061  * drawback is that it permits any acceptable number syntax provided by
1062  * 'scanf()' and 'strtoul()', including optional signs and '0x'
1063  * prefixes.
1064  */
1065 static int/*BOOL*/
1066 do_leap_hash(
1067 	sha1_digest * mac,
1068 	char const  * cp )
1069 {
1070 	int wi, di, num, len;
1071 	unsigned long tmp[5];
1072 
1073 	memset(mac, 0, sizeof(*mac));
1074 	num = sscanf(cp, " %lx %lx %lx %lx %lx%n",
1075 		     &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
1076 		     &len);
1077 	if (num != 5 || cp[len] > ' ')
1078 		return FALSE;
1079 
1080 	/* now do the byte twiddle */
1081 	for (wi=0; wi < 5; ++wi)
1082 		for (di=3; di >= 0; --di) {
1083 			mac->hv[wi*4 + di] =
1084 				(unsigned char)(tmp[wi] & 0x0FF);
1085 			tmp[wi] >>= 8;
1086 		}
1087 	return TRUE;
1088 }
1089 
1090 /* [internal] add the digits of a data line to the hash, stopping at the
1091  * next hash ('#') character.
1092  */
1093 static void
1094 do_hash_data(
1095 	isc_sha1_t * mdctx,
1096 	char const * cp   )
1097 {
1098 	unsigned char  text[32]; // must be power of two!
1099 	unsigned int   tlen =  0;
1100 	unsigned char  ch;
1101 
1102 	while ('\0' != (ch = *cp++) && '#' != ch)
1103 		if (isdigit(ch)) {
1104 			text[tlen++] = ch;
1105 			tlen &= (sizeof(text)-1);
1106 			if (0 == tlen)
1107 				isc_sha1_update(
1108 					mdctx, text, sizeof(text));
1109 		}
1110 
1111 	if (0 < tlen)
1112 		isc_sha1_update(mdctx, text, tlen);
1113 }
1114 
1115 /* given a reader and a reader arg, calculate and validate the the hash
1116  * signature of a NIST leap second file.
1117  */
1118 int
1119 leapsec_validate(
1120 	leapsec_reader func,
1121 	void *         farg)
1122 {
1123 	isc_sha1_t     mdctx;
1124 	sha1_digest    rdig, ldig; /* remote / local digests */
1125 	char           line[50];
1126 	int            hlseen = -1;
1127 
1128 	isc_sha1_init(&mdctx);
1129 	while (get_line(func, farg, line, sizeof(line))) {
1130 		if (!strncmp(line, "#h", 2))
1131 			hlseen = do_leap_hash(&rdig, line+2);
1132 		else if (!strncmp(line, "#@", 2))
1133 			do_hash_data(&mdctx, line+2);
1134 		else if (!strncmp(line, "#$", 2))
1135 			do_hash_data(&mdctx, line+2);
1136 		else if (isdigit((unsigned char)line[0]))
1137 			do_hash_data(&mdctx, line);
1138 	}
1139 	isc_sha1_final(&mdctx, ldig.hv);
1140 	isc_sha1_invalidate(&mdctx);
1141 
1142 	if (0 > hlseen)
1143 		return LSVALID_NOHASH;
1144 	if (0 == hlseen)
1145 		return LSVALID_BADFORMAT;
1146 	if (0 != memcmp(&rdig, &ldig, sizeof(sha1_digest)))
1147 		return LSVALID_BADHASH;
1148 	return LSVALID_GOODHASH;
1149 }
1150 
1151 /*
1152  * lstostr - prettyprint NTP seconds
1153  */
1154 static const char *
1155 lstostr(
1156 	const vint64 * ts)
1157 {
1158 	char *		buf;
1159 	struct calendar tm;
1160 
1161 	LIB_GETBUF(buf);
1162 
1163 	if ( ! (ts->d_s.hi >= 0 && ntpcal_ntp64_to_date(&tm, ts) >= 0))
1164 		snprintf(buf, LIB_BUFLENGTH, "%s", "9999-12-31T23:59:59Z");
1165 	else
1166 		snprintf(buf, LIB_BUFLENGTH, "%04d-%02d-%02dT%02d:%02d:%02dZ",
1167 			tm.year, tm.month, tm.monthday,
1168 			tm.hour, tm.minute, tm.second);
1169 
1170 	return buf;
1171 }
1172 
1173 /* reset the global state for unit tests */
1174 void
1175 leapsec_ut_pristine(void)
1176 {
1177 	memset(_ltab, 0, sizeof(_ltab));
1178 	_lptr     = NULL;
1179 	_electric = 0;
1180 }
1181 
1182 
1183 
1184 /* -*- that's all folks! -*- */
1185