#!/opt/vdops/bin/perl # This script automates the process of saving IOS config files. # # If told to "write mem", it saves running-config to startup-config. # # If told to save to a tftp host, it saves the current config file on the # tftp host to a changetree (with a ".before" extension). Then, it tells # the device to write its startup-config to the tftp host. And finally, # it saves this new config file (with a ".after" extension) to the # changetree. # # If told to save to flash, it copies the config file from the tftp server # to local storage. # V Who When What # --------------------------------------------------------------------------- # 2.0.0 skendric 02-21-2011 Upgrade to Netops 1.4.0 # 1.9.1 skendric 06-25-2010 Allow $error{$target} to be undef # 1.9.0 skendric 02-05-2010 Upgrade to perl 5.10.1 # 1.8.9 skendric 12-19-2008 Support new write_flash arg format # 1.8.8 skendric 07-25-2008 Verify file permissions # 1.8.7 skendric 12-29-2007 Loosen use of touch_file # 1.8.6 skendric 08-17-2007 Cosmetic changes to output # 1.8.5 skendric 03-21-2007 Stylistic mods # 1.8.4 skendric 11-19-2006 Replace Object Values with OIDs # 1.8.3 skendric 09-25-2006 Change variable names # 1.8.2 skendric 11-05-2005 Upgrade to new WI::VDOPS module structure # 1.8.1 skendric 05-22-2005 Fix bug in use of touch_file # 1.8.0 skendric 05-09-2005 Support Netops.pm-1.2 # 1.7.1 skendric 11-17-2004 Add SNMP dump packet debugging # 1.7.0 skendric 11-08-2004 Add ability to save a copy of startup-config # to another location on local storage # 1.6.7 skendric 07-21-2004 Prettified error msg printing # 1.6.6 skendric 05-30-2004 Fix bug affecting NativeIOS targets # 1.6.5 skendric 05-01-2004 Migrate common functions to Netops.pm # 1.6.4 skendric 04-30-2004 Enhance command-line options # 1.6.3 skendric 12-01-2003 Autodetect local IP address # 1.6.2 skendric 11-16-2003 Use Net::Ping::External # 1.6.1 skendric 08-10-2003 Minor bug fixes # 1.6.0 skendric 03-28-2003 Numerous minor updates # 1.5.9 skendric 01-26-2003 Tightened scoping, changed DEBUG to $debug # 1.5.8 skendric 01-05-2003 Check IOS version for SNMP/TFTP support # 1.5.7 skendric 12-18-2002 Added support for Aironet # 1.5.6 skendric 11-13-2002 Added @version, improved build_target() # 1.5.5 skendric 10-20-2002 Fixed bug in use of $grab_hosts # 1.5.4 skendric 08-12-2002 Fixed error in permissions mods # 1.5.3 skendric 08-05-2002 Additional error handling # 1.5.2 skendric 07-21-2002 Add support for 'all' # 1.5.1 skendric 06-24-2002 Fix bug in Catalyst 3500 detection # 1.5.0 skendric 05-31-2002 Auto-detect running from cron # 1.4.2 skendric 05-10-2002 Logs username of operator # 1.4.1 skendric 05-05-2002 Clean-up # 1.4.0 skendric 04-15-2002 Added $tftp_path, more debugging # 1.3.2 skendric 02-24-2002 Fixed bug in use of @error # 1.3.1 skendric 02-21-2002 Fixed bug in permissions checking # 1.3.0 skendric 02-10-2002 Instruct IOS devices to perform a 'write mem' # 1.2.3 skendric 02-10-2002 Cleaned up error_check() # 1.2.2 skendric 01-29-2002 Fixed bug in permissions checking # 1.2.1 skendric 01-17-2002 Handled case of no valid targets # 1.2.0 skendric 01-13-2002 Regularized report format # 1.1.0 skendric 12-30-2001 Multiple SNMP community strings # 1.0.0 skendric 12-20-2001 First Version # # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/device # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # I borrowed heavily from the following URLs in order to write this script: # http://www.cisco.com/warp/public/477/SNMP/copy_configs_snmp.shtml # http://www.cisco.com/warp/public/477/SNMP/11.html # http://www.cisco.com/warp/public/477/SNMP/move_files_images_snmp.html # # # This script takes the following approach: # If $running_mode is set: # -Copies running-config to startup-config # If $tftp_mode is set: # -Copies the current config file, assumed to be sitting on a # tftp server, to the current location, appending ".before" to the # name # -Instructs the named device to write its current config # file to the tftp server # -Copies this most recent config file to the current location, # appending ".after" to the name # If $flash_mode is set: # -Copies config file from tftp server to $backup_config # # # Requirements: # -The target(s) must be pingable # # -The script must have file system access to the tftp directory # # -The following MIB modules stashed in /opt/vdops/share/snmp/mibs, # or wherever it is that you store MIB modules: # CISCO-PRODUCTS-MIB.my # # -PERL modules: the WI::Netops collection # # # Assumptions: # # # Tested on: # -perl-5.12.2 # -net-snmp-5.6 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Set the $flash_mode, $tftp_mode, and $running_mode variables as # appropriate for your needs # -If you are using the 'tftp host' option, then: # * Make the directory into which you will save config files # * Change to this newly created directory # -Run this script # # # Here is an example session, assuming that the date is November 25, 2001, # the time is 3:00pm, and the box whose config file I have recently changed # is named "d5-esx": # # guru% cd /home/netops/logs/switches # guru% mkdir -p 2001-11-25/15:00 # guru% cd 2001-11-25/15:00 # guru% save-config -s yes d5-esx # guru% [...deleted...] # guru% ls # guru% d5-esx-config.before d5-esx-config.after # # # Caveats: # This script messes with permissions in the current directory -- don't # run this from your home directory; you won't like the results # # # Known Bugs: # # # To do: # -Add support for SNMPv3 # -Add error checking to flash_mode, such that if the specified # flash device doesn't exist, we respond intelligently # # Begin script # Load modules use strict; use warnings; use feature 'say'; use feature 'switch'; use Carp qw(carp cluck croak confess); use Cwd; use Data::Dumper; use English qw( -no_match_vars ); use File::Copy 'cp'; use Getopt::Std; use WI::Netops::CiscoTools 1.4.3; use WI::Netops::HostTools 1.0.4; use WI::Netops::NetopsTools 2.2.3; use WI::Netops::NetopsData 1.4.0; use WI::Netops::PingTools 1.1.7; use WI::Netops::SNMPTools 1.5.3; use WI::Netops::Utilities 1.4.4; # Declare global variables my $config_file; # Name of current config file my $backup_config; # Name of file on local storage to which we # will copy config file from tftp server my $flash_mode; # Boolean controlling save-to-flash capability my $new_config_file; # Name for new config file my $old_config_file; # Name for old config file my $running_mode; # Boolean controlling whether or not the script # will save running-config to startup-config my $tftp_mode; # Boolean controlling tftp/changetree capability # Define global variables # Debug stuff $program_name = 'save-config'; $usage = 'Usage: save-config -s {yes|no} [-d {integer}] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '2.0.0'; # Define user-configurable variables # File names $backup_config = 'slot0:backup-config'; # Modes $flash_mode = 0; $running_mode = 1; $tftp_mode = 1; # TFTP Stuff $tftp_server = $EMPTY_STR; $tftp_server = get_my_ipaddr() if $tftp_server eq $EMPTY_STR; $tftp_server_name = get_nodename($tftp_server); # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } ### Begin Main Program ############################################### { check_args(); # Check arguments read_config(); # Read Netops config file compile_mibs(); # Compile MIB files build_target(); # Populate @target target_check(); # Look for errors in @target basic_info(); # Gather basic information sanity_check(); # Exclude unusual cases print_before(); # Tell operator what I will do do_the_work(); # Do it clean_up(); # Chores print_after(); # Tell the operator what I did } ##### End Main Program ################################################# ######################################################################## # Save the new config file in $tftp_dir to the change tree as # "$target-config.after" ######################################################################## sub after_config { my $target = shift; # Debug trace trace_location('begin') if $debug; # Check for brain damage -e "$tftp_dir/$config_file" or print_it("$tftp_dir/$config_file doesn't exist"); # Copy new config file to current location if (cp ("$tftp_dir/$config_file", "$new_config_file") ) { chmod 0644, "./$new_config_file"; print_it("$tftp_dir/$config_file copied to ./$new_config_file"); } else { print_it("Could not copy $tftp_dir/$config_file to $new_config_file"); $error{$target} = "Could not copy $new_config_file"; } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Save the config file currently in $tftp_dir to the change tree as # "$target-config.before" ######################################################################## sub before_config { my $mode; # Mode of config file my $result; my $target = shift; my $world; # World bit of $mode # Verify existence and permissions unless (touch_file("$tftp_dir/$config_file", 0666)) { die "Unable to create $tftp_dir/$config_file"; } # Copy current config file to current location if (cp ("$tftp_dir/$config_file", "$old_config_file") ) { chmod 0644, "./$old_config_file"; print_it("$tftp_dir/$config_file copied to ./$old_config_file"); } else { print_it("Could not copy $tftp_dir/$config_file to $old_config_file"); $error{$target} = "Could not copy $old_config_file"; } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Set directory permissions. In our environment, we want the change tree # to be group-writeable ... we forget to do this manually, when we # create directories. This subroutine does this for us, in case # we've forgotten ######################################################################## sub clean_up { my $file; # Itererates across files in cwd my $mode; # mode of current directory, in octal my $parent_dir; # directory above starting directory my $start_dir; # starting directory # Debug trace trace_location('begin') if $debug; # Chores for $tftp_mode if ($tftp_mode) { # Assign variables for starting directory $start_dir = getcwd(); $mode = (stat($start_dir))[2]; $mode &= 07777; $mode = sprintf "%lo", $mode; say "mode of $start_dir = $mode" if $debug; # If starting directory isn't group writable, make it so unless ($mode eq "770") { chmod 0770, $start_dir or print_it("$start_dir is not group-writeable: $!"); } # Ensure that all files are flagged 440 opendir (DIR, $start_dir) or print_it("Cannot opendir $start_dir: $!"); while (defined ($file = readdir(DIR)) ) { next if $file =~ /^\.\.?$/; chmod 0440, "$start_dir/$file" or print_it("Cannot chmod files in $start_dir: $!"); say "file = $file" if $debug; } closedir DIR or warn "Coudn't close DIR: $!"; # Assign variables for parent directory chdir ".." or print_it("Cannot examine parent directory: $!"); $parent_dir = getcwd(); $mode = (stat($parent_dir))[2]; $mode &= 07777; $mode = sprintf "%lo", $mode; say "mode of $parent_dir = $mode" if $debug; # If parent directory isn't group writable, make it so if (not ($mode eq "770")) { chmod 0770, $parent_dir or print_it("$parent_dir is not group-writeable: $!"); } # Return to original directory chdir "$start_dir"; } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Copy old config files to the change tree, instruct the devices to save # their current config files to the tftp server, copy these new config # files to the changetree ######################################################################## sub do_the_work { my $result; # Debug trace trace_location('begin') if $debug; # Loop through the list of targets TARGET: for my $target (@target) { # Define meta variables $config_file = $target . '-config'; $old_config_file = $config_file . '.before'; $new_config_file = $config_file . '.after'; # Announce start say '--------------------------------------------------------' if $mode eq 'interactive'; print_it("Beginning to process $target"); # If permissions aren't correct, whine unless ( touch_file("$tftp_dir/$config_file", 0666) ) { print_it("Cannot write to $tftp_dir/$config_file, skipping"); next TARGET; } if ($dome) { # Save running-config to startup-config if ($running_mode) { if (write_mem($target)) { print_it('Success: saved running-config to startup-config'); } else { print_it('Failure: did not save running-config to startup-config'); } } # Save old config file to change tree before_config($target) if $tftp_mode; # Save startup-config to tftp server if ($tftp_mode) { my ($config, $result); $config = "$tftp_path/$config_file"; $result = download_config( {host => $target, file => $config} ); given ($result) { when (1) { print_it("Success: saved startup-config to $config"); } default { print_it("Failure: did not save startup-config to $config"); } } } # Save startup-config on tftp server to local storage if ($flash_mode) { my %arg = ( host => $target, original => $config_file, backup => $backup_config ); if (write_flash(\%arg)) { print_it("Success: copied $config_file to $backup_config"); } else { print_it("Failure: did not copy $config_file to $backup_config"); } } # Save new config file to change tree after_config($target) if $tftp_mode; } else { sleep $short; } # Announce completion print_it("Done processing $target"); say "-------------------------------------------------------\n\n" if $mode eq 'interactive'; } # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell operator what I did ######################################################################## sub print_after { my $dir = getcwd(); my $shit_happens = 0; # Did errors occur? # Debug trace trace_location('begin') if $debug; # If running from cron, don't print report return 1 if $mode eq 'batch'; say "\n# Here is what I did\n"; print < 0) { print < 11) { say "\n$target does not support SNMP/TFTP copy, ignoring" if $debug; push @remove, $target; } # Entertain operator print $BANG if $mode eq 'interactive'; } # Remove entries which failed checks prune_basic(@remove); # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <