Coverage Report

Created: 2026-02-26 06:20

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