3 # Copyright (C) 2014 Timothe Litt litt at acm dot org
4 # Modified 20180105 Sanjeev Gupta ghane0@gmail.com
6 # SPDX-License-Identifier: BSD-2-Clause
8 # Bugfixes and improvements would be appreciated by the author.
10 # leap-seconds file manager/updater
13 # wget sed, tr, shasum/sha1sum, logger
15 # This file needs to remain free of bash-isms. Make sure it
16 # runs on plain POSIX shell.
18 # GNU sed has many extensions not on macOS and FreeBSD. If
19 # you change near sed, test on FreeBSD.
21 # ########## Default configuration ##########
23 # Where to get the file
24 LEAPSRC
="https://data.iana.org/time-zones/tzdb/leap-seconds.list"
26 # How many times to try to download new file
30 # Where to find ntp config file
33 # How long before expiration to get updated file
36 # How to restart NTP - older NTP: service ntpd? try-restart | condrestart
37 # Recent NTP checks for new file daily, so there's nothing to do
40 # Where to put temporary copy before it's validated
41 TMPFILE
="/tmp/leap-seconds.$$.tmp"
45 # ###########################################
47 # Places to look for commands. Allows for CRON having path to
48 # old utilities on embedded systems
50 PATHLIST
="/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
52 REQUIREDCMDS
=" wget logger tr sed"
58 Usage: $SELF [options] [leapfile]
60 Verifies and if necessary, updates leap-second definition file
62 All arguments are optional: Default (or current value) shown:
63 -s Specify the URL of the master copy to download
68 Prefer IPv4 or IPv6 (as specified) addresses, but use either
69 -e Specify how long before expiration the file is to be refreshed
70 Units are required, e.g. "-e 60 days" Note that larger values
71 imply more frequent refreshes.
73 -f Specify location of ntp.conf (used to make sure leapfile directive is
74 present and to default leapfile)
76 -F Force update even if current file is OK and not close to expiring.
77 -c Command to restart NTP after installing a new file
78 <none> - ntpd checks file daily
79 -r Specify number of times to retry on get failure
81 -i Specify number of minutes between retries
83 -l Use syslog for output (Implied if CRONJOB is set)
84 -L Don't use syslog for output
85 -P Specify the syslog facility for logging
87 -t Name of temporary file used in validation
88 -q Only report errors to stdout
90 -z Specify path for utilities
92 -Z Only use system path
93 -V Output version information and exit
95 $SELF will validate the file currently on the local system
97 Ordinarily, the file is found using the "leapfile" directive in $NTPCONF.
98 However, an alternate location can be specified on the command line.
100 If the file does not exist, is not valid, has expired, or is expiring soon,
101 a new copy will be downloaded. If the new copy validates, it is installed and
102 NTP is (optionally) restarted.
104 If the current file is acceptable, no download or restart occurs.
106 -c can also be used to invoke another script to perform administrative
107 functions, e.g. to copy the file to other local systems.
109 This can be run as a cron job. As the file is rarely updated, and leap
110 seconds are announced at least one month in advance (usually longer), it
111 need not be run more frequently than about once every three weeks.
113 For cron-friendly behavior, define CRONJOB=1 in the crontab.
115 This script depends on: sha1sum/shasum $REQUIREDCMDS
117 Version @NTPSEC_VERSION_EXTENDED@
124 ntpleapfetch ntpsec-@NTPSEC_VERSION_EXTENDED@
129 # Default: Use syslog for logging if running under cron
133 if [ "$1" = "--help" ]; then
138 if [ "$1" = "--version" ]; then
145 while getopts 46p
:P
:s
:e
:f
:Fc
:r
:i
:lLt
:hqvz
:ZV opt
; do
154 if [ "$OPTARG" = '4' -o "$OPTARG" = '6' ]; then
155 PREFER
="--prefer-family=IPv$OPTARG"
157 echo "Invalid -p $OPTARG" >&2
215 echo "$SELF -h for usage" >&2
222 export PATH
="$PATHLIST$PATH"
224 # Add to path to deal with embedded systems
226 for P
in $REQUIREDCMDS ; do
227 if >/dev
/null
2>&1 command -v "$P" ; then
230 [ "$P" = "logger" ] && continue
231 echo "FATAL: missing $P command, please install"
235 # find sha1sum or shasum
236 if >/dev
/null
2>&1 command -v "sha1sum" ; then
238 elif >/dev
/null
2>&1 command -v "shasum" ; then
241 echo "FATAL: Can not find sha1sum or shasum command, please install"
247 if ! LOGGER
="`2>/dev/null command -v logger`" ; then
252 # "priority" "message"
254 # Stdout unless syslog specified or logger isn't available
256 if [ -z "$SYSLOG" -o -z "$LOGGER" ]; then
257 if [ -n "$QUIET" -a \
( "$1" = "info" -o "$1" = "notice" -o "$1" = "debug" \
) ]; then
260 echo "`echo \"$1\" | tr a-z A-Z`: $2"
264 # Also log to stdout if cron job && notice or higher
266 if [ -n "$CRONJOB" -a \
( "$1" != "info" \
) -a \
( "$1" != "debug" \
) ] ||
[ -n "$VERBOSE" ]; then
269 $LOGGER $S -t "$SELF[$$]" -p "$LOGFAC.$1" "$2"
273 INTERVAL
=$
(( $INTERVAL *1 ))
275 # Validate a leap-seconds file checksum
277 # File format: (full description in files)
278 # # marks comments, except:
279 # #$ number : the NTP date of the last update
280 # #@ number : the NTP date that the file expires
281 # Date (seconds since 1900) leaps : leaps is the # of seconds to add for times >= Date
282 # Date lines have comments.
283 # #h hex hex hex hex hex is the SHA-1 checksum of the data & dates, excluding whitespace w/o leading zeroes
287 if [ ! -f "$1" ]; then
291 # Remove comments, except those that are markers for last update, expires and hash
293 local RAW
="`sed -ne'/^#[\$@h]/p;/^[^#]/s/.#.*$//p' $1 `"
295 # Extract just the data, removing all whitespace
297 local DATA
="`echo \"$RAW\" | sed -e'/^#h/d' -e's/^#[\$@]//g' | tr -d '[:space:]'`"
299 # Compute the SHA-1 hash of the data, removing the marker and filename
300 # Computed in binary mode, which shouldn't matter since whitespace
302 # shasum/sha1sum comes in several flavors;
303 # a portable one is available in Perl (with Digest::SHA)
305 local DSHA1
="`printf \"$DATA\" | $SHASUM | sed -e's/[? *].*$//'`"
307 # Extract the file's hash. Restore any leading zeroes in hash segments.
309 # The sed [] includes a tab (\t) and space; #h is followed by a tab and space
310 # or maybe a space and a tab. remove the tab, wherever it may be. Add some 0x
311 # so it can be run through printf to restore missing leading zeros.
313 local FSHA1
="`grep '^#h' $1 | tr -d '[:cntrl:]' | sed -e's/^#h[ \t]*/0x/' -e's/ / 0x/g'`"
314 FSHA1
=`printf '%08x%08x%08x%08x%08x' $FSHA1`
316 if [ -n "$FSHA1" -a \
( "$FSHA1" = "$DSHA1" \
) ]; then
318 log
"info" "Checksum of $1 validated"
321 log
"error" "Checksum of $1 is invalid:"
322 [ -z "$FSHA1" ] && FSHA1
="(no checksum record found in file)"
323 log
"error" "EXPECTED: $FSHA1"
324 log
"error" "COMPUTED: $DSHA1"
328 # Check the expiration date, converting NTP epoch to Unix epoch used by date
330 EXPIRES
="`echo \"$RAW\" | sed -e'/^#@/!d' -e's/^#@//' | tr -d '[:space:]'`"
331 EXPIRES
="$(($EXPIRES - 2208988800 ))"
333 if [ $EXPIRES -lt `date -u +%s` ]; then
334 log
"notice" "File expired on `date -u -d \"Jan 1, 1970 00:00:00 +0000 + $EXPIRES seconds\"`"
342 if ! [ -f "$NTPCONF" ]; then
343 log
"critical" "Missing ntp configuration $NTPCONF"
347 # Parse ntp.conf for leapfile directive
348 LEAPFILE
="`grep -x 'leapfile *.*' $NTPCONF | grep -o ' [^ ]*.*'`"
349 if [ -z "$LEAPFILE" ]; then
350 log
"warning" "$NTPCONF does not specify a leapfile"
353 # Allow placing the file someplace else - testing
356 if [ "$1" != "$LEAPFILE" ]; then
357 log
"notice" "Requested install to $1, but $NTPCONF specifies $LEAPFILE"
361 if [ -z "$LEAPFILE" ]; then
362 log
"error" "No leapfile in ntp.conf or on the command line."
366 # Verify the current file
367 # If it is missing, doesn't validate or expired
368 # Or is expiring soon
371 if [ -n "$FORCE" ] ||
! verifySHA1
$LEAPFILE "$VERBOSE" ||
[ $EXPIRES -lt `date -d "NOW + $PREFETCH" +%s` ] ; then
375 if [ -n "$VERBOSE" ]; then
376 log
"info" "Attempting download from $LEAPSRC, try $TRY.."
378 if wget
-T 10 $PROTO $PREFER -o ${TMPFILE}.log
$LEAPSRC -O $TMPFILE ; then
379 log
"info" "Download of $LEAPSRC succeeded"
380 if [ -n "$VERBOSE" ]; then
384 if ! verifySHA1
$TMPFILE "$VERBOSE" ; then
385 # There is no point in retrying, as the file on the server is almost
388 log
"warning" "Downloaded file $TMPFILE rejected -- saved for diagnosis"
395 # Set correct permissions on temporary file
398 if [ ! -f $LEAPFILE ]; then
399 log
"notice" "$LEAPFILE was missing, creating new copy - check permissions"
401 # Can't copy permissions from old file,
402 # copy from NTPCONF instead
405 chmod --reference=$REFFILE $TMPFILE > /dev
/null
2>&1
406 if [ $?
-ne 0 ] ; then
407 # the above chmod fails on macOS and BSD, just force it
410 chown
--reference=$REFFILE $TMPFILE > /dev
/null
2>&1
411 if [ $?
-ne 0 ] ; then
412 # the above chown fails on macOS and BSD, just force it
413 chown root
:wheel
$TMPFILE
415 ( command -v selinuxenabled
&& selinuxenabled
&& command -v chcon
) >/dev
/null
2>&1
416 if [ $?
-eq 0 ] ; then
417 chcon
--reference $REFFILE $TMPFILE
420 # Replace current file with validated new one
422 if mv -f $TMPFILE $LEAPFILE ; then
423 log
"notice" "Installed new $LEAPFILE from $LEAPSRC"
425 log
"error" "Install $TMPFILE => $LEAPFILE failed -- saved for diagnosis"
429 # Restart NTP (or whatever else is specified)
431 if [ -n "$RESTART" ]; then
432 if [ -n "$VERBOSE" ]; then
433 log
"info" "Attempting restart action: $RESTART"
435 R
="$( 2>&1 $RESTART )"
436 if [ $?
-eq 0 ]; then
437 log
"notice" "Restart action succeeded"
438 if [ -n "$VERBOSE" -a -n "$R" ]; then
442 log
"error" "Restart action failed"
452 # Failed to download. See about trying again
455 if [ $TRY -ge $MAXTRIES ]; then
458 if [ -n "$VERBOSE" ]; then
460 log
"info" "Waiting $INTERVAL minutes before retrying..."
462 sleep $
(( $INTERVAL * 60))
465 # Failed and out of retries
467 log
"warning" "Download from $LEAPSRC failed after $TRY attempts"
468 if [ -f ${TMPFILE}.log
]; then
470 rm -f ${TMPFILE}.log
$TMPFILE
474 log
"info" "Not time to replace $LEAPFILE"