#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
    if 0; # not running under some shell


#$Id: rat.PL,v 2.18 2002/03/14 16:40:29 gmj Exp $
#
# Router Audit Tool (rat) - audit router configs
#
# Copyright (C) 2002  George M. Jones <gmj@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

=head1 NAME

rat (Router Audit Tool) - A tool for auditing Cisco configs

=head1 SYNOPSIS

B<rat> [OPTIONS] I<config [config ...]>

=head1 DESCRIPTION

B<rat> audits router configurations.  It will log into the routers
specified (you have to provide login info), pull down the configurations,
audit them against a set of rules and produces four output files (see FILES
section) for each router.  One is a passwd style file listing all rules,
pass/fail and other info.  Two is a simple text-based report.  Three is a
"fix" file suitable for cut-and-past into config mode to fix the problems
identified. Four is an HTML version of the report.

=head1 OPTIONS

=over 8

=item B<-u, --user>

The B<--user> flag allows the specification of an a username to be used
when logging in to routers.  The default is the current login name.

=item B<-w, --userpw>

The B<--userpw> flag allows the specification of a user-level password
on the command line.  If the password is not specified, then the user
will be prompted (without echo) for the password.

=item B<-e, --enablepw>

The B<--enablepw> flag allows the specification of an enable password.
If the password is not specified, then the user
will be prompted (without echo) for the password.

=item B<-b, --noclobber>

The B<--noclobber> flag indicates that devices configurations should not be pulled
if they already exist.

=item B<-a, --snarf>

The B<--snarf> flag indicates that devices configurations should be downloaded.


=item B<-n, --nonenable>

The B<--noenable> flag indicates that snarf should not try to enable before pulling configs.

=item B<-r, --rules>

The B<--rules> flag is used to specify the B<ncat(1)> rules file to 
be used to audit the default is ncat_out.conf

=item B<-l, --limitrulesto>

The C<--limitrulestoto> allows the commandline specification of a regular
expression to limit the rules that are checked.  The name of the rule
must match the regexp specified or the rule is skipped.  You might
try something like

  --limitrulesto=finger

or

   --limitrulesto='finger\|syslog'

=item B<-c, --limitclassto>

The C<--limitclassto> allows the commandline specification of a regular
expression to limit the rules that are checked.  The class of the rule
must match the regexp specified or the rule is skipped.  You might
try something like

  --limitclassto=access
  --limitclassto=localrules
  --limitclassto=access,logging,aaa
  --limitclassto='access\|logging\|localrules'

See the rules file for definition of rule classes.  By default, only rules
matching the class "default" are checked. "all" is  synonym for ".*".
You can give a "normal" comma separated list of classes that you want
to check because "," is treated as a synonym for the regular 
expression or ("|").

=item B<-s, --sortorder>

The C<--sortorder> flag allows the specification of the field that reports are sorted by.
Possible values are "importance" (default), "passfail","rule","device","line","instance".

=item B<-p, --onlypass>

The C<--onlypass> flag indicates flag indicates that only passing rules
should be reported.  It may not be combined with C<--onlyfail>

=item B<-f, --onlyfail>

The C<--onlyfail> flag indicates flag indicates that only failing rules
should be reported.  It may not be combined with C<--onlypass>

=item B<-V, --version>

The C<--version> option displays the current program version.

=back

=head1 ARGUMENTS

The B<router> argument(s) allow the user to specify which devices are
to be audited. These may be either IP addresses or DNS names.

=head1 RETURN VALUE

???

=head1 EXAMPLES

 % rat --snarf --rules=$HOME/etc/ncat.conf gw1.atl7
 User Password: 
 Enable Password [same as user]: 
 snarfing gw1.atl7...done.
 auditing gw1.atl7...done.
 Generating report gw1.atl7.ncat_report.txt...done.
 %
 % ls -1 gw1*
 gw1.atl7		# the config file
 gw1.atl7.ncat_fix.txt	# the fix file
 gw1.atl7.ncat_out.txt	# the raw data
 gw1.atl7.ncat_report.txt # the report
 gw1.atl7.html 		# the HTML report
 rules.html		# the rules in HTML
 index.html		# index of html files


=head1 FILES

