From: Harlan Stenn Date: Sat, 30 May 2015 07:19:56 +0000 (+0000) Subject: [Bug 2769] Convert update-leap to perl X-Git-Tag: NTP_4_3_34~7^2~3^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fefb40ff8a3b3523e5434900911769dff4c73fed;p=thirdparty%2Fntp.git [Bug 2769] Convert update-leap to perl bk: 5569649c7wvcD7jDJQHYxuxhuZay-w --- diff --git a/ChangeLog b/ChangeLog index 6dd8aba23..b2697e8a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,7 @@ --- + +* [Bug 2769] Convert update-leap to perl. +--- (4.2.8p3-RC1) 2015/05/12 Released by Harlan Stenn * CID 739725: Fix a rare resource leak in libevent/listener.c. diff --git a/scripts/update-leap/Makefile.am b/scripts/update-leap/Makefile.am index 163810233..9b5829a0b 100644 --- a/scripts/update-leap/Makefile.am +++ b/scripts/update-leap/Makefile.am @@ -28,13 +28,14 @@ endif EXTRA_DIST = \ invoke-update-leap.menu \ invoke-update-leap.texi \ - update-leap \ update-leap-opts.def \ update-leap-opts \ update-leap.1update-leapman \ update-leap.1update-leapmdoc \ + update-leap.in \ update-leap.man.in \ update-leap.mdoc.in \ + update-leap.sh \ update-leap.texi \ update-leap.html \ $(NULL) diff --git a/scripts/update-leap/update-leap.in b/scripts/update-leap/update-leap.in new file mode 100755 index 000000000..5397dc4e2 --- /dev/null +++ b/scripts/update-leap/update-leap.in @@ -0,0 +1,425 @@ +#! @PERL_PATH@ -w + +# Copyright (C) 2015 Network Time Foundation +# Author: Harlan Stenn + +# Original shell version: +# Copyright (C) 2014 Timothe Litt litt at acm dot org + +# This script may be freely copied, used and modified providing that +# this notice and the copyright statement are included in all copies +# and derivative works. No warranty is offered, and use is entirely at +# your own risk. Bugfixes and improvements would be appreciated by the +# author. + +use strict; + +use Digest::SHA qw(sha1_hex); +use File::Copy qw(move); +use File::Fetch; +use Getopt::Long qw(:config no_ignore_case bundling); +use Sys::Syslog; + +my $VERSION="1.003"; + +# leap-seconds file manager/updater + +# ########## Default configuration ########## +# + +my $CRONJOB = $ENV{'CRONJOB'}; +$CRONJOB = "" unless defined($CRONJOB); +my $LOGGER; +my $QUIET = ""; +my $VERBOSE = ""; + +# Where to get the file +my $LEAPSRC="ftp://time.nist.gov/pub/leap-seconds.list"; +my $LEAPFILE; + +# How many times to try to download new file +my $MAXTRIES=6; +my $INTERVAL=10; + +# Where to find ntp config file +my $NTPCONF="/etc/ntp.conf"; + +# How long (in days) before expiration to get updated file +my $PREFETCH="60"; + +# How to restart NTP - older NTP: service ntpd? try-restart | condrestart +# Recent NTP checks for new file daily, so there's nothing to do +my $RESTART=""; + +my $EXPIRES; +my $FORCE = ""; + +# Where to put temporary copy before it's validated +my $TMPFILE="/tmp/leap-seconds.$$.tmp"; + +# Syslog facility +my $LOGFAC="daemon"; + +# ########################################### + +=item update-leap + +Usage: $0 [options] [leapfile] + +Verifies and if necessary, updates leap-second definition file + +All arguments are optional: Default (or current value) shown: + -s Specify the URL of the master copy to download + $LEAPSRC + -d Specify the filename on the local system + $LEAPFILE + -e Specify how long (in days) before expiration the file is to be + refreshed. Note that larger values imply more frequent refreshes. + "$PREFETCH" + -f Specify location of ntp.conf (used to make sure leapfile directive is + present and to default leapfile) + $NTPCONF + -F Force update even if current file is OK and not close to expiring. + -r Specify number of times to retry on get failure + $MAXTRIES + -i Specify number of minutes between retries + $INTERVAL + -l Use syslog for output (Implied if CRONJOB is set) + -L Don't use syslog for output + -P Specify the syslog facility for logging + $LOGFAC + -t Name of temporary file used in validation + $TMPFILE + -q Only report errors to stdout + -v Verbose output + +The following options are not (yet) implemented in the perl version: + -4 Use only IPv4 + -6 Use only IPv6 + -c Command to restart NTP after installing a new file + - ntpd checks file daily + -p 4|6 + Prefer IPv4 or IPv6 (as specified) addresses, but use either + -z Specify path for utilities + $PATHLIST + -Z Only use system path + +$0 will validate the file currently on the local system + +Ordinarily, the file is found using the "leapfile" directive in $NTPCONF. +However, an alternate location can be specified on the command line. + +If the file does not exist, is not valid, has expired, or is expiring soon, +a new copy will be downloaded. If the new copy validates, it is installed and +NTP is (optionally) restarted. + +If the current file is acceptable, no download or restart occurs. + +-c can also be used to invoke another script to perform administrative +functions, e.g. to copy the file to other local systems. + +This can be run as a cron job. As the file is rarely updated, and leap +seconds are announced at least one month in advance (usually longer), it +need not be run more frequently than about once every three weeks. + +For cron-friendly behavior, define CRONJOB=1 in the crontab. + +Version $VERSION +=cut + +# Default: Use syslog for logging if running under cron + +my $SYSLOG = $CRONJOB; + +# Parse options + +our(%opt); + +GetOptions(\%opt, + 'c=s', + 'e:60', + 'F', + 'f=s', + 'h', + 'i:10', + 'L', + 'l', + 'P=s', + 'q', + 'r:6', + 's=s', + 't=s', + 'v' + ); + +$LOGFAC=$opt{P} if (defined($opt{P})); +$LEAPSRC=$opt{s} if (defined($opt{s})); +$PREFETCH=$opt{e} if (defined($opt{e})); +$NTPCONF=$opt{f} if (defined($opt{f})); +$FORCE="Y" if (defined($opt{F})); +$RESTART=$opt{c} if (defined($opt{c})); +$MAXTRIES=$opt{r} if (defined($opt{r})); +$INTERVAL=$opt{i} if (defined($opt{i})); +$TMPFILE=$opt{t} if (defined($opt{t})); +$SYSLOG="Y" if (defined($opt{l})); +$SYSLOG="" if (defined($opt{L})); +$QUIET="Y" if (defined($opt{q})); +$VERBOSE="Y" if (defined($opt{v})); + +# export PATH="$PATHLIST$PATH" + +# Handle logging + +openlog($0, 'pid', $LOGFAC); + +sub logger { + my ($priority, $message) = @_; + + # "priority" "message" + # + # Stdout unless syslog specified or logger isn't available + # + if ($SYSLOG eq "" or $LOGGER eq "") { + if ($QUIET ne "" and ( $priority eq "info" or $priority eq "notice" or $priority eq "debug" ) ) { + return 0 + } + printf "%s: $message\n", uc $priority; + return 0; + } + + # Also log to stdout if cron job && notice or higher + if (($CRONJOB ne "" and ($priority ne "info" ) and ($priority ne "debug" )) || ($VERBOSE ne "")) { + # Log to stderr as well + print STDERR "$0: $priority: $message\n"; + } + syslog($priority, $message); +} + +# Verify interval +# INTERVAL=$(( $INTERVAL *1 )) + +# Validate a leap-seconds file checksum +# +# File format: (full description in files) +# # marks comments, except: +# #$ number : the NTP date of the last update +# #@ number : the NTP date that the file expires +# Date (seconds since 1900) leaps : leaps is the # of seconds to add for times >= Date +# Date lines have comments. +# #h hex hex hex hex hex is the SHA-1 checksum of the data & dates, excluding whitespace w/o leading zeroes +# +# Returns: +# 0 File is valid +# 1 Invalid Checksum +# 2 Expired + +sub verifySHA { + my ($file, $verbose) = @_; + + my $raw = ""; + my $data = ""; + my $FSHA; + + # Remove comments, except those that are markers for last update, + # expires and hash + + unless (open(LF, $file)) { + warn "Can't open <$file>: $!\n"; + print "Will try and create that file.\n"; + return 1; + }; + while () { + if (/^#\$/) { + $raw .= $_; + s/^..//; + $data .= $_; + } + elsif (/^#\@/) { + $raw .= $_; + s/^..//; + $data .= $_; + s/\s+//g; + $EXPIRES = $_ - 2208988800; + } + elsif (/^#h\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+)/) { + chomp; + $raw .= $_; + $FSHA = sprintf("%08s%08s%08s%08s%08s", $1, $2, $3, $4, $5); + } + elsif (/^#/) { + # ignore it + } + elsif (/^\d/) { + s/#.*$//; + $raw .= $_; + $data .= $_; + } else { + chomp; + print "Unexpected line: <$_>\n"; + } + } + close LF; + + # Remove all white space + $data =~ s/\s//g; + + # Compute the SHA hash of the data, removing the marker and filename + # Computed in binary mode, which shouldn't matter since whitespace has been removed + + my $DSHA = sha1_hex($data); + + # Extract the file's hash. Restore any leading zeroes in hash segments. + + if ( ( "$FSHA" ne "" ) && ( $FSHA eq $DSHA ) ) { + if ( $verbose ne "" ) { + logger("info", "Checksum of $file validated"); + } + } else { + logger("error", "Checksum of $file is invalid:"); + $FSHA="(no checksum record found in file)" + if ( $FSHA eq ""); + logger("error", "EXPECTED: $FSHA"); + logger("error", "COMPUTED: $DSHA"); + return 1; + } + + # Check the expiration date, converting NTP epoch to Unix epoch used by date + + if ( $EXPIRES < time() ) { + logger("notice", "File expired on " . gmtime($EXPIRES)); + return 2; + } + return 0; +} + +# Verify ntp.conf + +-r $NTPCONF || die "Missing ntp configuration: $NTPCONF\n"; + +# Parse ntp.conf for leapfile directive + +open(LF, $NTPCONF) || die "Can't open <$NTPCONF>: $!\n"; +while () { + chomp; + if (/^ *leapfile\s+(\S+)/) { + $LEAPFILE = $1; + } +} +close LF; + +-s $LEAPFILE || warn "$NTPCONF specifies $LEAPFILE as a leapfile, which is empty.\n"; + +# Allow placing the file someplace else - testing + +if ( defined $ARGV[0] ) { + if ( $ARGV[0] ne $LEAPFILE ) { + logger("notice", "Requested install to $ARGV[0], but $NTPCONF specifies $LEAPFILE"); + } + $LEAPFILE = $ARGV[0]; +} + +# Verify the current file +# If it is missing, doesn't validate or expired +# Or is expiring soon +# Download a new one + +if ( $FORCE ne "" || verifySHA($LEAPFILE, $VERBOSE) || ( $EXPIRES lt ( $PREFETCH * 86400 + time() ) )) { + my $TRY = 0; + my $ff = File::Fetch->new(uri => $LEAPSRC) || die "Fetch failed.\n"; + while (1) { + ++$TRY; + logger("info", "Attempting download from $LEAPSRC, try $TRY..") + if ($VERBOSE ne ""); + my $where = $ff->fetch( to => '/tmp' ); + + if ($where) { + logger("info", "Download of $LEAPSRC succeeded"); + + if ( verifySHA($where, $VERBOSE )) { + # There is no point in retrying, as the file on the + # server is almost certainly corrupt. + + logger("warning", "Downloaded file $where rejected -- saved for diagnosis"); + exit 1; + } + + # While the shell script version will set correct permissions + # on temporary file, for the perl version that's harder, so + # for now at least one should run this script as the + # appropriate user. + + # REFFILE="$LEAPFILE" + # if [ ! -f $LEAPFILE ]; then + # logger "notice" "$LEAPFILE was missing, creating new copy - check permissions" + # touch $LEAPFILE + # # Can't copy permissions from old file, copy from NTPCONF instead + # REFFILE="$NTPCONF" + # fi + # chmod --reference $REFFILE $TMPFILE + # chown --reference $REFFILE $TMPFILE + # ( which selinuxenabled && selinuxenabled && which chcon ) >/dev/null 2>&1 + # if [ $? == 0 ] ; then + # chcon --reference $REFFILE $TMPFILE + # fi + + # Replace current file with validated new one + + if ( move $where, $LEAPFILE ) { + logger("notice", "Installed new $LEAPFILE from $LEAPSRC"); + } else { + logger("error", "Install $where => $LEAPFILE failed -- saved for diagnosis: $!"); + exit 1; + } + + # Restart NTP (or whatever else is specified) + + if ( $RESTART ne "" ) { + if ( $VERBOSE ne "" ) { + logger("info", "Attempting restart action: $RESTART"); + } + +# XXX + #R="$( 2>&1 $RESTART )" + #if [ $? -eq 0 ]; then + # logger "notice" "Restart action succeeded" + # if [ -n "$VERBOSE" -a -n "$R" ]; then + # logger "info" "$R" + # fi + #else + # logger "error" "Restart action failed" + # if [ -n "$R" ]; then + # logger "error" "$R" + # fi + # exit 2 + #fi + } + exit 0; + } + + # Failed to download. See about trying again + + # rm -f $TMPFILE + if ( $TRY ge $MAXTRIES ) { + last; + } + if ( $VERBOSE ne "" ) { + logger("info", "Waiting $INTERVAL minutes before retrying..."); + } + sleep $INTERVAL * 60 ; + } + + # Failed and out of retries + + logger("warning", "Download from $LEAPSRC failed after $TRY attempts"); + exit 1; +} + +print "FORCE is <$FORCE>\n"; +print "verifySHA is " . verifySHA($LEAPFILE, "") . "\n"; +print "EXPIRES <$EXPIRES> vs ". ( $PREFETCH * 86400 + time() ) . "\n"; + +logger("info", "Not time to replace $LEAPFILE"); + +exit 0; + +# EOF diff --git a/scripts/update-leap/update-leap b/scripts/update-leap/update-leap.sh similarity index 100% rename from scripts/update-leap/update-leap rename to scripts/update-leap/update-leap.sh