#!/usr/bin/perl -w # ISpy - a network monitoring tool # # Copyright (C), 2001 Alexander Hajnal # # You may distribute under the terms of the GNU General Public # License as specified in the file COPYING that comes with the # ISpy distribution. # # Sun Jul 29 19:00:53 EDT 2001 akh (ahajnal@interport.net) use IO::Socket; use Getopt::Long; use strict; use vars qw / $opt_config $opt_state $opt_reset $opt_test $opt_verbose $opt_help $opt_debug $opt_version /; use vars qw / $ISpyConf $ISpyState $alert $maxlen $alias $portname $portnum $watch $state $fail $status /; use constant FALSE => 0; use constant TRUE => 1; $opt_config = ''; $opt_state = ''; $opt_reset = FALSE; $opt_test = FALSE; $opt_help = FALSE; $opt_debug = FALSE; $opt_version= FALSE; Getopt::Long::Configure('no_ignore_case', 'no_auto_abbrev'); $opt_help = 1 unless (GetOptions( "config|c=s", "state|s=s", "reset|r!", "help|h|?!", "debug|d|verbose|v", "version|V")); if ($opt_version == TRUE) { print "ISpy v.1.0 Copyright (c) 2001 Alexander Hajnal ahajnal\@interport.net\n"; exit; } if ($opt_help == TRUE) { ShowUsage(); exit; } ($ISpyConf,$ISpyState) = GetFilenames($opt_config,$opt_state); DEBUG("config='$ISpyConf', state='$ISpyState'"); ($alert,$maxlen,$alias,$portname,$portnum,$watch) = ReadConfigFile($ISpyConf); ($state) = ReadStateFile($ISpyState); ($fail,$status)=CheckHosts($watch,$portnum); WriteStateFile($ISpyState, $status); SendReports($status,$state,$alert,$maxlen,$alias,$portname,$portnum) unless ($opt_reset); sub DEBUG { print "DEBUG: $_[0]\n" if ($opt_debug); } sub ShowUsage { print <$state" or die "Could not create state file $state\n"; close STATE; } elsif ( ! -f $state ) { die "$state is not a file"; } return ($config, $state); } else { die "Config file $config not found\n"; } } elsif (-e "$ENV{HOME}/ispy.conf") { # Look in home directory DEBUG("config file in ~/"); if ( ! -f "$ENV{HOME}/ispy.conf" ) { die "$ENV{HOME}/ispy.conf is not a file"; } $state = "$ENV{HOME}/ispy.state" unless ($state); if ( ! -e $state) { open STATE, ">$state" or die "Could not create state file $state\n"; close STATE; } elsif ( ! -f $state ) { die "$state is not a file"; } return ("$ENV{HOME}/ispy.conf", $state); } elsif (-e "/etc/ispy.conf") { # Look in /etc DEBUG("config file in /etc/"); $state = "/etc/ispy.state" unless ($state); if ( ! -e $state ) { open STATE, ">$state" or die "Could not create state file $state\n"; close STATE; } elsif ( ! -f $state ) { die "$state is not a file"; } return ("/etc/ispy.conf", $state); } elsif (-e "ispy.conf") { # Look in current directory DEBUG("config file in current directory"); $state = "ispy.state" unless ($state); if ( !-e $state ) { open STATE, ">$state" or die "Could not create state file $state\n"; close STATE; } elsif ( ! -f $state ) { die "$state is not a file"; } return ("ispy.conf", $state); } elsif ($state ne '') { # User supplied state file but not config file DEBUG("config file not found, attempting to derive"); if ($state =~ /^(.+)\/[^\/]+?$/) { # find state path $config = $1.'ispy.conf'; } else { $config = 'ispy.conf'; } if (-e $state) { if ( ! -f $state ) { die "$state exists but is not a file"; } if ( ! -e $config ) { die "Could not find configuration file $config\n"; } elsif ( ! -f $config ) { die "$config exists but is not a file"; } return ($config, $state); } else { die "No configuration or state files found\n"; } } die "ispy.conf and ispy.state not found (checked /etc, ~/ and current directory\n"; } sub ReadConfigFile { my $ISpyConf = $_[0]; DEBUG("reading config file"); my %alert = (); my %maxlen = (); my %alias = (); my %portname = (); my %portnum = (); my @watch = (); my $line; open CONF, $ISpyConf or die "Could not open configuration file '$ISpyConf'\n"; while ($line=) { $line =~ s/\r|\n//g; if ($line =~ /^\s*(#.*)?$/) { # NOOP } elsif ($line =~ /^alert\s+(\S.*)\s+(\S.*)$/) { # alert alias name@host $alert{$1} = $2; } elsif ($line =~ /^maxlen\s+(\S.*)\s+(\S.*)$/) { # maxlen account length # length = 0 -> no limit $maxlen{$1} = $2; } elsif ($line =~ /^alias\s+(\S.*)\s+(\S.*)$/) { # alias longname shortname $alias{$2} = $1; } elsif ($line =~ /^port\s+(\S.*)\s+(\S.*)$/) { # port number name $portname{$1} = $2; $portnum{$2} = $1; } elsif ($line =~ /^watch\s+(\S.*)\s+([^\/].*)\/([^\/]+$)/) { # watch host port/protocol push @watch, [$1,$2,$3]; } else { print STDERR "Bad configuration line: $line\n"; } } close CONF; return (\%alert,\%maxlen,\%alias,\%portname,\%portnum,\@watch); } sub ReadStateFile { my $ISpyState = $_[0]; DEBUG("reading state file"); my %state = (); my $line; open STATE, $ISpyState or die "Could not open configuration file '$ISpyState' for reading\n"; while ($line=) { $line =~ s/\r|\n//g; if ($line =~ /^\s*(#.*)?$/) { # NOOP } elsif ($line =~ /^(\S+)\s+:\s+(\S.*)\s+:\s+(\S.*)\s+:\s+(\S.*)$/) { # state host port status $state{"$2:$3:$4"}=$1; } else { print STDERR "Bad state file line: $line\n"; } } close STATE; return (\%state); } sub WriteStateFile { my ($ISpyState, $curstate) = @_; DEBUG("writing state file"); my ($host, $state); rename $ISpyState, "$ISpyState.bck"; open STATE, ">$ISpyState" or die "Could not open configuration file '$ISpyState' for writing\n"; print STATE "# ISpy state file v1.0\n"; print STATE "# This is a generated file, do not edit\n"; print STATE "# As of ".localtime().":\n"; foreach $host (sort keys %$curstate) { foreach $state(@{$$curstate{$host}}) { print STATE "$$state[0] : $$state[1] : $$state[2] : $$state[3]\n" } } close STATE; } sub CheckHosts { my ($watchlist,$portnum)=@_; DEBUG("checking hosts"); my %fail=(); my %status=(); my ($watch,$hostname,$SOCKET); foreach $watch(@$watchlist) { my ($host,$port,$protocol)=@$watch; if (defined $$alias{$host}) { $hostname = $$alias{$host}; } else { $hostname=$host; } $port = $$portnum{$port} if (defined $$portnum{$port}); unless ( $SOCKET=new IO::Socket::INET(PeerAddr => $hostname, PeerPort => $port, Proto => $protocol) ) { # failed to connect DEBUG("host: $host, port: $port, protocol: $protocol - FAIL"); push @{$fail{$host}}, $watch; push @{$status{$host}}, ['-', $$watch[0], $$watch[1], $$watch[2]]; } else { DEBUG("host: $host, port: $port, protocol: $protocol - OK"); push @{$status{$host}}, ['+', $$watch[0], $$watch[1], $$watch[2]]; } } return (\%fail,\%status); } sub SendReports { my ($curhash,$lasthash,$alert,$maxlen,$alias,$portname,$portnum) = @_; DEBUG("sending reports"); my $subject = 'ISpy status'; my $body=''; my $host=''; my $global_delta = FALSE; my $delta = FALSE; my ($watch, $line, $email, $text, $spec, $cur, $last); my %msg=(); my %curtext=(); my %shown=(); foreach $host (sort keys %$curhash) { foreach $watch(@{$$curhash{$host}}) { $spec="$$watch[1]:$$watch[2]:$$watch[3]"; $$lasthash{$spec} = '+' unless (defined $$lasthash{$spec}); DEBUG("Current: $$watch[0] $spec"); DEBUG("Last: ".$$lasthash{$spec}." $spec"); $cur=$$watch[0]; $last=$$lasthash{$spec}; if (($cur eq '+') && ($last eq '-')) { unless (defined $shown{$host}) { $shown{$host} = TRUE; foreach $email(keys %$alert) { $curtext{$email}.="$host:\n"; } } $text = "UP $$watch[2]\n"; $delta = TRUE; } elsif (($cur eq '-') && ($last eq '+')) { unless (defined $shown{$host}) { $shown{$host} = TRUE; foreach $email(keys %$alert) { $curtext{$email}.="$host:\n"; } } $text = "DOWN $$watch[2]\n"; $global_delta = TRUE; $delta = TRUE; } if ($delta == TRUE) { foreach $email(keys %$alert) { if (($$maxlen{$email} > 0) && (length($curtext{$email}) > $$maxlen{$email})) { SendMail($$alert{$email}, $curtext{$email}); $curtext{$email}="$host:\n$text"; } else { $curtext{$email}.=$text; } } } $delta = FALSE; } } if ($global_delta == TRUE) { foreach $email(keys %$alert) { SendMail($$alert{$email}, $curtext{$email}); } } } sub SendMail { my ($email, $text) = @_; my $subject = 'ISpy status'; DEBUG("sending email"); DEBUG("- S -----------------------------------------"); DEBUG("TO: $email"); DEBUG("SUBEJCT: $subject"); DEBUG("BODY:\n$text"); DEBUG("- E -----------------------------------------\n"); open MAIL, "|mail $email -s \"$subject\""; print MAIL $text; close MAIL; }