]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 2769] Convert update-leap to perl
authorHarlan Stenn <stenn@ntp.org>
Sat, 30 May 2015 07:19:56 +0000 (07:19 +0000)
committerHarlan Stenn <stenn@ntp.org>
Sat, 30 May 2015 07:19:56 +0000 (07:19 +0000)
bk: 5569649c7wvcD7jDJQHYxuxhuZay-w

ChangeLog
scripts/update-leap/Makefile.am
scripts/update-leap/update-leap.in [new file with mode: 0755]
scripts/update-leap/update-leap.sh [moved from scripts/update-leap/update-leap with 100% similarity]

index 6dd8aba23567c68bb5d0e558a420cd11ae6def07..b2697e8a23be7e0cff391978ca1c9123a73666d0 100644 (file)
--- 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 <stenn@ntp.org>
 
 * CID 739725: Fix a rare resource leak in libevent/listener.c.
index 163810233b19d346e44689b3ab96d6a0853bf82c..9b5829a0b33c55f27f37170c8e35034d9284a4b0 100644 (file)
@@ -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 (executable)
index 0000000..5397dc4
--- /dev/null
@@ -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
+          <none> - 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 (<LF>) {
+       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 (<LF>) {
+    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