client-check.pl

Author David Baldwin
Compatibility Xymon 4.2
Requirements Perl, XymonExt.pm
Download client-check.pl
Last Update 2010-07-29

Client side

Server side

client-check.pl and XymonExt.pm in $BBHOME/ext

client-check.pl

Show Code ⇲

Hide Code ⇱

#!/usr/bin/perl -w
 
#*---------------------------------------------------------------------------*/
#* client-check.pl : Xymon client message processor.                         */
#*                                                                           */
#* This perl program is a server-side module using the                       */
#* data sent by the Xymon clients. This program is fed data from the         */
#* Xymon "client" channel via the hobbitd_channel program; each client       */
#* message is processed by looking at various sections and generates         */
#* a test status report when various conditions are met                      */
#*                                                                           */
#* Original 2007-Jan-28 by Henrik Storner <henrik@hswn.dk>                   */
#* Modified 2008-2010 by David Baldwin <david.baldwin@ausport.gov.au>        */
#*                                                                           */
#* This program is in the public domain, and may be used freely for          */
#* creating your own Xymon server-side modules.                              */
#*                                                                           */
#*---------------------------------------------------------------------------*/
#
# CONFIG START
 
# default route where other than .1 address on subnet
my %netCheckRoutes = ( prod => { net => "1.1", mask => "/16", gw => "1.1.0.10"},
		     );
 
# check minimum available memory in host under [free] section
my %memCheckTable = ( esx => { host => "esx.*",  min => 799000},
		     );
 
# CONFIG END
 
=pod
 
=head1 NAME
 
 client-check.pl - Perl script to check client messages on xymon/hobbit server
 
=head1 SYNOPSIS
 
Install script in F<$BBHOME/ext/client-check.pl>
 
Ensure you also have F<$BBHOME/ext/XymonExt.pm> installed
 
Add following lines to F</etc/hobbit/hobbitlaunch.cfg>
 
 [client-check]
	ENVFILE /usr/lib/hobbit/server/etc/hobbitserver.cfg
	NEEDS hobbitd
	CMD hobbitd_channel --channel=client --log=$BBSERVERLOGS/client-check.log $BBHOME/ext/client-check.pl
 
=head1 DESCRIPTION
 
Requires: C<$BBHOME> environment variable to be set
 
Requires: F<XymonExt.pm> module
 
See CONFIG section at top of script
 
=head1 TESTING
 
If you wish to add your own tests, this script can be run against current client report for that host
 
C<bbcmd client-check.pl -d MYHOST>
 
 2010-07-29 14:04:25 Using default environment file /usr/lib/hobbit/server/etc/hobbitserver.cfg
 DBG: using clientlog for MYHOST (1.1.1.2)
 DBG: clientID linux (linux)
 /usr/lib/hobbit/server/bin/bb 1.1.1.1 "status MYHOST.who green OK
 
 &green No root login active
 
 
 someuser pts/1        Jul 26 12:29 (1.1.1.3)
 "
 
 DBG: netCheck MYHOST (1.1.1.1) GW: 1.1.0.10 SM: 0.0.0.0
 /usr/lib/hobbit/server/bin/bb 1.1.1.1 "status MYHOST green OK
 
 &green Default route OK
 
 
 Kernel IP routing table
 Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
 1.1.0.0         0.0.0.0         255.255.0.0     U         0 0          0 eth0
 0.0.0.0         1.1.0.10        0.0.0.0         UG        0 0          0 eth0
 "
 
=cut
 
my $xymonlib;
BEGIN { $xymonlib = $ENV{BBHOME} && "$ENV{BBHOME}/ext" || "/usr/lib/hobbit/server/ext"; }
 
use strict;
use lib $xymonlib;
use XymonExt;
 
 
my $bb;
my $bbdisp;
 
my $hostname = "";
my $clientip = "";
my $clientname = "";
my $clientos = "";
my $msgtxt = "";
my %sections = ();
my $cursection = "";
 
my $debug=0;
my $ipsubre = qr/\d|\d\d|1\d\d|2[0-4]\d|25[0-5]/;
my $ipre = qr/$ipsubre\.$ipsubre\.$ipsubre\.$ipsubre/;
$|=1;
 
# Get the BB and BBDISP environment settings.
$bb = $ENV{"BB"} || die "BB not defined";
$bbdisp = $ENV{"BBDISP"} || die "BBDISP not defined";
 
