#!/usr/local/bin/perl -w # File: ntp.monitor # Author: Daniel Hagerty, hag@linnaean.org # Date: Sun Feb 20 00:38:21 2000 # Description: ntp monitor for mon. # # $Id$ # # Command Line options: # -s max_stratum # Raise an error if stratum is above this number. # -c # Raise an error if the read clock differs from the local clock # by more than 30 seconds. # ### use strict; use Symbol; use Getopt::Std; ### use vars qw($opt_s $opt_c); my $getopt_str = "s:c:"; ## # Hashref that we accumulate errors into my $error_accumulator; ### sub main { usage_error() if(@_ == 0); my @hosts = @_; foreach my $host (@hosts) { do_host_ntp_parse($host); } my $exitval = 0; $exitval = 1 if(dump_errs()); exit($exitval); } # Do a per host ntpq run. sub do_host_ntp_parse { my $host = shift; my $text; open(NTPQ, "ntpq -n -c rv $host 2>&1 |"); while() { $text .= $_; } close(NTPQ); while($text) { if($text =~ /([a-zA-Z0-9]+)=/) { # Standard NTPc output. Lookup the specialized handler for # this tag, or use the default if there isn't one. my $tag = $1; my $ref = qualify_to_ref($tag, "handlers"); my $code = *{$ref}{CODE}; my $textref = \$text; my $oldtext = $text; if(defined($code)) { # print "Calling special $tag\n"; my @val = &{$code}($textref); if($val[0] != 0) { accum_err($host, $val[1]); } } else { my @val = handler_default($textref); if($val[0] != 0) { accum_err($host, $val[1]); } } if($text eq $oldtext) { # We didn't do any work; we blew it. dump_errs(); die "XXX no work accomplished"; } } else { # NTP error of some type. if($text =~ m/Network is unreachable/) { accum_err($host, "Network unreachable"); return; } if($text =~ m/timed out, nothing received/) { accum_err($host, "Connection timed out; host down?"); return; } if($text =~ m/ntpq: read: Connection refused/) { accum_err($host, "Connection refused; daemon down?"); return; } if($text =~ m/^\*\*\*Can't find host / ) { accum_err($host, "DNS lookup failed"); return; } accum_err($host, "Unparsed error: $text"); return; } } } sub usage_error { my $progname = $0; $progname =~ s,.*/,,; print STDERR "Usage error: $progname [ -c ] [-s max_strat ] hosts ...\n"; exit 1; } ## sub dump_errs { my @hosts = sort keys(%{$error_accumulator}); my $host_str = join(" ", @hosts); return unless(@hosts); print "NTP check failed: $host_str\n"; foreach my $host (@hosts) { print "Host $host:\n"; map { print $_ } @{$error_accumulator->{$host}}; } } # Blow our brains out appropriately for a mon monitor sub accum_err { my $host = shift; my $errtext = shift; if($errtext eq "") { $errtext = "Program error, no text available\n"; } elsif(substr($errtext, length($errtext) - 1, 1) ne "\n") { $errtext .= "\n"; } push(@{$error_accumulator->{$host}}, $errtext); } sub handler_default { my $textref = shift; my $maybe_sep = shift; my $maybe_count = shift; my $used_seperator; my $do_sepsearch = sub { my $sep = shift; my $count = 1; if(defined($maybe_count)) { $count = $maybe_count; } my $sepindex; while($count--) { my @indexargs = ($$textref, $sep); if($sepindex) { push(@indexargs, $sepindex+1); } # I hate you for making me do this. my $perl_sucks = "'" . join("', '", @indexargs) . "'"; $sepindex = eval "index $perl_sucks"; return() if($sepindex == -1); } return($sepindex); }; my $sepindex; if(defined($maybe_sep)) { $sepindex = &{$do_sepsearch}($maybe_sep); $used_seperator = $maybe_sep; } else { # Least common first my @seperators = ("\n", ",\n", ", "); my @indexes; foreach my $trysep (@seperators) { push(@indexes, &{$do_sepsearch}($trysep)); } for(my $i = 0; $i < scalar(@seperators); $i++) { my $maybe_short = $indexes[$i]; if(defined($maybe_short)) { if(!defined($sepindex)) { $sepindex = $maybe_short; $used_seperator = $seperators[$i]; } elsif($maybe_short < $sepindex) { $sepindex = $maybe_short; $used_seperator = $seperators[$i]; } } } } die "XXX default handler parse failure" unless $sepindex; my $string = substr($$textref, 0, $sepindex); $string =~/([a-zA-Z0-9]+)=(.*)/; my $tag = $1; my $value = $2; die "XXX default handler parse failure" unless(defined($tag)); die "XXX default handler parse failure" unless(defined($value)); # print "Default($tag): $value\n"; my $newstringindex = $sepindex + length($used_seperator); if($newstringindex > length($$textref)) { return(1, "XXX default handler parse failure"); } else { $$textref = substr($$textref, $newstringindex); } return(0, $value); } sub handler_default_die { my @val = handler_default(@_); if($val[0] != 0) { die "XXX fatal parse failure"; } return(@val); } ### package handlers; use strict; use Time::ParseDate; use Time::Local; ## sub status { my $textref = shift; my @val = main::handler_default_die($textref, "\n"); my $status = $val[1]; if($status =~ /sync_unspec/) { return(1, "unsynchronized"); } return(0, ""); } sub stratum { my $textref = shift; my @val = main::handler_default_die($textref); my $stratum = $val[1]; if($stratum == 0) { return(1, "stratum(0) is insane"); } my $max_strat = defined($main::opt_s) ? $main::opt_s + 1 : 16; if($stratum >= $max_strat) { return(1, "stratum($stratum) is too high"); } return(0, ""); } sub reftime { my $textref = shift; my @val = main::handler_default_die($textref, ", ", 2); my $reftime = $val[1]; return(0, ""); } sub clock { my $textref = shift; my @val = main::handler_default_die($textref, ", ", 2); my $clock = $val[1]; if(defined($main::opt_c)) { $clock =~ m/([0-9a-f]{8}\.[0-9a-f]{8}) (.*)/ || die "Didn't understand clock"; my $remote_time = parsedate($2); my $local_time = timelocal(localtime(time)); if(abs($remote_time - $local_time) > $main::opt_c) { return(1, "local/remote clock difference too great"); } } return(0, ""); } ### package main; getopts($getopt_str) || usage_error(); &main(@ARGV); ###