#!/usr/bin/perl -wT

# (c)1999 Ian Cass, Knowledge Matters Ltd.
# Read the GNU copyright stuff for all the legalese
#
# Check NTP time servers plugin. This plugin requires the ntpdate utility to
# be installed on the system, however since it's part of the ntp suite, you 
# should already have it installed.
#
# $Id: check_ntp.pl,v 1.19 2003/10/14 02:40:31 sghosh Exp $
# 
# Nothing clever done in this program - its a very simple bare basics hack to
# get the job done.
#
# Things to do...
# check @words[9] for time differences greater than +/- x secs & return a
# warning.
#
# (c) 1999 Mark Jewiss, Knowledge Matters Limited
# 22-9-1999, 12:45
#
# Modified script to accept 2 parameters or set defaults.
# Now issues warning or critical alert is time difference is greater than the 
# time passed.
#
# These changes have not been tested completely due to the unavailability of a
# server with the incorrect time.
#
# (c) 1999 Bo Kersey, VirCIO - Managed Server Solutions <bo@vircio.com>
# 22-10-99, 12:17
#
# Modified the script to give useage if no parameters are input.
#
# Modified the script to check for negative as well as positive 
# time differences.
#
# Modified the script to work with ntpdate 3-5.93e Wed Apr 14 20:23:03 EDT 1999
#
# Modified the script to work with ntpdate's that return adjust or offset...
#
#
# Script modified 2000 June 01 by William Pietri <william@bianca.com>
#
# Modified script to handle weird cases:
#     o NTP server doesn't respond (e.g., has died)
#     o Server has correct time but isn't suitable synchronization
#           source. This happens while starting up and if contact
#           with master has been lost.
#
# Modifed to run under Embedded Perl  (sghosh@users.sf.net)
#   - combined logic some blocks together..
# 
# Added ntpdate check for stratum 16 desynch peer (James Fidell) Feb 03, 2003
#
# ntpdate - offset is in seconds
# changed ntpdc to ntpq - jitter/dispersion is in milliseconds
#
# Patch for for regex for stratum1 refid.

require 5.004;
use POSIX;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw(:DEFAULT :standard :thresholds);

use vars qw($opt_H $opt_t $opt_w $opt_c $opt_j $opt_k $opt_v $PROGNAME $def_jitter);

use utils qw(%ERRORS);

$w_opt->default(60);
$w_opt->description("Clock offset in seconds at which a warning message will be generated");
$c_opt->default(120);
$c_opt->description("Clock offset in seconds at which a critical message will be generated");
my $j_opt = new Plugin::Parameter(-name => "jwarn", -flags => [ 'j', 'jwarn' ],
				  -optional => "yes", -valueoptional => "no", -type => "INTEGER",
				  -default => 5000,
				  -description => "Clock jitter in milliseconds at which a warning message will be generated");
my $k_opt = new Plugin::Parameter(-name => "jcrit", -flags => [ 'k', 'jcrit' ],
				  -optional => "yes", -valueoptional => "no", -type => "INTEGER",
				  -default => 10000,
				  -description => "Clock jitter in milliseconds at which a critical message will be generated");
my $plugin = new Plugin(-revision => '$Revision: 1.19 $',
			-copyright => "2003 Bo Kersey/Karl DeBisschop, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment => "Checks the local timestamp offset versus <host> with ntpdate. Check the jitter/dispersion of clock signal between <host> and its sys.peer with ntpq",
			-longcomment => "If jitter/dispersion is specified with -j or -k and ntpq times out, then a warning is returned",
			-parameterlists => [ [ $H_opt, $w_opt, $c_opt, $j_opt, $k_opt, $t_opt, $v_opt ], $h_opts, $V_opts ]);
$ENV{'PATH'}='';
$ENV{'BASH_ENV'}='';
$ENV{'ENV'}='';

$plugin->init();

# jitter test params specified
if (defined $opt_j || defined $opt_k ) {
	$def_jitter = 1;
}


if ($opt_c < $opt_w ) {
	$plugin->usage();
	usage("$PROGNAME UNKNOWN: Critical offset should be larger than warning offset\n");
}

if ($def_jitter) {
	if ($opt_k < $opt_j) {
		$plugin->usage();
		usage("$PROGNAME UNKNOWN: Critical jitter should be larger than warning jitter\n");
	}
}

my $stratum = -1;
my $ignoreret = 0;
my $answer = undef;
my $offset = undef;
my $jitter = undef;
my $syspeer = undef;
my $candidate = 0;
my @candidates;
my $msg; # first line of output to print if format is invalid

my $state = $ERRORS{'UNKNOWN'};
my $ntpdate_error = $ERRORS{'UNKNOWN'};
my $jitter_error = $ERRORS{'UNKNOWN'};

