]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 3405] update-leap.in: general cleanup, HTTPS support. Paul McMath.
authorHarlan Stenn <stenn@ntp.org>
Wed, 13 Dec 2017 03:15:09 +0000 (19:15 -0800)
committerHarlan Stenn <stenn@ntp.org>
Wed, 13 Dec 2017 03:15:09 +0000 (19:15 -0800)
bk: 5a309b3daepGVTxjAjOhRAKLiznY1g

ChangeLog
NEWS
scripts/update-leap/update-leap.in

index 9bfe76719a28f08fd7982e0709303ac3727aae20..5a4f576e0113bf5de852a8f3b5e1d40baabb76c1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -22,6 +22,7 @@
   - raised receive buffer size to 1200 <perlinger@ntp.org>
 * [Bug 3408] refclock_jjy.c: Avoid a wrong report of the coverity static
   analysis tool. <abe@ntp.org>
+* [Bug 3405] update-leap.in: general cleanup, HTTPS support.  Paul McMath.
 * [Bug 3399] NTP: linker error in 4.2.8p10 during Linux cross-compilation
   - initial patch by timeflies@mail2tor.com  <perlinger@ntp.org>
 * [Bug 3398] tests fail with core dump <perlinger@ntp.org>
diff --git a/NEWS b/NEWS
index 0e95f428dc82548f2efac2ba6435584f852e84f4..5cd1db36edafadf1bb238ba3572c7627a1d15067 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,10 @@
 --
+
+update-leap needs:
+       Net::SSLeay
+       IO::Socket::SSL
+
+--
 NTP 4.2.8p10 (Harlan Stenn <stenn@ntp.org>, 2017/03/21) 
 
 Focus: Security, Bug fixes, enhancements.
index bd7ed1805cd45878f4779194ef8e6ee982d3b14a..dd666046d6295e51821c4bc40243ebd9a90d5d1a 100755 (executable)
 #! @PATH_PERL@ -w
 
-# Copyright (C) 2015 Network Time Foundation
+# Copyright (C) 2015, 2017 Network Time Foundation
 # Author: Harlan Stenn
-
+#
+# General cleanup and https support: Paul McMath
+#
 # 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.
 
+######## BEGIN #########
 use strict;
 
+# Core modules
 use Digest::SHA qw(sha1_hex);
+use File::Basename;
 use File::Copy qw(move);
-use File::Fetch;
+use File::Temp qw(tempfile);
 use Getopt::Long qw(:config auto_help no_ignore_case bundling);
-use Sys::Syslog;
+use Sys::Syslog qw(:standard :macros);
 
-my $VERSION="1.003";
+# External modules
+use HTTP::Tiny 0.056;
+use Net::SSLeay 1.49;
+use IO::Socket::SSL 1.56;
 
-# leap-seconds file manager/updater
+my $VERSION = '1.004';
 
-# ########## Default configuration ##########
-#
+my $RUN_DIR = '/tmp';
+my $RUN_UID = 0;
+my $TMP_FILE;
+my $TMP_FH;
+my $FILE_MODE = 0644;
+
+######## DEFAULT CONFIGURATION ##########
+# LEAP FILE SRC URIS
+#    HTTPS - (default)
+#      https://www.ietf.org/timezones/data/leap-seconds
+#    HTTP - No TLS/SSL - (not recommended)
+#      http://www.ietf.org/timezones/data/leap-seconds.list
 
-my $CRONJOB = $ENV{'CRONJOB'};
-$CRONJOB = "" unless defined($CRONJOB);
-my $LOGGER;
-my $QUIET = "";
-my $VERBOSE = "";
-
-# Where to get the file
-# Choices:
-#      https://www.ietf.org/timezones/data/leap-seconds.list
-#      ftp://time.nist.gov/pub/leap-seconds.list
-my $LEAPSRC="https://www.ietf.org/timezones/data/leap-seconds.list";
+my $LEAPSRC = 'https://www.ietf.org/timezones/data/leap-seconds.list';
 my $LEAPFILE;
 
 # How many times to try to download new file
-my $MAXTRIES=6;
-my $INTERVAL=10;
+my $MAXTRIES = 6;
+my $INTERVAL = 10;
 