=over 8

 $config    			- the config file that was pulled
 $config.ncat_out.txt    	- a passwd style file with raw results
 $config.ncat_fix.txt    	- commands to correct problems found
 $config.ncat_report.txt    	- a simple text report with statistics
 all.ncat_report.txt    	- a simple text report listing all device
 $config.html		    	- an HTML version of the report w/fixes
 all.html		    	- an HTML version of the report listing all devices
 rules.html			- an HTML version of the rules applied
 index.html			- an index of the rules and html files

=back

=head1 SEE ALSO

  sbin/rat			- this program
  sbin/router-snarf		- the config puller
  sbin/ncat			- the audit tool
  sbin/ncat_report		- the report generator
  etc/ncat.conf	    		- file containing audit rules

=head1 CAVEATS

Yes, there are some.

=head1 BUGS

Yes.

=head1 AUTHOR

George M. Jones <gmj@users.sourceforge.net>

=head1 CREDIT WHERE CREDIT IS DUE

John Stewart has helped with the code in numerous ways.  It's much cleaner, 
and the install process is better thanks to his efforts.

Eric Brandwine and Jared Allison at UUNET wrote a config checker that
has provided some ideas for this one.  Rob Thomas developed a security 
template for IOS that was the initial inspiration.

Joshua Wright did the port for ActiveState on Windows.

=cut