# some systems don't have a proper ntpq  (migrated from ntpdc)
my $have_ntpq = undef;
if ($utils::PATH_TO_NTPQ && -x $utils::PATH_TO_NTPQ ) {
	$have_ntpq = 1;  
}else{
	$have_ntpq = 0;
}

$plugin->start_timeout($opt_t, "No response from ntp server (alarm)");

###
###
### First, check ntpdate
###
###

if (!open (NTPDATE, "$utils::PATH_TO_NTPDATE -q $opt_H 2>&1 |")) {
        $plugin->usage();
	usage("$PROGNAME UNKNOWN: Could not open ntpdate\n");
}

while (<NTPDATE>) {
	print if ($opt_v);
	$msg = $_ unless ($msg);
	
	if (/stratum\s(\d+)/) {
		$stratum = $1;
	}
	
	if (/(offset|adjust)\s+([-.\d]+)/i) {
		$offset = $2;

		# An offset of 0.000000 with an error is probably bogus. Actually,
		# it's probably always bogus, but let's be paranoid here.
		if ($offset == 0) { undef $offset;}

		$ntpdate_error = defined ($offset) ? $ERRORS{"OK"} : $ERRORS{"CRITICAL"};
		print "ntperr = $ntpdate_error \n" if $opt_v;
	
	}

	if (/no server suitable for synchronization found/) {
		if ($stratum == 16) {
			$ntpdate_error = $ERRORS{"WARNING"};
			$msg = "Desynchronized peer server found";
			$ignoreret=1;
		}
		else {
			$ntpdate_error = $ERRORS{"CRITICAL"};
			$msg = "No suitable peer server found - ";
		}
	}

}

close (NTPDATE); 
# declare an error if we also get a non-zero return code from ntpdate
# unless already set to critical
if ( $? && !$ignoreret ) {
	print "stderr = $? : $! \n" if $opt_v;
	$ntpdate_error = $ntpdate_error == $ERRORS{"CRITICAL"} ? $ERRORS{"CRITICAL"} : $ERRORS{"UNKNOWN"}  ;
	print "ntperr = $ntpdate_error : $!\n" if $opt_v;
}

###
###
### Then scan xntpq/ntpq if it exists
### and look in the 11th column for jitter 
###
# Field 1: Tally Code ( Space, 'x','.','-','+','#','*','o')
#           Only match for '*' which implies sys.peer 
#           or 'o' which implies pps.peer
#           If both exist, the last one is picked. 
# Field 2: address of the remote peer
# Field 3: Refid of the clock (0.0.0.0 if unknown, WWWV/PPS/GPS/ACTS/USNO/PCS/... if Stratum1)
# Field 4: stratum (0-15)
# Field 5: Type of the peer: local (l), unicast (u), multicast (m) 
#          broadcast (b); not sure about multicast/broadcast
# Field 6: last packet receive (in seconds)
# Field 7: polling interval
# Field 8: reachability resgister (octal) 
# Field 9: delay
# Field 10: offset
# Field 11: dispersion/jitter
# 
# According to bug 773588 Some solaris xntpd implementations seemto match on
# "#" even though the docs say it exceeds maximum distance. Providing patch
# here which will generate a warining.

