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