]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/ntp/scripts/update-leap/update-leap.sh
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / ntp / scripts / update-leap / update-leap.sh
1 #!/bin/bash
2
3 # Copyright (C) 2014 Timothe Litt litt at acm dot org
4
5 # This script may be freely copied, used and modified providing that
6 # this notice and the copyright statement are included in all copies
7 # and derivative works.  No warranty is offered, and use is entirely at
8 # your own risk.  Bugfixes and improvements would be appreciated by the
9 # author.
10
11 VERSION="1.003"
12
13 # leap-seconds file manager/updater
14
15 # Depends on:
16 #  wget sed, tr, shasum, logger
17
18 # ########## Default configuration ##########
19 #
20 # Where to get the file
21 LEAPSRC="ftp://time.nist.gov/pub/leap-seconds.list"
22
23 # How many times to try to download new file
24 MAXTRIES=6
25 INTERVAL=10
26
27 # Where to find ntp config file
28 NTPCONF=/etc/ntp.conf
29
30 # How long before expiration to get updated file
31 PREFETCH="60 days"
32
33 # How to restart NTP - older NTP: service ntpd? try-restart | condrestart
34 # Recent NTP checks for new file daily, so there's nothing to do
35 RESTART=
36
37 # Where to put temporary copy before it's validated
38 TMPFILE="/tmp/leap-seconds.$$.tmp"
39
40 # Syslog facility
41 LOGFAC=daemon
42 # ###########################################
43
44 # Places to look for commands.  Allows for CRON having path to
45 # old utilities on embedded systems
46
47 PATHLIST="/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
48
49 REQUIREDCMDS=" wget logger tr sed shasum"
50
51 SELF="`basename $0`"
52
53 function displayHelp {
54             cat <<EOF
55 Usage: $SELF [options] [leapfile]
56
57 Verifies and if necessary, updates leap-second definition file
58
59 All arguments are optional:  Default (or current value) shown:
60     -s    Specify the URL of the master copy to download
61           $LEAPSRC
62     -4    Use only IPv4
63     -6    Use only IPv6
64     -p 4|6
65           Prefer IPv4 or IPv6 (as specified) addresses, but use either
66     -d    Specify the filename on the local system
67           $LEAPFILE
68     -e    Specify how long before expiration the file is to be refreshed
69           Units are required, e.g. "-e 60 days"  Note that larger values
70           imply more frequent refreshes.
71           "$PREFETCH"
72     -f    Specify location of ntp.conf (used to make sure leapfile directive is
73           present and to default  leapfile)
74           $NTPCONF
75     -F    Force update even if current file is OK and not close to expiring.
76     -c    Command to restart NTP after installing a new file
77           <none> - ntpd checks file daily
78     -r    Specify number of times to retry on get failure
79           $MAXTRIES
80     -i    Specify number of minutes between retries
81           $INTERVAL
82     -l    Use syslog for output (Implied if CRONJOB is set)
83     -L    Don't use syslog for output
84     -P    Specify the syslog facility for logging
85           $LOGFAC
86     -t    Name of temporary file used in validation
87           $TMPFILE
88     -q    Only report errors to stdout
89     -v    Verbose output
90     -z    Specify path for utilities
91           $PATHLIST
92     -Z    Only use system path
93
94 $SELF will validate the file currently on the local system
95
96 Ordinarily, the file is found using the "leapfile" directive in $NTPCONF.
97 However, an alternate location can be specified on the command line.
98
99 If the file does not exist, is not valid, has expired, or is expiring soon,
100 a new copy will be downloaded.  If the new copy validates, it is installed and
101 NTP is (optionally) restarted.
102
103 If the current file is acceptable, no download or restart occurs.
104
105 -c can also be used to invoke another script to perform administrative
106 functions, e.g. to copy the file to other local systems.
107
108 This can be run as a cron job.  As the file is rarely updated, and leap
109 seconds are announced at least one month in advance (usually longer), it
110 need not be run more frequently than about once every three weeks.
111
112 For cron-friendly behavior, define CRONJOB=1 in the crontab.
113
114 This script depends on$REQUIREDCMDS
115
116 Version $VERSION
117 EOF
118    return 0
119 }
120
121 # Default: Use syslog for logging if running under cron
122
123 SYSLOG="$CRONJOB"
124
125 if [ "$1" = "--help" ]; then
126     displayHelp
127     exit 0
128 fi
129
130 # Parse options
131
132 while getopts 46p:P:s:e:f:Fc:r:i:lLt:hqvz:Z opt; do
133     case $opt in
134         4)
135             PROTO="-4"
136             ;;
137         6)
138             PROTO="-6"
139             ;;
140         p)
141             if [ "$OPTARG" = '4' -o "$OPTARG" = '6' ]; then
142                 PREFER="--prefer-family=IPv$OPTARG"
143             else
144                 echo "Invalid -p $OPTARG" >&2
145                 exit 1;
146             fi
147             ;;
148         P)
149             LOGFAC="$OPTARG"
150             ;;
151         s)
152             LEAPSRC="$OPTARG"
153             ;;
154         e)
155             PREFETCH="$OPTARG"
156             ;;
157         f)
158             NTPCONF="$OPTARG"
159             ;;
160         F)
161             FORCE="Y"
162             ;;
163         c)
164             RESTART="$OPTARG"
165             ;;
166         r)
167             MAXTRIES="$OPTARG"
168             ;;
169         i)
170             INTERVAL="$OPTARG"
171             ;;
172         t)
173             TMPFILE="$OPTARG"
174             ;;
175         l)
176             SYSLOG="y"
177             ;;
178         L)
179             SYSLOG=
180             ;;
181         h)
182             displayHelp
183             exit 0
184             ;;
185         q)
186             QUIET="Y"
187             ;;
188         v)
189             VERBOSE="Y"
190             ;;
191         z)
192             PATHLIST="$OPTARG:"
193             ;;
194         Z)
195             PATHLIST=
196             ;;
197         *)
198             echo "$SELF -h for usage" >&2
199             exit 1
200             ;;
201     esac
202 done
203 shift $((OPTIND-1))
204
205 export PATH="$PATHLIST$PATH"
206
207 # Add to path to deal with embedded systems
208 #
209 for P in $REQUIREDCMDS ; do
210     if >/dev/null 2>&1 which "$P" ; then
211         continue
212     fi
213     [ "$P" = "logger" ] && continue
214     echo "FATAL: missing $P command, please install"
215     exit 1
216 done
217
218 # Handle logging
219
220 if ! LOGGER="`2>/dev/null which logger`" ; then
221     LOGGER=
222 fi
223
224 function log {
225     # "priority" "message"
226     #
227     # Stdout unless syslog specified or logger isn't available
228     #
229     if [ -z "$SYSLOG" -o -z "$LOGGER" ]; then
230         if [ -n "$QUIET" -a \( "$1" = "info" -o "$1" = "notice" -o "$1" = "debug" \) ]; then
231             return 0
232         fi
233         echo "`echo \"$1\" | tr a-z A-Z`: $2"
234         return 0
235     fi
236
237     # Also log to stdout if cron job && notice or higher
238     local S
239     if [ -n "$CRONJOB" -a \( "$1" != "info" \) -a \( "$1" != "debug" \) ] || [ -n "$VERBOSE" ]; then
240         S="-s"
241     fi
242     $LOGGER $S -t "$SELF[$$]" -p "$LOGFAC.$1" "$2"
243 }
244
245 # Verify interval
246 INTERVAL=$(( $INTERVAL *1 ))
247
248 # Validate a leap-seconds file checksum
249 #
250 # File format: (full description in files)
251 # # marks comments, except:
252 # #$ number : the NTP date of the last update
253 # #@ number : the NTP date that the file expires
254 # Date (seconds since 1900) leaps : leaps is the # of seconds to add for times >= Date
255 # Date lines have comments.
256 # #h hex hex hex hex hex is the SHA-1 checksum of the data & dates, excluding whitespace w/o leading zeroes
257
258 function verifySHA {
259
260     if [ ! -f "$1" ]; then
261         return 1
262     fi
263
264     # Remove comments, except those that are markers for last update, expires and hash
265
266     local RAW="`sed $1 -e'/^\\([0-9]\\|#[\$@h]\)/!d' -e'/^#[\$@h]/!s/#.*\$//g'`"
267
268     # Extract just the data, removing all whitespace
269
270     local DATA="`echo \"$RAW\" | sed -e'/^#h/d' -e's/^#[\$@]//g' | tr -d '[:space:]'`"
271
272     # Compute the SHA hash of the data, removing the marker and filename
273     # Computed in binary mode, which shouldn't matter since whitespace has been removed
274     # shasum comes in several flavors; a portable one is available in Perl (with Digest::SHA)
275
276     local DSHA="`echo -n \"$DATA\" | shasum | sed -e's/[? *].*$//'`"
277
278     # Extract the file's hash. Restore any leading zeroes in hash segments.
279
280     # The sed [] includes a tab (\t) and space; #h is followed by a tab and space
281     local FSHA="`echo \"$RAW\" | sed -e'/^#h/!d' -e's/^#h//' -e's/[     ] */ 0x/g'`"
282     FSHA=`printf '%08x%08x%08x%08x%08x' $FSHA`
283
284     if [ -n "$FSHA" -a \( "$FSHA" = "$DSHA" \) ]; then
285         if [ -n "$2" ]; then
286             log "info" "Checksum of $1 validated"
287         fi
288     else
289         log "error" "Checksum of $1 is invalid:"
290         [ -z "$FSHA" ] && FSHA="(no checksum record found in file)"
291         log "error" "EXPECTED: $FSHA"
292         log "error" "COMPUTED: $DSHA"
293         return 1
294     fi
295
296     # Check the expiration date, converting NTP epoch to Unix epoch used by date
297
298     EXPIRES="`echo \"$RAW\" | sed -e'/^#@/!d' -e's/^#@//' | tr -d '[:space:]'`"
299     EXPIRES="$(($EXPIRES - 2208988800 ))"
300
301     if [ $EXPIRES -lt `date -u +%s` ]; then
302         log "notice" "File expired on `date -u -d \"Jan 1, 1970 00:00:00 +0000 + $EXPIRES seconds\"`"
303         return 2
304     fi
305
306 }
307
308 # Verify ntp.conf
309
310 if ! [ -f "$NTPCONF" ]; then
311     log "critical" "Missing ntp configuration $NTPCONF"
312     exit 1
313 fi
314
315 # Parse ntp.conf for leapfile directive
316
317 LEAPFILE="`sed $NTPCONF -e'/^ *leapfile  *.*$/!d' -e's/^ *leapfile  *//'`"
318 if [ -z "$LEAPFILE" ]; then
319     log "error" "$NTPCONF does not specify a leapfile"
320 fi
321
322 # Allow placing the file someplace else - testing
323
324 if [ -n "$1" ]; then
325     if [ "$1" != "$LEAPFILE" ]; then
326         log "notice" "Requested install to $1, but $NTPCONF specifies $LEAPFILE"
327     fi
328     LEAPFILE="$1"
329 fi
330
331 # Verify the current file
332 # If it is missing, doesn't validate or expired
333 # Or is expiring soon
334 #  Download a new one
335
336 if [ -n "$FORCE" ] || ! verifySHA $LEAPFILE "$VERBOSE" || [ $EXPIRES -lt `date -d "NOW + $PREFETCH" +%s` ] ; then
337     TRY=0
338     while true; do
339         TRY=$(( $TRY + 1 ))
340         if [ -n "$VERBOSE" ]; then
341             log "info" "Attempting download from $LEAPSRC, try $TRY.."
342         fi
343         if wget $PROTO $PREFER -o ${TMPFILE}.log $LEAPSRC -O $TMPFILE ; then
344             log "info" "Download of $LEAPSRC succeeded"
345             if [ -n "$VERBOSE" ]; then
346                 cat ${TMPFILE}.log
347             fi
348
349             if ! verifySHA $TMPFILE "$VERBOSE" ; then
350                 # There is no point in retrying, as the file on the server is almost
351                 # certainly corrupt.
352
353                 log "warning" "Downloaded file $TMPFILE rejected -- saved for diagnosis"
354                 cat ${TMPFILE}.log
355                 rm -f ${TMPFILE}.log
356                 exit 1
357             fi
358             rm -f ${TMPFILE}.log
359
360             # Set correct permissions on temporary file
361
362             REFFILE="$LEAPFILE"
363             if [ ! -f $LEAPFILE ]; then
364                 log "notice" "$LEAPFILE was missing, creating new copy - check permissions"
365                 touch $LEAPFILE
366                 # Can't copy permissions from old file, copy from NTPCONF instead
367                 REFFILE="$NTPCONF"
368             fi
369             chmod --reference $REFFILE $TMPFILE
370             chown --reference $REFFILE $TMPFILE
371             ( which selinuxenabled && selinuxenabled && which chcon ) >/dev/null 2>&1
372             if  [ $? == 0 ] ; then
373                 chcon --reference $REFFILE $TMPFILE
374             fi
375
376             # Replace current file with validated new one
377
378             if mv -f $TMPFILE $LEAPFILE ; then
379                 log "notice" "Installed new $LEAPFILE from $LEAPSRC"
380             else
381                 log "error" "Install $TMPFILE => $LEAPFILE failed -- saved for diagnosis"
382                 exit 1
383             fi
384
385             # Restart NTP (or whatever else is specified)
386
387             if [ -n "$RESTART" ]; then
388                 if [ -n "$VERBOSE" ]; then
389                     log "info" "Attempting restart action: $RESTART"
390                 fi
391                 R="$( 2>&1 $RESTART )"
392                 if [ $? -eq 0 ]; then
393                     log "notice" "Restart action succeeded"
394                     if [ -n "$VERBOSE" -a -n "$R" ]; then
395                         log "info" "$R"
396                     fi
397                 else
398                     log "error" "Restart action failed"
399                     if [ -n "$R" ]; then
400                         log "error" "$R"
401                     fi
402                     exit 2
403                 fi
404             fi
405             exit 0
406         fi
407
408         # Failed to download.  See about trying again
409
410         rm -f $TMPFILE
411         if [ $TRY -ge $MAXTRIES ]; then
412             break;
413         fi
414         if [ -n "$VERBOSE" ]; then
415             cat ${TMPFILE}.log
416             log "info" "Waiting $INTERVAL minutes before retrying..."
417         fi
418         sleep $(( $INTERVAL * 60))
419     done
420
421     # Failed and out of retries
422
423     log "warning" "Download from $LEAPSRC failed after $TRY attempts"
424     if [ -f ${TMPFILE}.log ]; then
425         cat ${TMPFILE}.log
426         rm -f ${TMPFILE}.log $TMPFILE
427     fi
428     exit 1
429 fi
430 log "info" "Not time to replace $LEAPFILE"
431
432 exit 0
433
434 # EOF