# $Log: rat.PL,v $
# Revision 2.18  2002/03/14 16:40:29  gmj
# * winsnarf mrege
#
# Revision 2.17  2002/03/14 15:33:17  gmj
# * fix Usage message.  Prepare for winsnarf merge.
#
# Revision 2.15.2.5  2002/03/13 14:39:04  joshwr1ght
# Fixed Usage command line parameter confusion with -r for snarf to -a
#
# Revision 2.15.2.4  2002/03/13 14:14:59  joshwr1ght
# Added --noenable option to Usage options print statement
#
# Revision 2.15.2.3  2002/03/13 14:13:00  joshwr1ght
# Changed command line argument for "use snarf" to -a to avoid usage with
# --rules|-r option.
#
# Revision 2.15.2.2  2002/03/12 21:15:25  joshwr1ght
# Modified to let Windows users utilize snarf.  Changed Usage and command
# line parameter passing to snarf.  Commented out code to check for and
# ask for usernames/passwords to let snarf figure it out instead.
#
# Revision 2.15.2.1  2002/03/12 15:59:47  joshwr1ght
# Modified snarf option to -r to avoid conflict with -f and onlyfail.
# Modified Usage to show snarf parameters to Windows and Unix users.
#
# Revision 2.15  2002/03/07 21:33:36  joshwr1ght
# Removed if windows system calls for path to ncat and ncat_report in favor of subs
#
# Revision 2.14  2002/03/07 19:39:48  gmj
# rat.PL
#
# Revision 2.13  2002/03/04 17:01:56  gmj
# * rat was not passing --verbose to other programs.  Fixed
#
# Revision 2.12  2002/03/02 13:42:11  gmj
# * put parens around exit status to force numeric interpretation.  Was generating a syntax error
#
# Revision 2.11  2002/03/01 13:18:11  gmj
# * unlink existing config before snarfing
#
# Revision 2.10  2002/02/27 14:09:53  gmj
# Intial Windows/ActiveState version
#
# Revision 2.9.2.2  2002/02/27 13:45:51  gmj
# * updated comments.  * clean up printing of usage under windows.
#
# Revision 2.9.2.1  2002/02/25 16:09:09  joshwr1ght
#
# First update of code to support ActiveState Perl on Windows.
#
# Revision 2.9  2002/02/19 16:10:12  gmj
# * changed --nosnarf to --snarf
# * Made -V a synonym for --version
# * Use Getopt::Long::Configure ("bundling_override");
#   which required all long options to use "--".
#
# Revision 2.8  2002/02/05 20:07:43  gmj
# * In doc, tried to clear up confusion between "configs" and "rules file"
# * Moved "TO DO"s to TODO.txt or WISHLIST.txt
#
# Revision 2.7  2002/02/04 23:07:00  gmj
# Fix problem with RCS version number in --version
#
# Revision 2.6  2002/02/04 22:56:02  gmj
# * Added RCS id to --version output
#
# Revision 2.5  2002/02/04 17:06:20  jnssf
# INSTALL.txt: missing directions on ensuring the PATH variable had the PREFIX/bin in it so that rat can find its supporting files
#
# Timestamp/skew issues with some files, yet no differences
#
# Revision 2.4  2002/02/04 16:49:22  jnssf
# Makefile.PL
# 	Created another environment variable for PREFIX so that we can
# 	auto-reference the etc directory where the ncat.conf file is.
# All Others
# 	Auto-referenced the PREFIX/etc for ncat.conf as the default
# 	location, plus updated documentation indicating that lib had
# 	a default directory for ncat.conf when it was no longer the
# 	case
#
# Revision 2.3  2002/02/01 13:15:41  gmj
# Changed authors email address
#
# Revision 2.2  2002/01/29 16:50:17  jnssf
# Modifications for automatically changing @INC and allowing the
# NCAT.pm file to be placed in $(PREFIX)/lib for ease of us (non root
# install)
#
# Revision 2.1  2002/01/24 21:52:08  gmj
# merge back to mainline
#
# Revision 2.0.2.2  2002/01/24 21:34:25  gmj
# * Updated copyright info
#
# Revision 2.0.2.1  2001/12/21 17:37:01  gmj
# Updated to use CIS Terms of Service
#
# Revision 2.0  2001/12/21 15:23:36  gmj
# Level set version to 2.0 prior to branch for Center for Internet Security.
#
# Revision 1.7  2001/12/21 15:18:15  gmj
# search a likely path of /etc directories for ncat.conf if not supplied.
#
# Revision 1.6  2001/12/14 11:28:54  gmj
# Added verbose flag.
# Updated credits.
#
# Revision 1.5  2001/12/11 21:44:12  jnssf
# Changes for allowing 'use NCAT;' to incorporate --version flag,
# misc changes to make code similar to other entries.  Change to NCAT.pm
# so it doesn't use bootstrapping, but instead a function call to ensure
# we no longer need the Auto/Dyna loader routines (which in the end weren't
# used anyway), to allow for the 'use' statements with the .ix or .so files.
#
# Revision 1.4  2001/12/11 14:07:02  gmj
# Documented short (single character) versions of command line switches.
# Added --help.
#
# Revision 1.3  2001/12/10 15:48:21  gmj
# Changed default class from .* to "default".
#
# Revision 1.2  2001/11/26 14:58:04  gmj
# Fix minor problems with option parsing.
#
# Revision 1.1.1.1  2001/11/14 21:40:18  gmj
# Initial CVS checkin.
#
#
# Revision 2.1  2001/11/07 07:19:55  jns
# changed to '' where possible, switched the flush variable of STDERR
# in Usage, and some minor changes.
#
# Revision 2.0  2001/11/07 01:43:30  jns
# y
# branch for jns work
#
# Revision 1.9  2001/11/03 00:39:55  gjones
# - Converted to useing GetOptions
# - Clean up
# - Don't invoke shell in system() calls.
#
# Revision 1.8  2001/11/02 16:19:15  gjones
# - Added -sortorder switch.
# - Documentation cleanup.
#
# Revision 1.7  2001/11/01 20:06:05  gjones
# - Added -limitclassto switch.
# - Cleaned up report generation
#
# Revision 1.6  2001/10/22 18:58:24  gjones
# * Added -limitrulesto, -onlypass and -onlyfail flags for ncat
#
# Revision 1.5  2001/10/10 17:02:33  gjones
# Test for presence of Net::Telnet::Cisco module.
# Doc changes.
#
# Revision 1.4  2001/10/09 18:19:16  gjones
# Generate index.html for HTML reports, rules.
#
# Revision 1.3  2001/10/06 06:21:14  gjones
# Final updates for rat 0.1 release.  Documentation.
#
# Revision 1.2  2001/10/06 01:37:37  gjones
# Reworked dataflow between programs.  General cleanup.
#
# Revision 1.1  2001/09/29 22:21:23  gjones
# Initial revision
#

$ENV{'SHELL'}  = '/bin/sh';
$ENV{'IFS'}    = '';

use lib '/usr/lib';


use Getopt::Long;
use IO::Handle;
use NCAT;
use Config;
use strict;

