#!/usr/bin/perl
#
# saneDate.pl or check.saneDate.hourly (in my cron.hourly file)
#
# Program to do a sanity check on the local clock's time and date.
# Many if not most Unix based Web sites use ntp to synchronize their
# clock with that of a stratum 2 time server. ntp does a good job,
# but if for some reason, your clock differs significantly (about
# one hour), ntp will stop functioning, and on my machine, it even
# exits, thus leaving you with an unsynchronized clock. This
# typically happens during spring and fall daylight savings time
# changes. This program is meant to correct those situations and
# ensure that ntp is running and that your clock stays within
# reasonable sync with the external servers.
#
# This is my first Perl program, so if you have any comments
# don't hesitate to let me know how I can become more adept at Perl.
#
# Kern E. Sibbald, April 1999
#
# Version 3, 7 June 1999
#
# This program is in the public domain.
#
# Assumptions:
# 1. Perl is in /usr/bin/perl (if not change the first line)
# 2. You are running NTP (tested with xntp3-5.93-4 and xntp3-5.93-12)
# 3. The program ntpq is available
# 4. The program ntpdate is available
# 5. This Perl program is run once an hour
# 6. This program always restarts NTP if it thinks it is not running.
# 7. This program leaves files in the /tmp directory when it detects
# a problem. When all is OK, they are removed. If you automatically
# remove files from /tmp, you may want to put these files elsewhere.
# If they are removed, the worst that will happen is that you will
# miss sync warning messages, and you may get more email than
# you expected.
#
# Changes for Version 3 -- 7 June 99:
# - added better error handling if network is down
# - added separate variables for the directory for ntpdate and ntpq
# - added check that config for ntpdate, ntpq, and ntp.conf are correct.
#
# Changes for Version 2 -- 12 May 99:
# - added code to get clock sites from ntp.conf
#
# Definitions which should be carefully checked and
# possibly changed for each site
#
#
# if read_ntp_conf is set to one, this program will attempt to
# read the file in $ntp_conf and build a list of servers to be
# used instead of the hardcoded $clocksites. If no servers are
# found, then $clocksites is used.
$read_ntp_conf = 1;
# ntp_conf is the location of your ntp configuration file.
$ntp_conf = "/etc/ntp.conf";
# clocksites is the list of sites to be used for synchronization
# it probably should be the same as the sites in your ntp.conf file.
# Note, if read_ntp_conf is set, this program will attempt to build
# a list from your ntp configuration file.
$clocksites = "bernina.ethz.ch fartein.ifi.uio.no ntp0.strath.ac.uk";
# ntpdatedir is the directory where ntpdate is located
# It needs a trailing slash
$ntpdatedir = "/usr/sbin/";
# ntpqdir is the directory where ntpq is located
$ntpqdir = "/usr/sbin/";
# ntpstop is the name of a command/script that will stop the ntp daemon
$ntpstop = "/etc/rc.d/init.d/xntpd stop";
# ntpstart is the name of a command/script that will start the ntp daemon
$ntpstart = "/etc/rc.d/init.d/xntpd start";
#
# Definitions which might need changing for each site
#
# Report_NTP_not_syncd is the number of syncs after which this program
# will report that NTP is not synchronized. It appears to be normal that
# NTP goes out of sync occassionally. Consequently, we only report
# when successive executions of this program find NTP out of sync.
$Report_NTP_not_syncd = "2"; # no. nonsyncs after which reported
# if reset_clock_OK is nonzero, this program will attempt to reset the
# clock if it is more than max_clock_offset seconds from the reference
# site(s). In attempting to reset the clock, this program will
# stop NTP then do the reset, then restart NTP. If reset_clock_OK
# is zero, this program will not stop NTP, but if NTP is stopped,
# is will attept to restart it.
$reset_clock_OK = "1"; # set if you want clock reset
# set max_clock_offset to the maximum offset in seconds that you
# tolerate between the local clock and the reference site(s).
# This is a decimal number (e.g. 1.5 is OK too).
$max_clock_offset = "5"; # max clock offset in seconds
# To avoid the possibility of being innundated by error messages,
# set maxmail to the maximum number of mail messages that
# you want to receive during a problem period. Once NTP is running
# and the clock is synchronized, new mail will be sent on any
# subsequent problem.
$maxmail = 10; # max mail messages
# set mailto to the address where you want the mail messages sent
$mailto = "root"; # where to mail reports
# The following two files need to remain on the system during
# an error with NTP. They will be deleted after the error is
# resolved.
$mailcountf = "/tmp/saneDate.mailcount"; # mail sent count
$nosynccountf = "/tmp/saneDate.nosynccount"; # no synch count file
# Utility definitions -- normally should not need to be changed
$mailprog = "/bin/mail"; # location of mail
$dateoutf = "/tmp/saneDate.date"; # temp program output
$outf = "/tmp/saneDate.output"; # temp mail output file
$datefailmsg = "ntpdate command failed\: saneDate cannot do date-time sanity check\n";
$ntpqcmd = $ntpqdir . "ntpq -p"; # ntp query
$ntpdatequerycmd = $ntpdatedir . "ntpdate -q"; # remote date query
$ntpdatesetcmd = $ntpdatedir . "ntpdate "; # reset date
# return difference in seconds between local clock and remote clock(s).
sub clockdiff {
$notdebugging = 1;
if ($notdebugging) {
# issue ntpdate -q date/time query command
if (system("$ntpdatequerycmd $clocksites 2>$dateoutf >$dateoutf")) {
return 1; # error
}
}
# read output from ntpdate -q command
open(CONS, "cat $dateoutf |");
@cons = ;
close(CONS);
if ($#cons < 1) { # must have at least two lines
return 1; # nope error
}
$rdateo = $cons[$#cons-1];
chop($rdateo);
@ntpdate = split ' ', $rdateo;
if ($ntpdate[0] ne "server") { # second to last line starts with server
return 1; # nope error
}
$dateo = $cons[$#cons];
chop($dateo);
@ntpdate = split ' ', $dateo;
if ($ntpdate[8] ne "offset") {
return 1; # error
}
# offset is of form (-)111.2222 in seconds
$offset = $ntpdate[9]; # get time difference in secs
return 0; # OK
}
(-e ($ntpdatedir . "/ntpdate")) or die "Configuration problem: ntpdate program not found\n";
(-e ($ntpqdir . "/ntpq")) or die "Configuration problem: ntpq program not found\n";
(-e $ntp_conf) or die "Configuration problem: ntp.conf not found\n";
# get count of mail already sent
if (-e $mailcountf) {
$mailcount = `cat $mailcountf`;
} else {
$mailcount = 0;
}
# get count of times NTP not synced
if (-e $nosynccountf) {
$nosynccount = `cat $nosynccountf`;
} else {
$nosynccount = 0;
}
$me = `hostname`; # Host running this program
chop($me);
#take only machine name: machine.domain.top
$me=~ s/^(.*?)\..*$/$1/;
$clksites = "";
# if so configured, read the ntp.conf file and pickup the list
# of clocksites.
if ($read_ntp_conf) { # read sites from ntp.conf file
open(CONF, $ntp_conf);
@conf = ;
close(CONF);
for ($i = 0; $i <= $#conf; $i++) {
if (substr($conf[$i], 0, 6) eq "server") {
@words = split ' ', $conf[$i];
$clksites = $clksites . $words[1] . " ";
}
}
if ($clksites ne "") {
$clocksites = $clksites;
}
}
# get offset between local clock and reference site(s)
if (clockdiff()) {
# some error occurred
if ($mailcount < $maxmail) {
++$mailcount;
`echo $mailcount >$mailcountf`;
if ($mailcount == $maxmail) {
$subject = "\"" . $me . " ntpdate failure - Last warning\"";
} else {
$subject = "\"" . $me . " ntpdate failure\"";
}
`echo $datefailmsg >$outf`;
`echo $ntpdatequerycmd $clocksites >>$outf`;
`cat $dateoutf >>$outf`;
`cat $outf | $mailprog -s $subject $mailto`;
}
`rm -f $outf`;
`rm -f $dateoutf`;
exit 0;
}
$ntp_running = 0;
$notsyncd = 1;
$clock_reset = 0;
$ntp_stopped = 0;
$oldoffset = $offset;
# check if local NTP is running and is synchronized
`rm -f $dateoutf`;
`$ntpqcmd 2>$outf >$dateoutf`;
open(CONS, "cat $dateoutf |");
@cons = ;
close(CONS);
# if we get no lines (-1), NTP is probably not running
if ($#cons != -1) {
$ntp_running = 1;
for ($i = 0; $i <= $#cons; $i++) {
if (substr($cons[$i], 0, 1) eq "*") {
$notsyncd = 0; # we've got synchronization
last;
}
}
if ($notsyncd) {
# NTP is running but is not synchronized
++$nosynccount;
`echo $nosynccount >$nosynccountf`;
if ($nosynccount >= $Report_NTP_not_syncd) {
if ($mailcount < $maxmail) {
++$mailcount;
`echo $mailcount >$mailcountf`;
if ($mailcount == $maxmail) {
$subject = "\"" . $me . " NTP not synchronized - Last warning\"";
} else {
$subject = "\"" . $me . " NTP not synchronized\"";
}
`echo "NTP is not syncronized\n" >$outf`;
$msg = "\"Clock offset is: " . $offset . " seconds\n\"";
`echo $msg >>$outf`;
`cat $dateoutf >>$outf`;
`cat $outf | $mailprog -s $subject $mailto`;
`rm -f $outf`;
}
}
} else {
# NTP is synchronized, remove nosync file
`rm -f $nosynccountf`;
}
}
if (abs($offset) > abs($max_clock_offset)) { # we want to do something
if ($reset_clock_OK) {
# note, eventhough we may believe the NTP is not running
# we stop it anyway, if configured to do so
`$ntpstop >$outf`; # stop NTP
$ntp_stopped = 1;
}
if ($reset_clock_OK) {
# reset clock
`$ntpdatesetcmd $clocksites >$dateoutf`;
$clock_reset = 1;
}
}
if (!$ntp_running or $ntp_stopped) {
`$ntpstart >$outf`; # restart NTP
}
if (($clock_reset or !$ntp_running) and ($mailcount < $maxmail)) {
$msg = "\"NTP problem on " . $me . "\n\"";
`echo $msg >$outf`;
if (!$ntp_running) {
`echo "NTP was not running." >>$outf`;
}
$msg = "\"Clock offset for " . $me . " is: " . $offset . " seconds\n\"";
`echo $msg >>$outf`;
if ($ntp_stopped) {
`echo "NTP stopped.\n" >>$outf`;
}
if ($clock_reset) {
`echo "Clock reset.\n" >>$outf`;
`cat $dateoutf >>$outf`;
`echo "\n" >>$outf`;
}
if (!$ntp_running or $ntp_stopped) {
`echo "NTP restarted.\n" >>$outf`;
}
if ($clock_reset and !clockdiff()) {
$msg = "\"New clock offset for " . $me . " is: " . $offset . " seconds\n\"";
`echo $msg >>$outf`;
}
++$mailcount;
`echo $mailcount >$mailcountf`;
if ($mailcount == $maxmail) {
$subject = "\"" . $me . " NTP problem - Last warning\"";
} else {
$subject = "\"" . $me . " NTP problem\"";
}
`cat $outf | $mailprog -s $subject $mailto`;
}
# cleanup
`rm -f $outf`;
`rm -f $dateoutf`;
if (!$notsyncd and $ntp_running and !$clock_reset and (abs($oldoffset) <= abs($max_clock_offset))) {
# all is OK, so blowaway any mail count file
`rm -f $mailcountf`;
}