pixbkup_1.2.txt

#!/usr/bin/perl
#===========================================================#
# Cisco PIX Config Backup Utility Script                    #
# Copyright 2004 Chris Sawall   csawall@hotmail.com         #
# http://tech.stlsawall.com                                 #
#                                                           #
my $written = "08/12/04";                                   #
my $lastupdated = "09/08/04";                               #
my $version = "1.2";                                        #
my $myemail = "sawall\@gmail.com";                          #
#===========================================================#
#===========================================================#
# COPYRIGHT NOTICE                                          #
# Copyright 2004 Chris Sawall  All Rights Reserved.         #
#                                                           #
# Cisco PIX Config Backup Utility Script (pixbkup.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 automate
# the process of backing up Cisco PIX configs.  It does so
# over a secure channel by using SSH.
#
#==========================================
# Directions on configuring the PIX
#
# Depending on whether or not you are using some type of
# Radius or TACACS solution, you may or may not need
# to create a user account on the PIX.  If you do, the
# following is the command to do so:
#
# username pixbkup password pixbkup privilege 5
#
# and the PIX config will look like:
# username pixbkup password /O5wMATnjhpdZAY9 encrypted privilege 5
#
# you will need to configure ssh:
# aaa authentication ssh console {RADIUS|TACACS|LOCAL}
#
# ensure that the device running the script can connect
# to the PIX:
# ssh 10.10.10.10 255.255.255.255 inside (or appropriate interface)
#
# tell the PIX to get authorization permissions locally
# aaa authorization command LOCAL
#
# set the privilege level to only allow the user to
# show the running configuration:
# privilege show level 5 command running-config
#
# set the enable password for level 5:
# enable password pixbkup level 5
#
#==========================================
# the Expect Perl module will need to be installed:
#
# [root@yoursystem root]# cpan
#
# cpan shell -- CPAN exploration and modules installation (v1.7601)
# ReadLine support available (try 'install Bundle::CPAN')
#
# cpan> install Expect
#
#==========================================
# Change log
#
# 8/24/04 - added section to look for existing config files and MD5
#           hashes.  if the hashes match, then a new backup config
#           is not stored.
#
# 8/25/04 - added section to see which hosts a config could not be
#           gotten from.  put host into array and attempt again
#           after first pass at all hosts.  if fails a 2nd time,
#           it will send email to admin notifying of failure
#
# 9/02/04 - worked out the bugs of actually getting the above
#           two items to work properly.
#
# 9/08/04 - there were still bugs with the way the Expect lines
#	    were returning data.  the MD5s would not match a lot
#	    of the times, even if the config did not change.  so i
#	    rewrote to the script to compare the cryptochecksum
#	    at the end of the pix config rather than comparing the
#	    the last MD5 hash.  seems to be working every time now.
#==========================================

use File::stat;
use Expect;
use strict;
$|++;

my $debug = 0;
# set admin email addrs separated by a space
my $fwemail = "FWADMIN\@YOURDOMAIN.COM";
my $sendmailprog = '/usr/sbin/sendmail -t';
my $compareprog = "/usr/bin/cmp";
# set the username and password of the backup acct
my $user = "pixbkup";
my $pass = "pixbkup";
# set this parameter if your ssh client expect you
# to accept connecting to a new ssh host, my testing
# was with fedora and openssh
# 1 to enable; 0 to disable
my $autoaccept = 0;
# automatically delete older config files
my $deleteoldconfigs = 1;
my $keeplogfiletime = 30;
# define what privilege level is defined on the pix
my $enlvl = "5";
# tell script if you will be using a privilege level
# or if you are just going to use the default enable
# password to login (not suggested).
# set to 1 to use defined privilege level.
my $userprivset = 1;
# set "real" enable password if necessary (not suggested)
my $enpass = "PASSWORD";
# define command to display current configuration
# not the "exclude passw".  this is to ensure that the
# real enable password is not stored with the configuration
# i believe the pix version has to be at least 6.3 or higher
my $getcfgcmd = "sh run | exclude passw";
# define the file containing a list of firewalls to connect
# to and backup config.  hosts beginning with a pound sign (#)
# will be ignored.
my $hostfile = "firewall.list";
# define temp log file to store data.  deleted at end of script
my $logfile = "/tmp/xlog.txt";
# set the location to store config files.  be sure to lock
# down access to this folder.
my $configdir = "/logs/pixconfigs";
my $pixbkuplog = "/logs/pixbkup.log";
# choose whether or not to create md5 hashes of configurations
# setting to 1 will create hash
my $createmd5 = 1;
# define binary locations to do md5 hashes on config files
my $opensslexe = "/usr/bin/openssl";
my $md5sumexe = "/usr/bin/md5sum";
# define with binary to execute
my $usemd5prog = "md5sum"; # can be "md5sum" or "openssl"
# define whether or not to review for existing files with matching
# md5 hashes.  if the same, do not get/keep new file.
# set to 1 to enable and 0 to disable
my $removeifsame = 1;
# set various variables
my (@checkhosts, @hosts, $host, $expect, $result, $grabdata, $sumsmatch);
my ($sec,$min,$hour,$mday,$mon,$year,$currenttime,$pixcfgfile,$pixmd5file,$sum,$error);
my ($sumsmatch, @splitsum, $md5only, $numdownhosts, @downhosts, $beenhere, $checkdowns);
my (@getcrypto, $newcrypto, $origpixcfg, $foundend, $rmdnhost, $delresults, $cmpcfgs);
my $starttag = "---- Start config for $host ----";
my $endtag = "---- Stop config for $host ----";