my (
    $name,
    $opt_debug,         $opt_dir,
    $opt_enablepw,      $opt_limitclassto,  
    $opt_limitrulesto,  $opt_noclobber,
    $opt_snarf,       	$opt_onlyfail,
    $opt_onlypass,      $opt_rules,
    $opt_sortorder,     $opt_user,
    $opt_userpw,        $opt_version,
    $opt_help,		$opt_verbose,
    $opt_noenable,
    $cmd,               $config,
    $enablepw,          $home,
    $ncat_args,         $ncat_prog,
    $ncat_report_args,  $ncat_report_prog,
    $output,            $shortname,      
    $snarf_args,        $snarf_prog,
    $router,            $userpw,
    $found,		$path,
    $windows,		$configfile,
    ) = ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
         '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');

my (
    @cmd,
    @HaveConfigs,
    @HaveOutput,
    @EtcPath,
    ) = ((), (), ());

#test for Windows
if ($Config{'osname'} =~ /MSWin/) {
    $windows=1;
}

#default values
my $progname      = $0;
$progname         =~ s,.*/,,;    # only basename left in progname
$progname         =~ s/\.\w*$//; # strip extension if any

$opt_rules        = '/etc/ncat.conf';
$opt_dir          = '/usr';
$snarf_prog       = '/usr/sbin/router-snarf'; # hard code path
$ncat_prog        = '/usr/sbin/ncat'; # hard code path
$ncat_report_prog = '/usr/sbin/ncat_report'; # hard code path
$opt_debug        = '';
$opt_snarf      = 0;



#parse command line

Getopt::Long::Configure ("bundling_override");
GetOptions(
	   # Common
	   'rules|r=s',        \$opt_rules,
	   'debug|d=s',        \$opt_debug,
	   'version|V',    	\$opt_version,
	   "verbose|v", 	\$opt_verbose,
	   "help|h", 		\$opt_help,

	   # Snarf specific
	   'user|u=s',         \$opt_user,
	   'userpw|w=s',       \$opt_userpw,
	   'enablepw|e=s',     \$opt_enablepw,
	   'noclobber|b',      \$opt_noclobber,
	   'snarf|a',	       \$opt_snarf,
           'noenable|n',       \$opt_noenable,

	   # ncat specific 
	   "limitrulesto|l=s", \$opt_limitrulesto,
	   "limitclassto|c=s", \$opt_limitclassto,
	   'onlyfail|f',       \$opt_onlyfail,
	   'onlypass|p',       \$opt_onlypass,

	   # Report specific 
	   'sortorder|s=s',    \$opt_sortorder,

	  )
    or &Usage('');

&Version if $opt_version;
&Usage("") if ($opt_help || @ARGV == 0);

# expand out ~ (why dosn't Perl do this ?)

if ($windows) {
    $home = "."
} else {
    $home = $ENV{"HOME"} || $ENV{"LOGDIR"} || (getpwuid($<))[7];
}

$opt_rules =~ s/^~(.*)/$home$1/;

# Look for ncat.conf if it's not here.

@EtcPath = ("/usr/etc","/usr/local/etc","$home/etc","./etc");

unless (-e "$opt_rules") {
  printf STDERR "$progname: Configuration file file $opt_rules not found in current directory.  Searching...\n";

  for $path (@EtcPath) {
    if (-e "$path/$opt_rules") {
        printf STDERR "$progname: Using configuration file found at $path/$opt_rules\n";
        $opt_rules = "$path/$opt_rules";
	$found = 1;
	last;
    }
  }
  
  unless ($found) {
    die("Configuration file $opt_rules not found.")
  }
}

# Get args for snarf
$snarf_args = "";
$snarf_args .= " --user=$opt_user"         if ($opt_user);
$snarf_args .= " --userpw=$opt_userpw"     if ($opt_userpw);
$snarf_args .= " --enablepw=$opt_enablepw" if ($opt_enablepw);
$snarf_args .= " --noclobber"              if ($opt_noclobber ne "");
$snarf_args .= " --noenable"               if ($opt_noenable ne "");
$snarf_args .= " --debug=$opt_debug"       if $opt_debug ne "";
$snarf_args .= " --verbose"                if ($opt_verbose);

# Get args for ncat
$ncat_args = "";
$ncat_args .= " --rules=$opt_rules";
$ncat_args .= " --debug=$opt_debug"               if $opt_debug ne "";
$ncat_args .= " --limitrulesto=$opt_limitrulesto" if $opt_limitrulesto ne "";
$ncat_args .= " --limitclassto=$opt_limitclassto" if $opt_limitclassto ne "";
$ncat_args .= " --onlypass"                       if ($opt_onlypass);
$ncat_args .= " --onlyfail"                       if ($opt_onlyfail);
$ncat_args .= " --verbose" 			  if ($opt_verbose);


