#!/usr/bin/perl 
#===========================================================#
# Simple Host Monitor					    #
# Copyright 2004 Chris Sawall	csawall@hotmail.com         #
# http://tech.stlsawall.com				    #
#							    #
$written = "02/03/04";					    #
$lastupdated = "02/10/04";				    #
$version = "1.0";					    #
$myemail = "sawall\@gmail.com"; 			    #
#===========================================================#
#===========================================================#
# COPYRIGHT NOTICE                                          #
# Copyright 2004 Chris Sawall  All Rights Reserved.         #
#                                                           #
# Simple Host Monitor (fwstat.pl) may be used and modified  #
# free of charge by anyone so long as this copyright notice #
# and the comments above remain intact.  By using this code #
# you agree to indemnify Chris Sawall from any liability    #
# that might arise from its use.	                    #
#							    #
# Selling the code for this program without prior written   #
# consent is expressly forbidden.  In other words,          #
# please ask first before you try and make money off of my  #
# program.                                                  #
# 							    #
# Obtain permission before redistributing this software     #
# over the Internet or in any other medium. In all cases    #
# copyright and header must remain intact. 		    #
#===========================================================#
#
# This main purpose of this script is to monitor
# serveral hosts, dump the info to a web page
# that can be viewed by anybody.  It will 
# let users see what's up or down.  This script can
# also send emails to notify if hosts are down
# after a defined period of time.
#========================================== 
use Net::Ping;
#=========================================
# begin user defined area
# 
# all other settings made in config file
#=========================================
$debug = 0;
$configfile = "fwstat.cfg";
#=========================================
# end user defined area
#=========================================
#=========================================
# get info from config file
#=========================================
if (-e $configfile) {open(CONFFILE, "$configfile") || die "Failed to read config\n";}
else {CreateCONFIG();} #if config file does exist, let's create one