-# Where to find ntp config file
-my $NTPCONF="/etc/ntp.conf";
+my $NTPCONF='/etc/ntp.conf';
 
 # How long (in days) before expiration to get updated file
-my $PREFETCH="60";
+my $PREFETCH = 60;
+my $EXPIRES;
+my $FORCE;
+
+# Output Flags
+my $QUIET;
+my $DEBUG;
+my $SYSLOG;
+my $TOTERM;
+my $LOGFAC = 'LOG_USER';
+
+######### PARSE/SET OPTIONS #########
+my %SSL_OPTS;
+my %SSL_ATTRS = (
+    verify_SSL => 1,  
+    SSL_options => \%SSL_OPTS,
+);
 
-# 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="";
+our(%opt);
 
-my $EXPIRES;
-my $FORCE = "";
+GetOptions(\%opt,
+       'C=s',
+       'D=s',
+       'e:60',
+       'F',
+       'f=s',
+       'h',
+       'i:10',
+       'L=s',
+       'l=s',
+       'q',
+       'r:6',
+       's',
+       't',
+       'u=s',
+       'v',
+       );
 
-# Where to put temporary copy before it's validated
-my $TMPFILE="/tmp/leap-seconds.$$.tmp";
+$LOGFAC   = $opt{l} if defined $opt{l};
+$LEAPSRC  = $opt{u} if defined $opt{u};
+$LEAPFILE = $opt{L} if defined $opt{L};
+$PREFETCH = $opt{e} if defined $opt{e};
+$NTPCONF  = $opt{f} if defined $opt{f};
+$MAXTRIES = $opt{r} if defined $opt{r};
+$INTERVAL = $opt{i} if defined $opt{i};
+
+$FORCE   = 1 if defined $opt{F};
+$DEBUG  = 1 if defined $opt{v};
+$QUIET   = 1 if defined $opt{q};
+$SYSLOG  = 1 if defined $opt{s};
+$TOTERM  = 1 if defined $opt{t};
+
+$SSL_OPTS{SSL_ca_file} = $opt{C} if (defined($opt{C}));
+$SSL_OPTS{SSL_ca_path} = $opt{D} if (defined($opt{D}));
+
+###############
+## START MAIN
+###############
+my $PROG = basename($0);
+
+# Logging - Default is to use syslog(3) if STDOUT isn't 
+# connected to a tty.
+if ($SYSLOG || !-t STDOUT) {
+    $SYSLOG = 1;
+    openlog($PROG, 'pid', $LOGFAC);
+} 
+else {
+    $TOTERM = 1;
+}
 
-# Syslog facility
-my $LOGFAC="daemon";
+$SIG{INT} = \&signal_catcher;
+$SIG{TERM} = \&signal_catcher;
+$SIG{QUIT} = \&signal_catcher;
 
-# ###########################################
+# Take some security precautions
+close STDIN;
 
-=item update-leap
+if ($< != $RUN_UID) {
+    log_fatal(LOG_ERR, 'User ' . getpwuid($<) . " (UID $<) tried to run $PROG");
+}
 
-Usage: $0 [options] [leapfile]
+chdir $RUN_DIR || log_fatal("Failed to change dir to $RUN_DIR");
 
-Verifies and if necessary, updates leap-second definition file
+# Show help
+if (defined $opt{h}) {
+    show_help();
+    exit 0;
+}
 
-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
+if ($QUIET && $DEBUG) {
+    log_fatal(LOG_ERR, '-q and -d options mutually exclusive');
+}
 
-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
+if ($LEAPFILE && $NTPCONF) {
+    log_fatal(LOG_ERR, '-L and -f options mutually exclusive');
+}
 
