#!/usr/pkg/bin/perl 
#
# $Header: /home/vikas/src/nocol/perlnocol/RCS/armon,v 1.10 1999/11/01 13:35:22 vikas Exp $
#
# 	armon - perl nocol appletalk route monitor
#
# Date: September 21, 1993
# Programmer: John Wobus, jmwobus@mailbox.syr.edu
#  Modifications:  vikas@jvnc.net
#
#    (c) Syracuse University Computing & Network Services 1993
#
# No warranty is expressed or implied.  Permission to copy and use is
# extended to all.  Permission to redistribute is granted under the
# following conditions: it is not sold for profit; this copyright
# notice remains intact; the same permissions extend to the recipient;
# and if any changes are made, a notice is added so stating.
#
#####################
#
# Command Format:
#
#  armon
#
#    Automatically kills old process and forks a new one, reading
#    the configuration file in the process.
#
# What it does:
#
#    armon reads the AppleTalk routing table from a Cisco router and compares
#    it with its own configuration file and reports the differences
#    through nocol.  Nocol reports two kinds of problems:
#
#    Site      Address      Time   +-Variable-+ +-Value-+  Condition
#    ZONE1  12345 e9        09:50   Reg_ARoute        0    Critical
#    ZONE2  67890 e2        13:17   Unrg_ARoute       0    Critical
#
#    The first line states that a route to zone ZONE1 & Appletalk network
#    number 12345 through the router's interface Ethernet 9 is missing.
#    The second line states that a route has appeared to ZONE2 & Appletalk
#    network number 67890 through the router's interface Ethernet 2, but
#    this route is not listed in armon's configuration file.
#
#    It is important to remember that armon monitors the network as
#    it appears from one Cisco router in the network.  It will notice
#    and report many kinds of changes, but even so, its view is restricted.
#
# NOVELL Users:
#
#	The procedure to monitor Novell routes from a cisco is similar.
#	Just change the cisco command to 'show novell routes' and use
#	the alternate reg-expression in the 'doline'  subroutine.
#
# CAP ATLOOK Users:
#
#	Procedure is similar. Change the command to 'atlook' and
#	change the dotest() function to parse the alternate reg-exp
#	listed.
#
#
# Files used:
#
#   rcisco                      perl program to do a Cisco router
#                               command remotely.
#   nocol/data/armon-output     path to which to write nocol events.
#   armon-confg                 configuration file.
#   armon.pid                   file holding armon's current process id.
#
# Nocol event elements used:
#   sender                     "armon"
#   severity                   up: 4; down: 3,2,1;
#   nocop                      up, down, unknown, test
#   site
#     name                     the Appletalk zone name
#     addr                     the Appletalk network number or range of
#                               network numbers followed by space and
#                               the Router's ethernet port (e0, e1, etc).
#                               Example: "44123-44125 e8"
#   var                       
#     name                      either "Reg_AtalkRoute" or "Unrg ATalkRoute"
#     value                     0 if missing, 1 if present
#     threshold                 always 0
#     units                     always "Entry"
#
# To install this:
#   (1) Choose where to put this file and the above 4 files and
#       assign the perl variables below appropriately.
#   (2) Create your armon-confg file in its chosen place.
#   (3) Edit rcisco to include your Cisco router's password and put
#       rcisco in its chosen place. Preferably in 'rcisco' and not
#	in this file since otherwise a 'ps' will show the password.
#   (4) Put this file in its chosen place.
#   (5) Add appropriate code to rc.local to start this monitor.
#
# Configuration file format:
#    #<text>                                   Comment line.
#    router=<router>                           Name of Cisco router.
#    <netnum> e<intnum> <zone>                 Route via ethernet interface.
#    <netnum>-<netnum> s<intnum> <zone>        Phase 2 route via serial iface
#    test= <netnum> e<intnum> <zone>
#    test= <netnum>-<netnum> e<intnum> <zone>  Routes in test mode.
#
# Sample configuration file:
#    # armon configuration
#    router=mycisco.excellent.edu
#    12345 e1 ZONE1
#    # A Phase 2 net via ethernet 0:
#    11111-11112 e0 ZONE1
#    # A net we don't want to monitor (learn't via serial 1)
#    test=23456 s1 ZONE2
#
## 
##
#
#
############################
## Variables customization #  overrides values in the nocollib.pl library
############################
$rprog="./rcisco";			# Path for rcisco.
$rpasswd="";				# if NULL, uses the default in rcisco
$rcommand="show apple route";
$varname="Reg_ATalkRoute";		# Registered appletalk route
$varname_unreg="Unrg_ATalkRoute";	# Unregistered route
$varunits="Entry" ;			# the var.units field in EVENT struct
$sleepint=60*5;       			# Seconds to sleep between tries.
############################
$debug = 0;				# set to 1 for debugging output
$libdebug = 0;				# set to 1 for debugging output

