#!/usr/pkg/bin/perl
#
# $Header: /home/vikas/src/nocol/perlnocol/RCS/nrmon,v 1.7 1999/11/01 13:35:22 vikas Exp $
#
# 	nrmon - perl nocol NOVELL 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:
#
#  nrmon
#
#    Automatically kills old process and forks a new one, reading
#    the configuration file in the process.
#
# What it does:
#
#    nrmon reads the NOVELL 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
#   Net 501  e16 1b038cc5    14:43  Novell Route        1    Critical
#   Net 43   e9 04000118     16:10   Unrg NRoute        0    Critical
#
#    The first line states that a route to the IPX network with network
#    number 501 through the router's interface Ethernet 16 to a router
#    with mac address "????1b038cc5" is missing.
#    The second line states that a route has appeared to Net 43, through
#    the router's interface Ethernet 16 to "????04000118" but
#    this route is not listed in nrmon's configuration file.
#
#    It is important to remember that nrmon 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.
#
# 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                     "nrmon"
#   severity                   up: 4; down: 3,2,1;
#   nocop                      up, down, unknown, test
#   site
#     name                     "Net " followed by the hex IPX network number
#     addr                      The last 8 digits of the route's next
#                               hop's Ethernet address followed by space and
#                               the Router's ethernet port (e0, e1, etc).
#                               Example: "1b038cc5" e8"
#   var                       
#     name                      either "Reg_NovellRoute" or "Unrg_NovellRoute"
#     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> <next-hop>             Route via ethernet interface.
#    test= <netnum> s<intnum> <next-hop>
#
# Sample configuration file:
#    # nrmon configuration
#    router=mycisco.excellent.edu
#     # Route that is through another router:
#     2 e9 D2.aa00.0400.0118
#     # Directly-connected network:
#     4 e22
#    test=3  s1 D2.aa00.0400.1234
#
## 
##
#
#
############################
## Variables customization #  overrides values in the nocollib.pl library
############################
$rprog="./rcisco";			# Path for rcisco.
$rpasswd="";				# if NULL, uses the default in rcisco
$rcommand="show novell route";
$varname="Reg_NovellRoute";		# Registered appletalk route
$varname_unreg="Unrg_NovellRoute";	# 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 names). Also define the regular expression as
# a string to make it easier to modify, etc.

sub readconf {
    local ($nets, $interface, $nhop) ;
    local ($line_re)= '\s*([\dABCDEF]+)\s+((e|s|F.)[\d\.\/]+)(\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/){
 	    $nets=$1; $interface=$2; $nhop=$5;
	    $nhop=~tr/a-z/A-Z/;			# to lowercase
	    $item="$nets\t$interface\t$nhop"; # tabs as separators
	    push(@items,$item);
	    $nocop{$item} = $nocop{$item} | $n_TEST;
	}
	elsif(/^$line_re/){
            $nets=$1; $interface=$2; $nhop=$5;
	    $nhop=~tr/a-z/A-Z/;
            $item="$nets\t$interface\t$nhop";
	    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 '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
##	
sub doline
{
    local ($nets, $interface, $nhop) ;# $ready has the line to process.
    if($ready =~ /^[RC] (Net)?\s*([\dABCDEF]+) (((in)?.*via\s+(\S+),)|(is)?).*,\s+((Ethernet|Serial|F.)[\d\.\/]+)$/)
    {
	$nets=$2;
	$interface = $8;
	$nhop=$6; $nhop=~tr/a-z/A-Z/; # set case-insensitive
	$interface =~ s/Ethernet/e/ ;  $interface =~ s/Serial/s/ ;
	$item="$nets\t$interface\t$nhop";
	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);
	}
    }
    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, $nhop) = split(/\t/, $item) ;

    &init_event("$nhop", "$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, $nhop) = 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, $nhop) = split(/\t/, $item) ;
		    &init_event("$nhop", "$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)