# Get args for ncat_report
$ncat_report_args = "";
$ncat_report_args .= " --rules=$opt_rules";
$ncat_report_args .= " --debug=$opt_debug"         if $opt_debug ne "";
$ncat_report_args .= " --sortorder=$opt_sortorder" if ($opt_sortorder);
$ncat_report_args .= " --verbose" 		   if ($opt_verbose);


#
# Snarf configs
#

foreach $router (@ARGV) {

  if ($opt_snarf) {

    # Test for required modules so that we can die
    # more gracefully than warn
    
    # Snarf the config
    
    unlink $router if (-s $router); # get rid of old copy first

    $cmd = "$snarf_prog $snarf_args $router\n";
    if ($windows) {
        $cmd = "perl $cmd";
    }
    print "$cmd\n" if ($opt_debug =~ /cmd/);
    @cmd = split(/\s+/,$cmd);
    
    print STDERR "snarfing $router...";
    unless (system(@cmd) == 0) {
      warn "$snarf_prog failed processing $router. Exit status: " . ($? >> 8);
      next;
    } 
    print STDERR "done.\n";
  }

  # Push the config if a) it exits and b) we are not snarfing or we are snarfing
  # and the snarf worked.

  push @HaveConfigs,$router if (-s $router);
}

die "No configuration files available to audit" if (@HaveConfigs == 0);


#
# Audit the configs
#

for $config (@HaveConfigs) {

    $shortname = $config;
    $shortname =~ s/.*\///;

#    $cmd = "";
    $cmd = "$ncat_prog $ncat_args $config";
    if ($windows) {
        $cmd = "perl $cmd"; 
    }
    @cmd = split(/\s+/,$cmd);

    print "$cmd\n" if ($opt_debug =~ /cmd/);
    print STDERR "auditing $config...";

    unless (system(@cmd) == 0) {
	warn "$ncat_prog failed processing $config.";
	next;
    }
    print STDERR "done.\n";

    $output = "$shortname.ncat_out.txt";

    push @HaveOutput,"$output" if (-s "$output");

}

#
# Generate reports
#

$cmd = "$ncat_report_prog $ncat_report_args "; 
if ($windows) {
    $cmd = "perl $cmd";
}

$cmd = $cmd . join(" ",@HaveOutput);
print "$cmd\n" if ($opt_debug =~ /cmd/);
@cmd = split(/\s+/,$cmd);

unless (system(@cmd) == 0) {
    warn "$ncat_report_prog failed processing /" . join(" ",@HaveOutput) . "/";
}


sub Version {

    print STDERR "This is $progname version ", NCAT::Version, "\n";
    print STDERR '$Id: rat.PL,v 2.18 2002/03/14 16:40:29 gmj Exp $';
    print STDERR "\n\n";
    print STDERR "Copyright 2002, George M. Jones\n";
    print STDERR "\n";
    print STDERR "This program is free software; you can redistribute it and/or\n";
    print STDERR "modify it under the same terms as Perl itself\n";
    exit(0);
}


sub Usage {
  my($msg) = @_;

  select(STDERR);
  autoflush STDERR 1;

  print STDERR "$progname: $msg\n" unless ($msg eq "");
  print "Usage:\n";
  print "  $progname [options] address [address...]\n";
  print "    -r, --rules RULES_FILE\n";

  print "    -u, --user USERNAME\n";  
  print "    -w, --userpw USER_PW\n";
  print "    -e, --enablepw ENABLE_PW\n";
  print "    -n, --noenable\n";
  print "    -b, --noclobber\n";
  print "    -a, --snarf\n";

  print "    -l, --limitrulesto RULES_REGEXP\n";  
  print "    -c, --limitclassto CLASS_REGEXP\n";  
  print "    -p, --onlypass\n";  
  print "    -f, --onlyfail\n";
  print "    -s, --sortorder sortorder\n";  
  print "    -d, --debug DEBUG_OPTIONS\n";  
  print "    -V, --version\n";
  print "    -v, --verbose\n";

  exit(1);
}