require  "nocollib.pl" ;

-x $rprog || die("Could not find executable $rprog, exiting");

$reg_maxseverity = $E_CRITICAL ;	# max severity of registered events
$unreg_maxseverity = $E_ERROR ;		# max severity of unregistered events

##
# Read the config file. Use '\t' as a separator (since we are allowing
# spaces in the zone names). Also define the regular expression as
# a string to make it easier to modify, etc.
# Interfaces are specified as e (ethernet) s (serial) f (fddi) a (atm)
# Config file format:
#	router=nomad
#	50-50 e0/1 Netplex: Sub50
#	test= 52 e0 Netplex: Sub50
##

sub readconf {
    local ($nets, $interface, $zone) ;
    local ($line_re)= '^\s*(\d+(-\d+)?)\s+((e|s|a|f|t)\d+\S*)\s+(\w\S*(\s+\S+)*)(\s*)?$' ;

    open(CONFIG,"<$cfile")||die("Couldn't find $cfile, exiting");
    while(<CONFIG>)
    {
	chop;
	if(/^\s*#/) {next;}   # skip comments
	if(/^\s*$/) {next;}   # skip blank lines

	if(/^\s*router\s*=\s*(\S+)(\s.*)?$/)  {$router=$1;}
	elsif(/^\s*test\s*=$line_re/){ # test line
	    $nets=$1; $interface=$3; $zone=$5;
	    $zone=~tr/a-z/A-Z/;			# to lowercase
	    $item="$nets\t$interface\t$zone"; # tabs as separators
	    push(@items,$item);
	    $nocop{$item} = $nocop{$item} | $n_TEST;
	}
	elsif(/^$line_re/){
            $nets=$1; $interface=$3; $zone=$5;
	    $zone=~tr/a-z/A-Z/;
            $item="$nets\t$interface\t$zone";
	    push(@items,$item);
	}
    }			# end while(CONFIG)
    close(CONFIG);
    if(!$router){die("No router specified in $cfile, exiting")};
    if(0>$#items){die("Nothing to monitor in $cfile, exiting")};
    if ($debug)
    {
	print "\n(debug) Router= $router\n";
	print "Items are:\n"; foreach (@items) { print "\t$_\n" } ;
    }
    ;	    #end readconf
    
}

## Check the current state of the router
#
sub dotest
{
    local ($loginok) = 0;
    foreach(@items){$found{$_}=0;}
    
    $command="$rprog $router ".' "'."$rpasswd".'" '.'"'."$rcommand".'"';
    if ($debug) {print "(debug) dotest: running command $command\n" ;}
    
    open(ROUTER,"$command |");
    
    $line=""; $ready="";
    while(<ROUTER>)
    {
	tr/\r\n//d;
	if ( />/ ) {$loginok = 1 ;} # got the 'Router>' prompt
	if(/^\s/){	# Lines starting with space are continuation lines.
	    $line.=$_;
	}
	else {$ready=$line; $line=$_;}
	if($ready ne ""){&doline; $ready="";}
    }
    
    if($ready ne ""){&doline; $ready="";}
    close(ROUTER);
    if ($loginok == 0) { print "Login into remote host failed\n" ;}
}

## The output of the 'show apple route' on a cisco is:
#	Nomad-Gateway>show apple route
#	Codes: R - RTMP derived, C - connected, 2 routes in internet
#
#	C Net 50-50 directly connected, Ethernet0, zone GES:Sub50
#	R Net 52 [1/G] via 50.128, 1 sec, Ethernet0, zone GES:Sub50
#
# The output of 'show novell route' on a cisco is:
#	R Net 27 in [1] hops via D2.aa00.0400.0118,  18 sec, 0 uses, Ethernet9
#	C Net 31 is directly connected, 29924 uses, Ethernet15
#
# The output of the CAP (Columbia Appletalk) 'atlook'  command is:
#  nisc# % /usr/local/cap/bin/atlook
#  abInit: [ddp:   0.50, 183], [GW:   0.50, 128] starting
#  Looking for =:=@GES:Sub50 ...
#    1 - A1-Server:AFPServer@*               [Net:  0.52  Node: 56 Skt:251]
#    2 - Al's Macintosh:AFPServer@*          [Net:  0.50  Node: 82 Skt:251]
# Use a regexp of the form:
#  /^\s*\d+\s+-\s+(\w*(\s+\w+)*)"?\s+[.*]\s*$/
##	
sub doline
{
    local ($nets, $interface, $zone, $xzone) ;# $ready has the line to process.
    if($ready =~ /^[RC] Net (\d+(-\d+)?).*((Ethernet|Serial|ATM|Fddi|Fa)[\d\.\/]+\s*)(\D.*)$/)
    {
	$nets=$1; $interface = $3; $xzones=""; $_=$5;
	if(/^(.*)Additional zones: (.*)$/){$_=$1; $xzones=$2;}
	if(/^, zone (\S(.*\S)?)\s*$/){$zone=$1;}
	elsif(/\s*Zone: "(.*)"\s*/){$zone=$1;}
	elsif(/\s*no zone set\s*/){$zone="(no zone set!)";}
	else{$zone="(unparseable)";}
	$zone=~tr/a-z/A-Z/; # set case-insensitive
	$interface =~ s/Ethernet/e/ ;  $interface =~ s/Serial/s/ ;
	$interface =~ s/ATM/a/ ;   $interface =~ s/Fddi/f/ ;
	$interface =~ s/Fa/t/ ;
	$item="$nets\t$interface\t$zone";
	if ($debug) {print "(debug) doline: Found item $item\n" ;}
	$found{$item}=1;
	if(!$isitem{$item})		# determine if to be inserted into list
	{
	    $isitem{$item}=1;
	    push(@items,$item);
	} #Note: at some time, we might deal with the additional zones.
    }
    else
    {
#	if ($debug) {print "(debug) doline: Router output skipped- $ready\n";}
    }
}   


###
### Main program:
###

# Fork and get rid of old process.
&nocol_startup;

&readconf;
# $item  is an index into the arrays. Note the sitename and the siteaddr
# values.
foreach $item (@items) {
    local ($nets, $interface, $zone) = split(/\t/, $item) ;

    &init_event("$zone", "$nets/$interface", $item);
    $registered{$item}=1;	# exists in the config file
    $isitem{$item}=1;		# easy index to each item found.
}


while (1)
{
    &dotest;
    foreach $item (@items)
    {
	local ($nets, $interface, $zone) = split(/\t/, $item) ;
	local($status,$value);
	
	$maxseverity = $reg_maxseverity;
	if (!$registered{$item})
	{
	    $maxseverity = $unreg_maxseverity;
	    if ($found{$item})	# unexpected route found (not in config)
	    {
		if(!$varname{$item})
		{	#Varname is set only if initialized.
		    ($nets, $interface, $zone) = split(/\t/, $item) ;
		    &init_event("$zone", "$nets/$interface", $item);
					# stuff in initial kind of values
		    $varname{$item}=$varname_unreg;
		}
		$forget{$item}=0;	# remember to check the next time
		$status = 0; $value = 1;
	    }
	    else			# earlier unknown route has gone away.
	    {
	        &update_event($item, 1, 0, $maxseverity);
		$varname{$item}="";	#Force reinitialization when it
					# returns, thus resetting severity.
		$forget{$item}=1;
		$status = 1 ; $value = 0;
	    }
	}
	elsif ($found{$item})	# valid route is present
	{ $status = 1; $value = 1; }
	else {$status = 0; $value = 0; }		# configured route missing

	; ## Now update the event arrays for each item.
	if(!$forget{$item})
	{
	    &update_event($item, $status, $value, $maxseverity);
	}
    }  # end foreach()

    ; ## Note: we want to write the file quickly.

    open(OEVENTS,">$datafile");
    foreach $item (@items)
    {
	if(!$forget{$item})
	{
	    &writeevent(OEVENTS, $item);
	}
    }
    close(OEVENTS);
    sleep($sleepint);

}			# end while(forever)