-$0 will validate the file currently on the local system
+# Parse ntp.conf for path to leapfile if not set by user
+if (! $LEAPFILE) {
 
-Ordinarily, the file is found using the "leapfile" directive in $NTPCONF.
-However, an alternate location can be specified on the command line.
+    open my $LF, '<', $NTPCONF || log_fatal(LOG_ERR, "Can't open <$NTPCONF>: $!");
 
-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.
+    while (<$LF>) {
+       chomp;
+       $LEAPFILE = $1 if /^ *leapfile\s+"(\S+)"/;
+    }
+    close $LF;
 
-If the current file is acceptable, no download or restart occurs.
+    if (! $LEAPFILE) {
+       log_fatal(LOG_ERR, "No leapfile directive in $NTPCONF; leapfile location not known"); 
+    }
+}
 
--c can also be used to invoke another script to perform administrative
-functions, e.g. to copy the file to other local systems.
+-s $LEAPFILE || logger(LOG_DEBUG, "Leapfile $LEAPFILE is empty");
 
-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.
+# Download new file if:
+#   1. file doesn't exist
+#   2. invoked w/ force flag (-F)
+#   3. current file isn't valid
+#   4. current file expired or expires soon
 
-For cron-friendly behavior, define CRONJOB=1 in the crontab.
+if ( !-e $LEAPFILE || $FORCE || ! verifySHA($LEAPFILE) || 
+       ( $EXPIRES lt ( $PREFETCH * 86400 + time() ) )) {
 
-Version $VERSION
-=cut
+    for (my $try = 1; $try <= $MAXTRIES; $try++) {
+       logger(LOG_DEBUG, "Attempting download from $LEAPSRC, try $try..");
 
-# Default: Use syslog for logging if running under cron
+       ($TMP_FH, $TMP_FILE) = tempfile(UNLINK => 1, SUFFIX => '.list');
 
-my $SYSLOG = $CRONJOB;
+       if (retrieve_file($TMP_FH)) {
 
-# Parse options
+            if ( verifySHA($TMP_FILE) ) {
+               move_file($TMP_FILE, $LEAPFILE);
+               chmod $FILE_MODE, $LEAPFILE; 
+               logger(LOG_INFO, "Installed new $LEAPFILE from $LEAPSRC");
+           }
+           else {
+                logger(LOG_ERR, "Downloaded file $TMP_FILE rejected -- saved for diagnosis");
+               move_file($TMP_FILE, 'leap-seconds.list_corrupt');
+               exit 1;
+            }
+           # Fall through
+            exit 0;
+       }
 
-our(%opt);
+       # Failure
+       logger(LOG_INFO, "Download failed. Waiting $INTERVAL minutes before retrying...");
+        sleep $INTERVAL * 60 ;
+    }
 
-GetOptions(\%opt,
-       'c=s',
-       'e:60',
-       'F',
-       'f=s',
-       'i:10',
-       'L',
-       'l',
-       'P=s',
-       'q',
-       'r:6',
-       's=s',
-       't=s',
-       'v'
-       );
+    # Failed and out of retries
+    log_fatal(LOG_ERR, "Download from $LEAPSRC failed after $MAXTRIES attempts");
+}
+
+logger(LOG_INFO, "Not time to replace $LEAPFILE");
+
+exit 0;
 
-$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}));
+######## SUB ROUTINES #########
+sub move_file {
 
-# export PATH="$PATHLIST$PATH"
+    (my $src, my $dst) = @_;
 
-# Handle logging
+    if ( move($src, $dst) ) {
+       logger(LOG_DEBUG, "Moved $src to $dst");
+    } 
+    else {
+       log_fatal(LOG_ERR, "Moving $src to $dst failed: $!");
+    }
+}
 
-openlog($0, 'pid', $LOGFAC);
+# Removes temp file if terminating signal recv'd
+sub signal_catcher {
+    my $signame = shift;
+
+    close $TMP_FH;
+    unlink $TMP_FILE;
+    log_fatal(LOG_INFO, "Recv'd SIG${signame}. Terminating.");
+}          
+
+sub log_fatal {
+    my ($p, $msg) = @_;
+    logger($p, $msg);
+    exit 1;
+}
 
 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