for(my $i=0; $i<=$#ARGV; $i++) {
  if($ARGV[$i] =~ /^-(d|-debug)$/) {
    $debug++;
  } elsif($i=$#ARGV) {
    $debug ||= 1;
    $clientname=$ARGV[$i];
  }
}
 
 
if($clientname ne "") {
  open CL,"bb $bbdisp \'clientlog $clientname\'|";
  $hostname = $clientname;
  $clientip = XymonExt->HostIP($hostname);
  warn "DBG: using clientlog for $hostname ($clientip)\n" if $debug;
} else {
  open CL,"/dev/stdin";
}
# Main routine. 
#
# This reads client messages from <CL>, looking for the
# delimiters that separate each message, and also looking for the
# section markers that delimit each part of the client message.
# When a message is complete, the processmessage() subroutine
# is invoked. $msgtxt contains the complete message, and the
# %sections hash contains the individual sections of the client 
# message.
 
while (my $line = <CL>) {
	if ($line =~ /^\@\@client\#/) {
		# It's the start of a new client message - the header looks like this:
		# @@client#830759/HOSTNAME|1169985951.340108|10.60.65.152|HOSTNAME|sunos|sunos
 
		# Grab the hostname field from the header
		my @hdrfields = split(/\|/, $line);
		$hostname = $hdrfields[3];
		$hostname =~ s/,/./g;
		XymonExt->import; # if don't do this, won't see changes to bb-hosts
		$clientip = XymonExt->HostIP($hostname);
		warn "DBG: processing $hostname ($clientip)\n" if $debug;
 
		# Clear the variables we use to store the message in
		$msgtxt = "";
		%sections = ();
		$cursection = ""; # none found yet!
                $clientname = "";
                $clientos = "";
	}
	elsif ($line =~ /^\@\@/) {
		# End of a message. Do something with it.
		runTests();
	}
	elsif ($line =~ /^\[(.+)\]/) {
		# Start of new message section.
 
		$cursection = $1;
		$sections{ $cursection } = "\n";
	}
	elsif ($line =~ /^client\s+\S+\.([^.]+)\s+(\w+)/) {
		# client header - used when testing against current client report
		#   client esxsv01,ausport,gov,au.linux linux
		#   client sa01sv1.ausport.gov.au.bbwin win32
 
		$msgtxt .= $line;
 
                $clientname = $1;
                $clientos = $2;
		warn "DBG: clientID $clientname ($clientos)\n" if $debug;
	}
	else {
		# Add another line to the entire message text variable,
		# and the the current section.
		$msgtxt .= $line;
		if($cursection) {
			$sections{ $cursection } .= $line;
		} else {
			my $time = scalar localtime;
			warn "$time client-check: no current section for client $hostname\n$line";
		}
	}
}
# End of a message. Do something with it. Will only get here using clientlog (we hope)
runTests();
 
sub runTests {
 
    whoCheck();
    netCheck();
    memCheck();
}
 
sub report {
 
	my($hostname, $hobbitcolumn, $color, $summary, $statusmsg) = @_;
	# Build the command we use to send a status to the Xymon daemon
 
	if($debug) {
	  print "Would report:\n\"status " . $hostname . "." . $hobbitcolumn . " " . $color . " " . $summary . "\n" . $statusmsg . "\"\n";
	} else {
	# And send the message
	   XymonExt->Report($hostname, $hobbitcolumn, $color,  "$summary\n$statusmsg");
	}
}
 
# This subroutine processes the client message. In this case,
# we watch the [who] section of the client message and alert
# if there is a root login active.
 
sub whoCheck {
	my $color;
	my $summary;
	my $statusmsg;
	my $sec = "who";
	my $hobbitcolumn = "who";
 
	# Dont do anything unless we have the "who" section
	return unless ( $sections{$sec} );
 
	# Is there a "root" login somewhere in the "who" section?
	# Note that we must match with /m because there are multiple
	# lines in the [who] section.
	if ( $sections{$sec} =~ /^root /m ) {
		$color = "yellow";
		$summary = "ROOT login active";
		$statusmsg = "&yellow ROOT login detected!\n\n" . $sections{$sec};
	}
	else {
		$color = "green";
		$summary = "OK";
		$statusmsg = "&green No root login active\n\n" . $sections{$sec};
	}
	report($hostname, $hobbitcolumn, $color, $summary, $statusmsg);
}
 
# This subroutine processes the client message. In this case,
# we watch the [route] section of the client message and alert
# if the default route is incorrect
 