if ($debug) {print "#### Show Config file details ###\n";}
while (<CONFFILE>) { 	
     chomp();
     next if (/^(#.*|\s*)$/); #skip comments and blank lines 	
     ($key, $val) = split(/\s*=\s*/, $_, 2);
     $key =~ tr/a-z/A-Z/;
     $$key = $val;
     if ($debug) {print "key = $key and val = $val\n";}
     if ($key =~ "EXCLUDE") 
      {
      next if ($EXCLUDE eq "");
      $val =~ tr/a-z/A-Z/;
      push(@excludes,$val);
      } 
     if ($key =~ "EMAILADDR") 
      {
      next if ($EMAILADDR eq "");
      push(@emailaddrs,$val);
      }
}
close(CONFFILE);
if($debug) {print "### Show All Exclude Data ###\n"; print "all exclude data => @excludes\n";}
if($debug) {print "### Show All Email ADDRs ###\n"; print "all email addr => @emailaddrs\n";}

# check for any hosts that were down previously
if (-e $DOWNFILE)
 {
 open (HOSTSDOWN, "$DOWNFILE") || die "Failed to read $DOWNFILE\n";
 while (<HOSTSDOWN>) {
	chomp();
	next if (/^(#.*|\s*)$/); #skip comments and blank lines
	($downhost,$downtimes) = split(/\s*=\s*/, $_, 2);
     	$HDH{$downhost}=$downtimes;
 }
   	if ($debug)
	 {
	 print "### Show Current HASH Values ###\n";
	 foreach $key (keys %HDH)
 	 {print "$key = $HDH{$key}\n";}
	 }

 close (HOSTSDOWN);
 }
#=========================================
# set current time and day var
#=========================================
($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
$year+=1900;
$mon+=1;
if($mon < 10) {$mon = 0 . $mon;}
if($mday < 10) {$mday = 0 . $mday;}
if($sec < 10) {$sec = 0 . $sec;}
if($min < 10) {$min = 0 . $min;}
if($hour < 10) {$hour = 0 . $hour;}
$currenttime = "$mon\/$mday\/$year\t$hour:$min:$sec";
if($debug) {print "###Show Current Time ###\n"; print "Time => $currenttime\n";}
#=========================================
# open firewall host info file
# 
# open output file 
#=========================================
if (-e $FWINFOFILE) {open(INFILE, "$FWINFOFILE")|| die "Failed to read FWINFO --> $FWINFOFILE\n";}
else {CreateFWINFO();} #if the host info file doesn't exist, let's create it

open(OUTPUT, ">$OUTFILE")|| die "Failed to open OUTPUT --> $OUTFILE\n";
#=========================================
# configure html header 
#=========================================
print OUTPUT "<Content-type: text/html>\n";
print OUTPUT "<HTML><HEAD><TITLE>$WEBTTL</TITLE></HEAD>\n";
print OUTPUT "<body bgcolor=$BDYBGND text=$BDYFGND> <font face=Courier size=2>\n";
print OUTPUT "<center><table border=0 cellpadding=4 cellspacing=2 width=\"100%\">\n";
print OUTPUT "<tr><td nowrap aligh=left>\n";
print OUTPUT "<font size=\"6\">$HEADING</font>\n";
print OUTPUT "&nbsp;&nbsp;<small>As of $currenttime</small><br>\n";
#=========================================
# set vpn and fw headings to 1 so the 
# print the first time their loop starts 
#=========================================
$vpnhead =1; $fwhead = 1;
#=========================================
# parse the host information file 
#=========================================
if ($debug) {print "### Show HOST Information ###\n";}
if ($debug) {print "HW Type --- HOST --- Hostname --- Location --- Description\n";}
if ($debug) {print "----------------------------------------------------------\n";}
while (<INFILE>)
{
chomp($_);
next if (/^(#.*|\s*)$/); #skip comments and blank lines
@data = split $FWDELIMIT,$_;
$htype = $data[0];
$host = $data[1];
$host =~ tr/a-z/A-Z/;
$hostname = $data[2];
$location = $data[3];
$descrip = $data[4];
$hostcheck = 1;
if($debug) {print "$htype -- $host -- $hostname -- $location -- $descrip\n";}

# if the host is in the exclude list, don't waste time in scanning for it
foreach $exchost (@excludes) 
{
if($debug) {if ($exchost =~ $host) {print "exchost = $exchost and host = $host\n";}}
if ($exchost eq $host) {$hostcheck = 0;} 
}
# if hostcheck is true, check to make sure the host is still up.
if ($hostcheck)
 {
 $hoststat = 0;
 $p = Net::Ping->new("icmp") || die "can't create new ping, $!";
 if($p->ping($host))  { $hoststatus = "UP"; $hoststat = 1; }
 else { $hoststatus = "DOWN"; $hoststat = 0; }
 $p->close();
 if($debug) {print "host => $host -- $hoststatus => $hoststatus --- hoststat = $hoststat\n";}

# if host was previously down,but is now up
# remove it from the "down" hosts file. 
 if ($hoststatus eq "UP")
  {
  foreach $key (keys %HDH)
   {
   if ($key eq $host) 
    {
    delete($HDH{$key});
    }
   }
  }

# if host was down, and is still down
# increase times down count
 if ($hoststatus eq "DOWN")
  {
  $keynotfound=1; #if host is down, but wasn't before, make sure it adds it
  foreach $key (keys %HDH)
   {
   if ($key eq $host) 
    {
    $HDH{$key}++;
    if($debug) {print "### HASH INFO FOR HOST DOWN ###\n"; print "$key = $HDH{$key}\n";}
    if ($HDH{$key} == $MAXDOWNTIMES)
     {
     if ($NOTIFY eq "on") 
      {
      $downmins = $MAXDOWNTIMES * $PINGINTERVAL;
      `echo $DOWNMSG |$MAILPROG -s "Host $host has been done for $MAXDOWNTIMES ping attempts!" @emailaddrs`;
      }
     delete($HDH{$key});
     }
    $keynotfound=0;
    }
   }
# if host is down, but wasn't before, add host to down hash
  if ($keynotfound)
   {
   if ($debug) {print "HASH INFO FOR HOST DOWN ###\n"; print "Host $host is down.  Adding to hash.\n";}
   $HDH{$host}=1;
   }
  }
 }

# if the config files says to monitor the vpn hosts and hostcheck is true, check the status  
if (($MONITORVPNS eq "on") && ($hostcheck)) 
{
 if ($htype eq "v")
 {
  if ($vpnhead)
   {
   $vpnhead = 0; # set to zero so this heading only prints once
   #=========================================
   # heading for vpn info 
   #=========================================
   print OUTPUT "<br><font size=\"5\">$VPNSHEADING</font>\n";
   print OUTPUT "<table height=100% width=\"100%\"><tr><td valign=\"top\"><center>\n";
   print OUTPUT "<TABLE BORDER=0 WIDTH=\"100%\">\n";
   print OUTPUT "<TR><TD><strong>$VPNSTATUSTTL</strong></TD><TD><strong>$VPNNAME</strong></TD>\n";
   print OUTPUT "<TD><strong>$VPNLOCATIONTTL</strong></TD><TD><strong>$VPNDESCRIPTTL</strong></TD></TR>\n";
   }

  $result = `$NMAP $PORT $PROTOCOL $host`;
  $isakmp=0;
  if ($result =~ /open/) {$isakmp=1; $isakmpstatus="UP";}
  if ($result =~ /closed/) {$isakmp=0; $isakmpstatus="DOWN";}
  
  if($hoststat) {print OUTPUT "<TR><TD><font color=\"$PINGDEVICEUPCOLOR\">$hoststatus</font></TD>\n";}
  else {print OUTPUT "<TR><TD><font color=\"$PINGDEVICEDOWNCOLOR\">$hoststatus</font></TD>\n";}
  if($hoststat) {print OUTPUT "<TD>$hostname</TD>\n";}
  else {print OUTPUT "<TD><font color=\"$PINGDEVICEDOWNCOLOR\">$hostname</font></TD>\n";}
  print OUTPUT "<TD>$location</TD><TD>$descrip</TD></TR>\n";
  if($isakmp)
   {
   print OUTPUT "<TR><TD>$isakmpstatus</TD>\n";
   print OUTPUT "<TD>$hostname</TD>\n";
   print OUTPUT "<TD>&nbsp;</TD><TD><font color=\"$IPSECUPCOLOR\">$IPSECUPINFO</font></TD></TR>\n";
   }
  else
   {
   print OUTPUT "<TR><TD><font color=\"$IPSECDOWNCOLOR\">$isakmpstatus</font></TD>\n";
   print OUTPUT "<TD><font color=\"$PINGDEVICEDOWNCOLOR\">$hostname</font></TD>\n";
   print OUTPUT "<TD>&nbsp;</TD><TD><font color=\"$IPSECDOWNCOLOR\">$IPSECDOWNINFO</font></TD></TR>\n";
   }
 }
}

# if the config files says to monitor the firewall and hostcheck is true, check the status  
if (($MONITORFWS eq "on") && ($hostcheck))
{
 if ($htype eq "f")
  {
  if ($fwhead) 
    {
    $fwhead=0; # set to zero so this heading only prints once
    #=========================================
    # if vpn data was published, we need to
    # close the table before starting fw info
    #=========================================
    if (!$vpnhead) 
     {
     print OUTPUT "</table></center>\n";
     }
    #=========================================
    # heading for firewall info
    #=========================================
    print OUTPUT "<br><font size=\"5\">$FWSHEADING</font>\n";
    print OUTPUT "<table height=100% width=\"100%\"><tr><td valign=\"top\"><center>\n";
    print OUTPUT "<TABLE BORDER=0 WIDTH=\"100%\">\n";
    print OUTPUT "<TR><TD><strong>$FWSTATUSTTL</strong></TD><TD><strong>$FIREWALLNAME</strong></TD>\n";
    print OUTPUT "<TD><strong>$FWLOCATIONTTL</strong></TD><TD><strong>$FWDESCRIPTTL</strong></TD></TR>\n";
    }
 
  if($hoststat) {print OUTPUT "<TR><TD><font color=\"$PINGDEVICEUPCOLOR\">$hoststatus</font></TD>\n";}
  else {print OUTPUT "<TR><TD><font color=\"$PINGDEVICEDOWNCOLOR\">$hoststatus</font></TD>\n";}
  if($hoststat) {print OUTPUT "<TD><font color=\"$PINGDEVICEUPCOLOR\">$hostname</font></TD>\n";}
  else {print OUTPUT "<TD><font color=\"$PINGDEVICEDOWNCOLOR\">$hostname</font></TD>\n";}
  print OUTPUT "<TD>$location</TD><TD>$descrip</TD></TR>\n";
  }
 }
}
close(INFILE);
print OUTPUT "</table></center>\n";

#=========================================
# Check to see if EXCLUDE data should be diplayed.
#=========================================
if ($SHOWEXCLUDES eq "on")
 {
 print OUTPUT "<h4>EXCLUDE DATA</h4>\n";
 print OUTPUT "$SHOWEXCMSG<br><br>\n";
 if (@excludes == "") {print OUTPUT "No EXCLUDE Data found.<br><br>\n";}
 
 foreach $exchost (@excludes)
  {
  print OUTPUT "$exchost<br>\n";
  }
 }

print OUTPUT "</body></html>\n";

close(OUTPUT);

if ($debug)
 {
 print "### Show Current HASH Values ###\n";
 foreach $key (keys %HDH)
 {print "$key = $HDH{$key}\n";}
 }

# rewrite the downed hosts file for next time
open(HOSTSDOWN, ">$DOWNFILE") || die "Failed to open $DOWNFILE\n" ;
foreach $key (keys %HDH)
 {
 print HOSTSDOWN "$key = $HDH{$key}\n";
 }
close(HOSTSDOWN);

if ($debug) {print "### SCRIPT DONE ####\n";}

#### SUB ROUTINES FOR CREATING CONFIG FILES ###
sub CreateCONFIG
{
$defaultcc = "Y";
print "\n"x3;
print "The config file $configfile does not exist.\n";
print "Would you like to create the configuration file? [$defaultcc/n]: ";
chomp($ccanswer = <STDIN>);
$ccanswer =~ tr/a-z/A-Z/;
$ccanswer = $defaultcc unless $ccanswer;

if ($debug) {print "ccanswer is $ccanswer\n";}
if ($ccanswer eq "N") {exit 0;}
if ($ccanswer eq "Y")
 {
 open(CCFILE, ">$configfile");

 print CCFILE <<EOM;
# Main Configuration File for Simple Host Monitor
# Written by Chris Sawall ($myemail)
# Written: $written 
# Last Updated: $lastupdated
# Version: $version
#
# 
#===========================================
# Turn on/off functionality to monitor
# firewalls or vpns.  Turn on/off
# function to email admins if a host is down
#===========================================
MONITORVPNS = on
MONITORFWS = on
NOTIFY = on
#===========================================
# Configure location of mail and nmap programs
#
# Use the complete path to the program directory.
# The PORT setting defines the port in which to
# scan and the PROTOCOL set either UDP or TCP.
# The default IKE port is UDP 500.
#
# If MONITORVPNS is set to "off", then the
# NMAP, PORT and PROTOCOL settings are irrelevant
#
# If NOTIFY is set to "off", then the MAILPROG
# and DOWNMSG settings are irrelevant
#===========================================
MAILPROG = /bin/mail
DOWNMSG = HOST IS DOWN.
NMAP = /usr/bin/nmap
PORT = -p 500
PROTOCOL = -sU
#===========================================
# Define file locations.  This should be the
# full path to the files location.
#
# DOWNFILE -> file to store and track down hosts
# OUTFILE -> file to write output of findings
# FWINFOFILE -> file containing info on hosts to scan
# FWDELIMIT -> define how FWINFOFILE is delimited
#===========================================
DOWNFILE = /tmp/down.hosts
OUTFILE = /var/www/html/fwstat1.html
FWINFOFILE = /root/firewall.hosts
FWDELIMIT = :
#===========================================
# Define users to email of hosts are down
# Define as many as needed by simply adding
# another line.  Blank lines are ignored.
#===========================================
EMAILADDR = fwadmin\@yourdomain.com
EMAILADDR =
#===========================================
# Ping Interval in minutes as set in your Crontab
#
# Example Crontab Line:
# */4 * * * * /root/fwstat.pl
#
#===========================================
PINGINTERVAL = 4
#===========================================
# Define maximum number of times a host can
# be found down before notifying somebody
#===========================================
MAXDOWNTIMES = 3
#===========================================
# Define hosts that should be ignored
#
# This can be as many as needed, just add
# another EXCLUDE statement. Blank lines
# are ignored.
#
# The main purpose for this setting is so that
# a host can be defined within the FWINFOINFO
# file, but be temporarily ignored.  Good for
# times when a host is known to be down for
# maintenance, but you do not want to always
# be notified.
#===========================================
EXCLUDE = FIREWALL03
EXCLUDE =
#===========================================
# Show the exclude data with the result data
#
# Set SHOWEXCLUDES to on or off.
# Set the message to be displayed for these
# excluded hosts
#===========================================
SHOWEXCLUDES = on
SHOWEXCMSG = The following hosts are NOT being monitored:
#===========================================
# Configure information about the website
#===========================================
# Default body and text colors
BDYBGND = black
BDYFGND = white
# Main Title and Heading
WEBTTL = Firewall and VPN Status
HEADING = Firewall and VPN Status
# Color to display hostname in if host is found
# to be up or down
PINGDEVICEUPCOLOR = white
PINGDEVICEDOWNCOLOR = red
# Settings for info about firewalls
FWSHEADING = Cisco PIX Firewalls
FWSTATUSTTL = Status
FIREWALLNAME = PIX Name
FWLOCATIONTTL = Location
FWDESCRIPTTL = Description
# Settings for info about vpns
VPNSHEADING = VPN Devices
VPNSTATUSTTL = Status
VPNNAME = VPN Name
VPNLOCATIONTTL = Location
VPNDESCRIPTTL = Description
IPSECUPINFO = IPSEC is Listening
IPSECDOWNINFO = IPSEC is NOT Listening
IPSECUPCOLOR = yellow
IPSECDOWNCOLOR = red
### END ###
EOM

close(CCFILE);
print "\n"x5;
print "Configuration file $configfile, has been created.\nPlease check settings.\n\n";
exit 0;
 }
}

sub CreateFWINFO
{
$defaultcc = "Y";
print "\n"x3;
print "The host info file $FWINFOFILE does not exist.\n";
print "Would you like to create the host information file? [$defaultcc/n]: ";
chomp($ccanswer = <STDIN>);
$ccanswer =~ tr/a-z/A-Z/;
$ccanswer = $defaultcc unless $ccanswer;

if ($debug) {print "ccanswer is $ccanswer\n";}
if ($ccanswer eq "N") {exit 0;}
if ($ccanswer eq "Y")
 {
 open(CCFWFILE, ">$FWINFOFILE");

 print CCFWFILE <<EOM;

# Main Host Information file for Simple Host Monitor
# Written by Chris Sawall ($myemail)
# Written: $written
# Last Updated: $lastupdated 
# Version: $version
#
#
# This file needs to contain all of the information for the hosts
# that will be monitored.  The delimiter is set in the main
# configuration file.  The default delimiter is a colon (:).
#
# The first varilable must define the host to be either a VPN device
# or a firewall.  Use "v" for VPN or "f" for firewall.
#
# The basic format of each line should be:
# hosttype{v/f}:host ip or name:hostname to display:location:description
#
# Examples:
# v:10.10.6.4:Cisco 3015:STL - Main Office:Cisco 3015 Client VPN
# f:192.168.232.2:FIREWALL01:STL - Tucker Street:Internet GW, Mail DMZ
# f:firewall02:FIREWALL02:KCMO:Field Office, Internet Connection
#
#=================================================================
EOM
close(CCFWFILE);
print "\n"x5;
print "Host Infomation file $FWINFOFILE, has been created.\nPlease enter host settings.\n\n";
exit 0;
 }
}