#=========================================
# 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.$hour$min$sec";
if($debug) {print "###Show Current Time ###\n"; print "Time => $currenttime\n";}

if (-e $hostfile) {open(FWFILE,"$hostfile") || die "Failed to read config\n";}
if($debug)
 {
 print "Current hostfile is $hostfile\n";
 my $numhosts = `cat $hostfile | wc -l`;
 print "Total number of hosts is $numhosts\n";
 }
$checkdowns = 0;
@hosts = <FWFILE>;
close(FWFILE);

&main;
&sendnotice;
if($deleteoldconfigs) {&deloldconfigs;}
if($debug) {print ".........End of Script...........\n";}

sub main # --- Begin MAIN subroutine
{
if(!$checkdowns) {@checkhosts = @hosts;}
if($checkdowns) {@checkhosts = @downhosts;}

foreach (@checkhosts)
{
 chomp();
 $host = $_;
 undef $numdownhosts;
 # --- Set out filename for PIX config
 if($debug) {print "\n*****************************\nCurrent host is $host\n";}
 # --- Commented hosts will be ignored
 if($host =~ /^#/){if($debug) {print "Host commented out: $host\n";} next;}
 $pixcfgfile = "$host.$currenttime.cfg";
 $pixmd5file = "$host.$currenttime.md5";
 if($debug) {print "Current PIX CFG file is $host.$currenttime.cfg\n";}
 if($debug) {print "Current PIX MD5 file is $host.$currenttime.md5\n";}
 # --- Start using Expect to connect to PIX
 $expect=Expect->spawn("ssh -l $user $host");
 # --- Don't display data to screen ---
 $expect->log_stdout(0);
 # --- Start log file and truncate any existing log file ---
 $expect->log_file($logfile, "w");
 # --- Log into pix and write config ---
 $result=$expect->expect(5,'assword:');
 if ($result == 1) { print $expect "$pass\n";}
 if (($autoaccept) && ($result == 0))
  {
  if($debug) {print "Auto Accepting SSH Fingerprint from $host\n";}
  print $expect "yes\n";
  $result=$expect->expect(5,'assword:');
  if ($result == 1) { print $expect "$pass\n";}
  }
 else
  {
  $result=$expect->expect(5,'assword:');
  if ($result == 1) { print $expect "$pass\n";}
  }
 $result=$expect->expect(5,'>');
 if($userprivset)
  {
  if($debug) {print "User PRIV Set - Using Level $enlvl\n";}
  if ($result == 1) { print $expect "en $enlvl\n";}
  $result=$expect->expect(1,'assword:');
  if ($result == 1) { print $expect "$pass\n";}
  }
 else
  {
  if($debug) {print "User PRIV *NOT* Set - Using Default Enable Level 15\n";}
  if ($result == 1) { print $expect "en\n";}
  $result=$expect->expect(1,'assword:');
  if ($result == 1) { print $expect "$enpass\n";}
  }
 $result=$expect->expect(1,'#');
 if ($result == 1) { print $expect "pager 0\n";}
 $result=$expect->expect(1,'#');
 $expect->print_log_file("\n$starttag\n");
 if ($result == 1) { print $expect "$getcfgcmd\n";}
 $result=$expect->expect(1,'#');
 if ($result == 1) { print $expect "pager 25\n";}
 $result=$expect->expect(1,'#');
 $expect->print_log_file("\n$endtag\n");
 if ($result == 1) { print $expect "exit\n";}
 $error=$expect->before();
# --- write current config to file ---
# if (-e "$logfile") {unlink($logfile);}
 if (-e "$configdir\/$pixcfgfile")
  {
  unlink("$configdir\/$pixcfgfile");
  unlink("$configdir\/$pixmd5file");
  if($debug) {print "Config file ($configdir/$pixcfgfile) exists, removing...\n";}
  }
 open(PIXCFG, ">>$configdir/$pixcfgfile");
 open(PIXLOG, ">>$pixbkuplog");
 open(FIXFILE, "$logfile");
 $foundend = 0;
 while(<FIXFILE>)
 {
  chomp();
  tr/\r//d;
  if(/$starttag/) {$grabdata = 1; if($debug) {print "Found Start tag for $host\n";}}
  if(/$endtag/) {$grabdata = 0; if($debug) {print "Found End tag for $host\n";}}
  if(/^cryptochecksum/i)
   {
   $foundend = 1;
   @getcrypto = split(':', $_);
   $newcrypto = $getcrypto[1];
   if($debug)
    {
    print "Found End of Config file for $host - foundend = $foundend - grabdata = $grabdata\n";
    print "\tEnd line found is ==> $_\n";
    print "\tCryptosum by itself ==> $newcrypto\n";
    }
   }
 if($grabdata)
  {
   if($_=~/$starttag/ || /$getcfgcmd/ || /^\s+/) {next;}
   if($debug) {if(/^cryptochecksum/i) {print "\tIn Loop - Line still there? ==> $_\n";}}
   print PIXCFG "$_\n";
  }
 }
 close(PIXCFG);
 close(FIXFILE);
if(!$foundend)
 {
 if($debug) {print "Did not get config from $host - Did not find end of config\n";}
 unlink("$configdir\/$pixcfgfile");
 if($beenhere < 2) {push(@downhosts, $host);}
 next;
 }

# --- create md5 hash file if necessary
if($createmd5) {&makemd5s;}

if($removeifsame) {&comparecryptos;}
if(($debug) && (!$removeifsame)) {print "Not checking for config changes, just backing up anyways...\n";}

if($sumsmatch)
 {
 if($debug) {print "The Cryptochecksum matches existing config.  Moving on...\n";}
 if($beenhere >= 1) {&shiftout;}
 unlink("$configdir\/$pixcfgfile");
 unlink $logfile;
 if($debug) {print "Unlinked $logfile.\n";}
 next;
 }
else
 {
 if($debug) {print "Creating new file.\n";}
 print PIXLOG "$currenttime - Got new config for $host\n";
 open(MD5File,">>$configdir/$pixmd5file");
  print MD5File "$sum\n";
 close(MD5File);
 unlink $logfile;
 if($debug) {print "Unlinked $logfile.\n";}
 if($beenhere >= 1) {&shiftout;}
 }
} # --- END MAIN WHILE LOOP OF FWFILE

if($debug) {print "\nTEMP TEMP -> downhosts = @downhosts\n";}
$numdownhosts = @downhosts;
if($debug) {print "TEMP TEMP -> numdownhosts = $numdownhosts\n";}
if($debug) {print "TEMP TEMP -> 1st in downhosts = $downhosts[0]\n";}
close(PIXLOG);
} # --- END MAIN SUBROUTINE

sub makemd5s
 {
 if($debug) {print "Creating MD5 Hash\n";}
 if($usemd5prog =~ /openssl/)
  {
  if($debug) {print "Using OpenSSL...\n";}
  $sum = `$opensslexe md5 $configdir/$pixcfgfile`;
  }
 if($usemd5prog =~ /md5sum/)
  {
  if($debug) {print "Using MD5sum...\n";}
  $sum = `$md5sumexe $configdir/$pixcfgfile`;
  if($debug) {print "New Sum ==> $sum\n";}
  }
 } # --- END MAKEMD5S SUBROUTINE

sub sendnotice
 {
 if(($numdownhosts >= 1) && ($beenhere < 2))
  {
  if($debug) {print "\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";}
  if($debug) {print "Was unable to connect to: @downhosts\n";}
  if($debug) {print "Going to try one more time.\n";}
  if($debug) {print "!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";}
  $checkdowns = 1;
  $beenhere = 1;
  if($beenhere == 1) {$beenhere ++; &main;} # --- Only try this once
  }

 if(($numdownhosts >= 1) && ($beenhere >= 2))
  {
  if($debug) {print "\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";}
  if($debug) {print "numdownhosts = $numdownhosts\n";}
  if($debug) {print "Still unable to connect to: @downhosts\nSending emails to $fwemail\n";}
  if($debug) {print "!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";}
  print PIXLOG "$currenttime - Could not get config from: @downhosts\n";
  open(MAIL,"|$sendmailprog");
# NOTE - the following can not be indented
print MAIL <<END_EMAIL;
From: pixbkup\@ameren.com
To: $fwemail
Subject: Failures on PIX Config Backup

Failed to get the configs from the following hosts:
@downhosts

Current time: $currenttime

END_EMAIL
  close MAIL;
  }
 } # --- END SENDNOTICE SUBROUTINE

sub deloldconfigs
 {
 if($debug) {print "Auto set to delete log files older $keeplogfiletime day(s)...\n";}
 $delresults = `find $configdir/* -ctime +$keeplogfiletime  -exec rm -f {} \\;`;
 print "Deletion results ==> $delresults\n";
 } # --- END DELOLDCONFIGS SUBROUTINE

sub shiftout
 {
 $numdownhosts = @downhosts;
 for (my $x=0; $x < $numdownhosts; $x++)
  {
  if($host =~ $downhosts[$x])
   {
   delete $downhosts[$x];
   if($debug) {print "\"Shifting\" out downhost $host.\n";}
   }
  }
 }

sub comparecryptos # --- Begin comparecryptos loop
 {
 if(!$host) {if($debug) {print "Host variable is blank! Exiting.\n";} exit;}
 my ($origcrypto, @cfgdata, @tmpsplit, @multicfgs, $numcfgs, $newestcfg, $existcfgname, $existcfgfile);
 if($debug) {print "Checking config for $host against existing files.\n";}

 opendir(LDIR,$configdir);
 my @listofcfgs = readdir(LDIR);
 closedir(LDIR);

 foreach(@listofcfgs)
  {
  if($_ =~ /$host\S+\.cfg/)
   {
   push(@multicfgs, $_);
   }
  }
 $numcfgs = @multicfgs -1;
 if($debug) {print "Found $numcfgs file(s) for $host.\n";}
 if($numcfgs >= 1)
  {
  sort(@multicfgs);
  pop(@multicfgs);
  $newestcfg = pop(@multicfgs);
  }
 if(($newestcfg =~ /\S+/) || ($numcfgs == 0))
  {
  if($numcfgs != 0)
   {
   $existcfgname = $newestcfg;
   $existcfgfile = "$configdir/$newestcfg";
   if($debug) {print "Found an existing config file.\n";}
   if($debug) {print "Existing config file is ==> $existcfgfile\n";}
   open(GETCFG, "$existcfgfile");
   while(<GETCFG>)
    {
    if(/^cryptochecksum/i)
     {
     if($debug) {print "Found Cryptochecksum in existing PIX config\n";}
     @tmpsplit = split (':', $_ );
     $origcrypto = $tmpsplit[1];
     }
    }
   if($debug) {print "Existing Cryptochecksum is $origcrypto\n";}
   }
  if($origcrypto =~ $newcrypto)
   {
   if($debug) {print "Sums Match - Setting to true\n";}
   $sumsmatch=1;
   }
  else
   {
   if($debug)
    {
    if($numcfgs == 0)
     {
     print "No existing config files\n";
     }
    else
     {
     print "Sums DON'T Match - Setting to false\n";
     }
    }
   $sumsmatch=0;
   }
  }
 else
  {
  if($debug) {print "No existing config file found for $host\n";}
  }
 close(GETCFG);
 } # --- End comparecryptos subroutine

exit;