/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! -*- */ |