+    my ($p, $msg) = @_;
+
+    # Suppress LOG_DEBUG msgs unless $DEBUG set
+    return if (!$DEBUG && $p eq LOG_DEBUG);
+
+    # Suppress all but LOG_ERR msgs if $QUIET set
+    return if ($QUIET && $p ne LOG_ERR);
+
+    if ($TOTERM) {
+        if ($p eq LOG_ERR) {   # errors should go to STDERR
+           print STDERR "$msg\n";
+       }
+       else {
+           print STDOUT "$msg\n";
        }
-       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";
+    if ($SYSLOG) {
+       syslog($p, $msg)
     }
-    syslog($priority, $message);
 }
 
-# Verify interval
-# INTERVAL=$(( $INTERVAL *1 ))
+#################################
+# Connect to server and retrieve file
+#
+# Since we make as many as $MAXTRIES attempts to connect to the remote
+# server to download the file, the network socket should be closed after
+# each attempt, rather than let it be reused (because it may be in some
+# unknown state).
+#
+# HTTP::Tiny doesn't export a method to explicitly close a connected
+# socket, therefore, we instantiate the lexically scoped object in a
+# function; when the function returns, the http object goes out of
+# scope and is destroyed, closing the socket.
+sub retrieve_file {
+
+    my $fh = shift;
+    my $http;
+
+    if ($LEAPSRC =~ /^https\S+/) {
+       $http = HTTP::Tiny->new(%SSL_ATTRS);
+       (my $ok, my $why) = $http->can_ssl;
+       log_fatal(LOG_ERR, "TLS/SSL config error: $why") if ! $ok;
+    } 
+    else {
+       $http = HTTP::Tiny->new();
+    }
+
+    my $reply = $http->get($LEAPSRC);
+
+    if ($reply->{success}) {
+       logger(LOG_DEBUG, "Download of $LEAPSRC succeeded");
+       print $fh $reply->{content} || 
+           log_fatal(LOG_ERR, "Couldn't write new file contents to temp file: $!");
+       close $fh;
+       return 1;
+    } 
+    else {
+       close $fh;
+       return 0;
+    }
+}
 
+########################
 # 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
+# File format: (full description in file)
+# Pound sign (#) marks comments, EXCEPT:
+#      #$ number : the NTP date of the last update
+#      #@ number : the NTP date that the file expires
+#      #h hex hex hex hex hex : the SHA-1 checksum of the data & dates, 
+#         excluding whitespace w/o leading zeroes
+#
+# 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
+#   0  Invalid Checksum/Expired
+#   1  File is valid
 
 sub verifySHA {
-    my ($file, $verbose) = @_;
 
-    my $raw = "";
-    my $data = "";
+    my $file = shift;
+    my $fh;
+    my $data;
     my $FSHA;
 
+    open $fh, '<', $file || log_fatal(LOG_ERR, "Can't open $file: $!");
+
     # 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>) {
+    while (<$fh>) {
        if (/^#\$/) {
-               $raw .= $_;
-               s/^..//;
-               $data .= $_;
+           s/^..//;
+           $data .= $_;
        }
        elsif (/^#\@/) {
-               $raw .= $_;
-               s/^..//;
-               $data .= $_;
-               s/\s+//g;
-               $EXPIRES = $_ - 2208988800;
+           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);
+           chomp;
+           $FSHA = sprintf("%08s%08s%08s%08s%08s", $1, $2, $3, $4, $5);
        }
        elsif (/^#/) {
-               # ignore it
+           # ignore it
        }
        elsif (/^\d/) {
-               s/#.*$//;
-               $raw .= $_;
-               $data .= $_;
-       else {
-               chomp;
-               print "Unexpected line: <$_>\n";
+           s/#.*$//;
+           $data .= $_;
+       } 
+       else {
+           chomp;
+           print "Unexpected line: <$_>\n";
        }
     }
-    close LF;
+    close $fh;
+
+    if ( $EXPIRES < time() ) {
+        logger(LOG_DEBUG, 'File expired on ' . gmtime($EXPIRES));
+        return 0;
+    }
+
+    if (! $FSHA) {
+       logger(LOG_NOTICE, "no checksum record found in file");
+       return 0;
+    }
 
     # 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;
+    if ($FSHA eq $DSHA) {
+       logger(LOG_DEBUG, "Checksum of $file validated");
+       return 1;
+    } 
+    else {
+        logger(LOG_NOTICE, "Checksum of $file is invalid EXPECTED: $FSHA COMPUTED: $DSHA");
+        return 0;
     }
-    return 0;
 }
 