sub netCheck {
	my $color;
	my $summary;
	my $statusmsg;
	my $sec = "route";
	my $hobbitcolumn = "route";
 
	# Dont do anything unless we have the "route" section
	return unless ( $sections{$sec} );
 
	# Is there a default gateway somewhere in the "route" section?
	# Note that we must match with /m because there are multiple
	# lines
	$color = "green";
	$summary = "OK";
	$statusmsg = "&green Default route OK\n\n" . $sections{$sec};
	#darwin form:
	# default            1.1.0.1            UGSc        6       91    en0
	# 1.1/16             link#4             UCS        40        0    en0
	# 1.1.0.1            0:11:11:11:11:87   UHLW        0        0    en0   1196
 
	if ( $sections{$sec} =~ /^(?:0\.0\.0\.0|default)\s+($ipre)\s+($ipre|\w+)\s+(\S+)\s/m ) {
		my $gw = ($clientname eq "bbwin") ? $2 : $1;
		my $sm = ($clientname eq "bbwin") ? $1 : $2;
		warn "DBG: netCheck $hostname ($clientip) GW: $gw SM: $sm\n" if $debug;
		my $isok = 1;
		my $found = 0;
		foreach my $sn (keys %netCheckRoutes) {
		    my %snr = %{$netCheckRoutes{$sn}};
		    warn "DBG: NET: $sn IP: $snr{net} GW: $snr{gw} SM: $snr{mask}\n" if $debug;
		    if(! exists $snr{ipre}) {
			my $netre = $snr{net};
			$netre =~ s/\./\\./g;
			$netre =~ s/\.$//;
		        $netCheckRoutes{$sn}{ipre} = qr/^$netre\./;
		    }
		    if( $clientip =~ $netCheckRoutes{$sn}{ipre} ) {
		        $found = 1;
			$isok = $gw eq $snr{gw};
			$summary = "Found defgw: $gw OK";
			last
		    }
		}
		if( !$found && $gw =~ /^(.*)\.1$/) {
		        my $net = $1;
		        warn "DBG: DEFAULT IP: $net GW: $gw SM: /24\n" if $debug;
			$net =~ s/\./\\./g;
			$isok = 0 unless $clientip =~ /^$net/;
			$summary = "Found defgw: $gw OK" if $isok;
		}
		if(!$isok) {
			$color = "red";
			$summary = "Bad default route $gw for $clientip found";
			$statusmsg = "&red Bad default route $gw for $clientip found\n\n" . $sections{$sec};
		}
	} else {
		$color = "red";
		$summary = "Error";
		$statusmsg = "&red No default route found\n\n" . $sections{$sec};
	}
 
	report($hostname, $hobbitcolumn, $color, $summary, $statusmsg);
}
 
# This subroutine processes the client message. In this case,
# we watch the [free] section of the client message and alert
# if the machine is an ESX server and the physical memory size is not 800MB
 
sub memCheck {
	my $color;
	my $summary;
	my $statusmsg;
	my $sec = "free";
	my $hobbitcolumn = "pmem";
	my $min;
 
	# Dont do anything unless we have the "free" section in host starting with "esx"
	return unless ( $sections{$sec} );
	my $found = 0;
	foreach my $mh (keys %memCheckTable) {
	    my %mhr = %{$memCheckTable{$mh}};
	    warn "DBG: HOST: $mh MIN: $mhr{min}\n" if $debug;
	    if(! exists $mhr{hostre}) {
		my $hostre = $mhr{host};
		$memCheckTable{$mh}{hostre} = qr/^$hostre\./;
	    }
	    $found = $hostname =~ $memCheckTable{$mh}{hostre};
	    $min = $mhr{min};
	    last if $found;
	}
	return unless ( $found );
 
	# Get the Mem: line
	# Note that we must match with /m because there are multiple
	# lines
	$color = "green";
	$summary = "OK";
 
	if ( $sections{$sec} =~ /^Mem:\s+(\d+)\s+(\d+)\s+(\d+)\s/m ) {
		my $tot = $1;
	        $statusmsg = sprintf("&green memory allocation %dMB OK (at least %dMB)\n\n",$tot/1024,$min/1024) . $sections{$sec};
		warn "DBG: memCheck $hostname ($clientip) PhysMEM: $tot\n" if $debug;
		my $isok = $tot > $min;
		if(!$isok) {
			$color = "red";
			$summary = sprintf("Bad memory allocation %dMB for %s found (require at least %dMB)",$tot/1024,$hostname,$min/1024);
			$statusmsg = "&red ". sprintf("Bad memory allocation %dMB for %s found (require at least %dMB)\n\n",$tot/1024,$hostname,$min/1024) . $sections{$sec};
		}
	} else {
		$color = "red";
		$summary = "Error";
		$statusmsg = "&red No Mem: line found\n\n" . $sections{$sec};
	}
 
	report($hostname, $hobbitcolumn, $color, $summary, $statusmsg);
}

original rootlogin.pl script from Henrik

  • 2010-07-29
    • Initial release
  • monitors/check-client.txt
  • Last modified: 2010/08/23 22:23
  • (external edit)