if ($have_ntpq) {

	if ( open(NTPQ,"$utils::PATH_TO_NTPQ -np $opt_H 2>&1 |") ) {
		while (<NTPQ>) {
			print $_ if ($opt_v);
			if ( /timed out/ ){
				$have_ntpq = 0 ;
				last ;
			}
			# number of candidates on <host> for sys.peer
			if (/^(\*|\+|\#|o])/) {
				++$candidate;
				push (@candidates, $_);
				print "Candiate count= $candidate\n" if ($opt_v);
			}
			
			# match sys.peer or pps.peer
			if (/^(\*|o)([-0-9.\s]+)\s+([-0-9A-Za-z.]+)\s+([-0-9.]+)\s+([lumb-]+)\s+([-0-9m.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)/) {
				$syspeer = $2;
				$stratum = $4;
				$jitter = $11;
				print "match $_ \n" if $opt_v;
				if ($jitter > $opt_k) {
					print "Jitter_crit = $11 :$opt_k\n" if ($opt_v);
					$jitter_error = $ERRORS{'CRITICAL'};
				} elsif ($jitter > $opt_j ) {
					print "Jitter_warn = $11 :$opt_j \n" if ($opt_v);
					$jitter_error = $ERRORS{'WARNING'};
				} else {
					$jitter_error = $ERRORS{'OK'};
				}
			}
			
		}
		close NTPQ;

		# if we did not match sys.peer or pps.peer but matched # candidates only
		# generate a warning 
		# based on bug id 773588
		unless (defined $syspeer) {
			if ($#candidates >0) {
				foreach my $c (@candidates) {
					$c =~ /^(#)([-0-9.\s]+)\s+([-0-9A-Za-z.]+)\s+([-0-9.]+)\s+([lumb-]+)\s+([-0-9m.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)\s+([-0-9.]+)/;
					$syspeer = $2;
					$stratum = $4;
					$jitter = $11;
					print "candidate match $c \n" if $opt_v;
					if ($jitter > $opt_k) {
						print "Candidate match - Jitter_crit = $11 :$opt_k\n" if ($opt_v);
						$jitter_error = $ERRORS{'CRITICAL'};
					}elsif ($jitter > $opt_j ) {
						print "Candidate match - Jitter_warn = $11 :$opt_j \n" if ($opt_v);
						$jitter_error = $ERRORS{'WARNING'};
					} else {
						$jitter_error = $ERRORS{'WARNING'};
					}
				}

			}
		}
	}
}


if ($ntpdate_error != $ERRORS{'OK'}) {
	$state = $ntpdate_error;
	if ($ntpdate_error == $ERRORS{'WARNING'} ) {
		$answer = $msg . "\n";
	}
	else {
		$answer = $msg . "Server for ntp probably down\n";
	}

	if (defined($offset) && abs($offset) > $opt_c) {
		$state = $ERRORS{'CRITICAL'};
		$answer = "Server Error and offset $offset sec > +/- $opt_c sec\n";
	} elsif (defined($offset) && abs($offset) > $opt_w) {
		$answer = "Server error and offset $offset sec > +/- $opt_w sec\n";
	} elsif (defined($jitter) && abs($jitter) > $opt_k) {
		$answer = "Server error and jitter $jitter msec > +/- $opt_k msec\n";
	} elsif (defined($jitter) && abs($jitter) > $opt_j) {
		$answer = "Server error and jitter $jitter msec > +/- $opt_j msec\n";
	}

} elsif ($have_ntpq && $jitter_error != $ERRORS{'OK'}) {
	$state = $jitter_error;
	$answer = "Jitter $jitter too high\n";
	if (defined($offset) && abs($offset) > $opt_c) {
		$state = $ERRORS{'CRITICAL'};
		$answer = "Jitter error and offset $offset sec > +/- $opt_c sec\n";
	} elsif (defined($offset) && abs($offset) > $opt_w) {
		$answer = "Jitter error and offset $offset sec > +/- $opt_w sec\n";
	} elsif (defined($jitter) && abs($jitter) > $opt_k) {
		$answer = "Jitter error and jitter $jitter msec > +/- $opt_k msec\n";
	} elsif (defined($jitter) && abs($jitter) > $opt_j) {
		$answer = "Jitter error and jitter $jitter msec > +/- $opt_j msec\n";
	}

} elsif( !$have_ntpq ) { # no errors from ntpdate and no ntpq or ntpq timed out
	if (abs($offset) > $opt_c) {
		$state = $ERRORS{'CRITICAL'};
		$answer = "Offset $offset msec > +/- $opt_c sec\n";
	} elsif (abs($offset) > $opt_w) {
		$state = $ERRORS{'WARNING'};
		$answer = "Offset $offset msec > +/- $opt_w sec\n";
	} elsif (( abs($offset) > $opt_w) && $def_jitter ) {
		$state = $ERRORS{'WARNING'};
		$answer = "Offset $offset msec > +/- $opt_w sec, ntpq timed out\n";
	} elsif ( $def_jitter ) {
		$state = $ERRORS{'WARNING'};
		$answer = "Offset $offset secs, ntpq timed out\n";
	} else{
		$state = $ERRORS{'OK'};
		$answer = "Offset $offset secs \n";
	}



} else { # no errors from ntpdate or ntpq
	if (abs($offset) > $opt_c) {
		$state = $ERRORS{'CRITICAL'};
		$answer = "Offset $offset msec > +/- $opt_c sec, jitter $jitter msec\n";
	} elsif (abs($jitter) > $opt_k ) {
		$state = $ERRORS{'CRITICAL'};
		$answer = "Jitter $jitter msec> +/- $opt_k msec, offset $offset sec \n";
	} elsif (abs($offset) > $opt_w) {
		$state = $ERRORS{'WARNING'};
		$answer = "Offset $offset msec > +/- $opt_w sec, jitter $jitter msec\n";
	} elsif (abs($jitter) > $opt_j ) {
		$state = $ERRORS{'WARNING'};
		$answer = "Jitter $jitter msec> +/- $opt_j msec, offset $offset sec \n";

	} else {
		$state = $ERRORS{'OK'};
		$answer = "Offset $offset secs, jitter $jitter msec, peer is stratum $stratum\n";
	}
	
}

foreach my $key (keys %ERRORS) {
	if ($state==$ERRORS{$key}) {
		print ("$PROGNAME $key: $answer");
		last;
	}
}
exit $state;