-# Verify ntp.conf
+sub show_help {
+print <<EOF
 
--r $NTPCONF || die "Missing ntp configuration: $NTPCONF\n";
+Usage: $PROG [options]
 
-# 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;
+Verifies and if necessary, updates leap-second definition file
 
--s $LEAPFILE || warn "$NTPCONF specifies $LEAPFILE as a leapfile, which is empty.\n";
+All arguments are optional:  Default (or current value) shown:
+    -C    Absolute path to CA Cert (see SSL/TLS Considerations)
+    -D    Path to a CAdir (see SSL/TLS Considerations)
+    -e    Specify how long (in days) before expiration the file is to be
+         refreshed.  Note that larger values imply more frequent refreshes.
+          $PREFETCH
+    -F    Force update even if current file is OK and not close to expiring.
+    -f    Absolute path ntp.conf file (default /etc/ntp.conf)
+          $NTPCONF
+    -h    show help
+    -i    Specify number of minutes between retries
+          $INTERVAL
+    -L    Absolute path to leapfile on the local system
+         (overrides value in ntp.conf)
+    -l    Specify the syslog(3) facility for logging
+          $LOGFAC
+    -q    Only report errors (cannot be used with -v)
+    -r    Specify number of attempts to retrieve file
+          $MAXTRIES
+    -s    Send output to syslog(3) - implied if STDOUT has no tty or redirected
+    -t    Send output to terminal - implied if STDOUT attached to terminal
+    -u    Specify the URL of the master copy to download
+          $LEAPSRC
+    -v    Verbose - show debug messages (cannot be used with -q)
 
-# Allow placing the file someplace else - testing
+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
 
-if ( defined $ARGV[0] ) {
-    if ( $ARGV[0] ne $LEAPFILE ) {
-       logger("notice", "Requested install to $ARGV[0], but $NTPCONF specifies $LEAPFILE");
-    }
-    $LEAPFILE = $ARGV[0];
-}
+$PROG will validate the file currently on the local system.
 
-# 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;
-            }
+Ordinarily, the leapfile is found using the 'leapfile' directive in
+$NTPCONF.  However, an alternate location can be specified on the
+command line with the -L flag.
 
-           # 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;
-            }
+If the leapfile does not exist, is not valid, has expired, or is
+expiring soon, a new copy will be downloaded.  If the new copy is
+valid, it is installed.
 
-           # 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;
-       }
+If the current file is acceptable, no download or restart occurs.
 
-       # Failed to download.  See about trying again
+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.
 
-        # rm -f $TMPFILE
-        if ( $TRY ge $MAXTRIES ) {
-            last;
-        }
-        if ( $VERBOSE ne "" ) {
-            logger("info", "Waiting $INTERVAL minutes before retrying...");
-        }
-        sleep $INTERVAL * 60 ;
-    }
+SSL/TLS Considerations
+-----------------------
+The perl modules can usually locate the CA certificate used to verify
+the peer's identity.
 
-    # Failed and out of retries
+On BSDs, the default is typically the file /etc/ssl/certs.pem.  On
+Linux, the location is typically a path to a CAdir - a directory of
+symlinks named according to a hash of the certificates' subject names.
 
-    logger("warning", "Download from $LEAPSRC failed after $TRY attempts");
-    exit 1;
-}
+The -C or -D options are available to pass in a location if no CA cert
+is found in the default location.
 
-print "FORCE is <$FORCE>\n";
-print "verifySHA is " . verifySHA($LEAPFILE, "") . "\n";
-print "EXPIRES <$EXPIRES>  vs ". ( $PREFETCH * 86400 + time() ) . "\n";
+External Dependencies
+---------------------
+The following perl modules are required:
+HTTP::Tiny     - version >= 0.056
+IO::Socket::SSL - version >= 1.56
+NET::SSLeay    - version >= 1.49
 
-logger("info", "Not time to replace $LEAPFILE");
+Version: $VERSION
 
-exit 0;
+EOF
+}
 
-# EOF