#!/usr/local/bin/perl5.00502 -w

# $Id: pancho,v 4.0.1 2002/04/03 15:50:40 charles Exp $

##			     PANCHO
## 	      Copyright 2001-2002 Charles J. Menzes
## 		  http://pancho.lunarmedia.net/

## Send bug reports, feature requests, questions and everything else to:
## 		      pancho@lunarmedia.net

###################################################################
###################################################################

## module calls
use strict;
use Net::SNMP;
use Getopt::Long;
use Socket;
use POSIX qw(strftime);
use Sys::Hostname;
use Fcntl ':flock';

############ THE FOLLOWING VARIABLES CAN BE EDITED     ############
############ SPECIFIC TO YOUR ENVIRONMENT. PLEASE READ ############
############ THROUGH THE PERLDOC TO ENSURE YOU ARE     ############
############ USING THE CORRECT INFORMATION AND FORMAT  ############
############ REQUIRED.				       ############

my $community 	= '';		# your RW snmp community string

my $tftpserver 	= "";		# ip/hostname/fqdn of your tftpserver

my @devices 	= qw();		# list of network devices 

my $logging 	= "1";		# set this to "1" to enable logging

my $logfile 	= $ENV{HOME}."/pancho.log";	# full path to your logfile 

############ NO FURTHER EDITING SHOULD BE REQUIRED.    ############
############ DOING SO IS AT YOUR OWN RISK.	       ############

## this variable is included for administrators that would like 
## to make the default file suffix something other than ".cfg"
## filenames are by default $hostname$suffix meaning that you are 
## not restrained to having a dot3 suffix for your filename. an
## example would be "edge5.aus-config where -config is the value
## assigned to $suffix.
my $suffix = ".cfg";

## development variables
my $rcs = (qw$Revision: 4.0.1 $)[-1];

## ensure some arguments are given
die "\nPancho requires some flags to be specified.\nPlease try running $0 --help\n\n" 
  unless (@ARGV);

## command line options
my $upload;
my $download;
my $filehandle;
my $commit;
my $list;
my $host;
my $string;
my $version;
my $server;
my $regex;
my $help;
my $verbose;
my $path;
my $start;
my $reload;
my $fork;

GetOptions (	'upload'	=> \$upload,
		'download'	=> \$download,
		'filename=s'	=> \$filehandle,
		'commit'	=> \$commit,
		'list=s'	=> \$list,
		'host=s'	=> \$host,
		'server=s'	=> \$server,
		'string=s'	=> \$string,
		'version'	=> \$version,
		'regex=s'	=> \$regex,
		'help'		=> \$help,
		'verbose-help'	=> \$verbose,
		'path=s'	=> \$path,
		'startup'	=> \$start,
		'reload'	=> \$reload,
		'fork'		=> \$fork,
	   );


## generate random number used for mib instances
srand(time | $$);
my $rand = int(rand(90))+10; 

## set up oids
my %oid = (  
		## cisco-config-copy-mib
		method		=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.2.$rand",
		source		=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.3.$rand",
		destination	=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.4.$rand",
		ipaddress	=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.5.$rand",
		filename	=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.6.$rand",
		rowstatus	=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.14.$rand",
		state           => ".1.3.6.1.4.1.9.9.96.1.1.1.1.10.$rand",
		cause		=> ".1.3.6.1.4.1.9.9.96.1.1.1.1.13.$rand",

		## deprecated lsystem mibs
		wrnet   	=> ".1.3.6.1.4.1.9.2.1.55.",
                confnet 	=> ".1.3.6.1.4.1.9.2.1.53.",

		## universal router platform
		wrmem		=> ".1.3.6.1.4.1.9.2.1.54.0",

		## univeral router platform
		reload		=> ".1.3.6.1.4.1.9.2.9.9.0",

		## catalyst switch mibs
		cat_ipaddress	=> ".1.3.6.1.4.1.9.5.1.5.1.0",
		cat_filename	=> ".1.3.6.1.4.1.9.5.1.5.2.0",
		cat_rowstatus	=> ".1.3.6.1.4.1.9.5.1.5.3.0",
		cat_action	=> ".1.3.6.1.4.1.9.5.1.5.4.0",
		cat_result	=> ".1.3.6.1.4.1.9.5.1.5.5.0",

		## universal platform
		version		=> ".1.3.6.1.2.1.1.1.0",

	  );

my %filelocation =	( tftp		=> "1",
			  start		=> "3",
			  run		=> "4",
			);

my %state =     	( waiting 	=> "1",
                  	  running 	=> "2",
                  	  success  	=> "3",
                  	  failed	=> "4",
                	);

my %cause =		( 1	=> "Unknown Copy Failure",
		  	  2	=> "Bad File Name",
			  3	=> "Network timeout",
			  4	=> "Not Enough Memory",
			  5	=> "Source Configuration doesnt exist.",
			);

my %tftpResult =	( 1	=> "inProgress",
			  2	=> "success",
			  3	=> "No Response",
			  4	=> "Too Many Retries",
			  5	=> "No Buffers",
			  6	=> "No Processes",
			  7 	=> "Bad Checksum",
			  8	=> "Bad Length",
			  9	=> "Bad Flash",
			  10	=> "Server Error",
			  11	=> "User Canceled",
			  12	=> "Wrong Code",
			  13	=> "File Not Found",
			  14	=> "Invalid Tftp Host",
			  15	=> "Invalid Tftp Module",
			  16	=> "Access Violation",
			  17	=> "Unknown Status : Check TFTP Server",
			  18	=> "Invalid Storage Device",
			  19	=> "Insufficient Space On Storage Device",
			  20	=> "Insufficient Dram Size",
			  21	=> "Incompatible Image",
			);

## logging format and time
my $format;
my $user;

if ($logging) {
  my %clock = 	( 	month 	=> strftime("%b", localtime),
                	day 	=> strftime("%d", localtime),
                	time 	=> strftime("%H:%M:%S", localtime),
              	);

  $user = getpwuid($<);

  my $hostname = hostname(); 

  $format = "$clock{month} $clock{day} $clock{time} [$hostname] pancho:";
}

## show version and exit
&version if ($version);

## show verbose help menu and exit
die "\nThis flag is deprecated. Please run \"perldoc $0\".\n\n" if ($verbose);

## show the help menu and exit
&usage if ($help);

## build out count for error checking
my $count = 0;
$count++ if ($upload);
$count++ if ($download);

## ensure that we have a node or list of nodes 

if ($host) {
  @devices = ();
  push(@devices, $host);

} elsif ($list) {
  open(FH, $list)
    or die "\nCant open external file specified with --list.\nPlease check the path and ensure file exists.\n\n";
  @devices = <FH>;
  close(FH);

} elsif ( $#devices < 0 ) {
  print "\nYou have not specified a node or list of nodes to act upon!\n\n";
  exit 1;

}

## determine the path within the tftproot
my $tftppath = $path || "";

## determine if we are using the default server or another
$tftpserver = $server if ($server);

## declare a varible for the unresolved tftp server name
my $unresolved = $tftpserver;

## resolve fqdn/hostname for tftpserver
if ($tftpserver =~ /[a-zA-Z]/) {
  my $i = gethostbyname($tftpserver);
  $tftpserver = inet_ntoa($i);
}
  

## determine if we are using the default community string or another
$community = $string 
  if ($string);

## check to ensure that we have the information required

if (!$community) {
  print "\nYou have not specified an SNMP community.\n\n";
  exit 1;

} elsif (!$tftpserver && ($upload || $download)) {
  print "\nYou have not specified a tftp server.\n\n";
  exit 1;

}

if ($upload && $count == 1) {
  if ($start) {
    &execute('tftp','start');
  } else {
    &execute('tftp','run');
  }

} elsif ($download && $count == 1) {
  if ($start) {
    &execute('start','tftp');
  } else {
    &execute('run','tftp');
  }

} elsif ($commit) {
  &execute('wrmem',0);

} elsif ($reload) {
  &execute('reload',0);

} elsif ($count > 1) {

  print "\nYou cannot upload and download at the same time.\n\n"
    unless ($count == 0);

} else {

  ## exit
  exit 1;

}



## subroutines

sub version { print "\n  This is Pancho version $rcs\n\n"; exit; }
 
sub execute {
## pull source and destination of files
my %args = ( s => $_[0], d => $_[1], ); 

## declare a pid for forking
my $pid;

  ## 
  foreach (@devices) {
    chomp $_;

    ## put node into hash so it can be passed to other subroutines
    $args{h} = $_;

    ## if fork option is selected
    if ($fork) {
   
      ## if a child pid has not been spawned, do so.
      if ($pid = fork) {

        next;

      } elsif(defined($pid)) {
        ## spawn child process to act upon node
        &do_action(\%args);

        ## explicitly exit the child process
        exit;

      ## if forking error exit script
      } else {
      
        warn "\n\nPancho found errors in forking multiple processes!\nError: $!\n\n";

      ## endif 
      } 

    ## non-forking process
    } else {

      ## send node to be acted upon
      &do_action(\%args);

    }

  ## end of for loop
  }

  ## wait on chid processes to exit
  while (wait() != -1) {
    return 1 if ($?);
  }

  ## exit pancho
  exit;

## end subroutine
}

sub do_action {
my $args = shift;

      ## allow for comments
      return if ($args->{h} =~ /^\#/);
      return if ($args->{h} =~ /^$/);
      $args->{h} =~ s/\s*\#.*// if ($args->{h} =~ /\#/);

      ## test to see if hostname fits regex description
      return 1 if (($regex) and ($args->{h} !~ /$regex/));

      ## test to see if host resolves to ip address
      my $node = gethostbyname($args->{h});

      unless ($node) {
        ## if not print out a suitable message and move to next host
        print "\nThe following hostname could not be resolved: $args->{h}\n\n";

        ## skip to next host in list
        return 1;
      }

      ## test to see if hostname fits regex description
      return 1 if (($regex) and ($args->{h} !~ /$regex/));

      ## set value of filename on tftpserver
      my $filename;
      if ($filehandle) {
        $filename = $filehandle;

      } else {
        $filename = "$args->{h}$suffix";

      }

      ## query for ios version
      if ($count > 0) {
    
        ## set up initial parameters for this nodes snmp session(s)
        my $s = Net::SNMP->session( -hostname  => $args->{h},
                                    -community => $community );

        ## grab the ios major revision number
        my $ios_version = $s->get_request ($oid{version});

 
        ## grab an error if it exists
        $args->{e} = $s->error;

        ## close the snmp session
        $s->close;

        ## if the ios is undeterminable log it to screen and skip
        if ($args->{e}) {

          ## if the remote device is not able to be queried
          &log_action($args);

          ## after logging the error, skip to next host
          return 1;

        }

        ## test to see which os is on remote node
        if (($ios_version->{$oid{version}}) =~ /Version 1(?:1|0)/) {
          ## run for 10.x and 11.x
          &deprecated($args);

        } elsif (($ios_version->{$oid{version}}) =~ /Version 12/) {
          ## run for 12.x
          &ccCopy($args);

        } elsif (($ios_version->{$oid{version}}) =~ /Catalyst/i) {
          ## run for Catalysts
          &catos($args);

        } else {
          ## skip if not a supported device
          return 1;

        }

      }

      ## write config to memory
      if ($commit) {

        ## start session
        my $s = Net::SNMP->session( -hostname  => $args->{h},
                                    -community => $community );

        ## write to memory
        $s->set_request($oid{wrmem}, INTEGER, "1");

        ## close session
        $s->close;

        ## log output to screen and possibly external file
        &log_action($args);

      }

      ## reload the router
      if ($reload) {

        ## start the session
        my $s = Net::SNMP->session( -hostname  => $args->{h},
                                    -community => $community );

        ## reload the router
        $s->set_request($oid{reload}, INTEGER, "2");

        ## grab error if exists
        my $error = $s->error;

        ## put error value into hash
        $args->{e} = $error;

        ## close the session
        $s->close;

        ## log output to screen and possibly external file
        &log_action($args);
 
      }


      ## explicitly exit the child process
      exit if ($fork);

## end do_action
}

sub catos { 
my $args = shift;

  ## set value of filename on tftpserver
  my $filename;
  if ($filehandle) {
    $filename = $filehandle;

  } else {
    $filename = "$args->{h}$suffix";

  }

  if (($args->{s} eq "start") or ($args->{d} eq "start")) {
    print "\nCopying configurations to and from startup-config\nis not possible using the CatOS.\n\n";

  } else {

    ## determine the mib value for where the file will be sent
    my $i;
    if ($args->{s} eq "tftp") { $i = "2"; } else { $i = "3"; }

    ## create the session
    my $s = Net::SNMP->session( -hostname  => $args->{h},
				-community => $community );

    ## set up the request
    $s->set_request	( ## set the tftp server value
			  $oid{cat_ipaddress}, OCTET_STRING, $tftpserver,

		     	  ## set up the config file name
			  $oid{cat_filename}, OCTET_STRING, "$tftppath/$filename",
 
		     	  ## prep the session to go
			  $oid{cat_rowstatus}, INTEGER, 1,

		     	  ## send config
			  $oid{cat_action}, INTEGER, "$i",		
		   	);

    ## grab an error if exists
    my $error = $s->error;

    ## put error into hash
    $args->{e} = $error;

    if (!$error) {

      ## set default status as "running"
      my $result = "1";

      ## check for the results status
      while($result == "1") {

        ## get the current status of the tftp server's action
        my $current_state = $s->get_request ($oid{cat_result});

        $result = $current_state->{$oid{cat_result}};

      ## end while
      }


      ## failure!
      if ($result != "2") {

        ## add error message into $args hash
        $args->{e} = "$tftpResult{$result}";

      ## endif
      }

    ## endif
    }

    ## close snmp session
    $s->close;

    ## log output to screen and possibly external file
    &log_action($args);
      
  }

}

sub deprecated {
my $args = shift;

  ## set value of filename on tftpserver
  my $filename;
  if ($filehandle) {
    $filename = $filehandle;

  } else {
    $filename = "$args->{h}$suffix";

  }

  if (($args->{s} eq "start") or ($args->{d} eq "start")) {
    print "\nCopying configurations to and from startup-config\nis not possible using deprecated mibs.\n\n";    
 
  } else {
    my $mib;

    ## set up proper value for $mib
    if ($args->{s} eq "tftp") {
      $mib = $oid{confnet};
    } else {
      $mib = $oid{wrnet};
    }

    $mib = "$mib$tftpserver"; 

    my $s = Net::SNMP->session( -hostname  => $args->{h},
                                -community => $community );

    ## set up the request
    $s->set_request($mib, OCTET_STRING, "$tftppath/$filename");

    ## grab an error if it exists.
    my $error = $s->error;

    ## put error into hash
    $args->{e} = $error;

    ## close snmp session
    $s->close;

    ## log output to screen and possibly external file
    &log_action($args);

  }
}

sub ccCopy {
my $args = shift;

  ## set value of filename on tftpserver
  my $filename;
  if ($filehandle) {
    $filename = $filehandle;

  } else {
    $filename = "$args->{h}$suffix";

  }

  ## start up an snmp session
  my $s = Net::SNMP->session( -hostname  => $args->{h},
                              -community => $community );

  ## copy files across network
  $s->set_request   (  ## select method of transfer
                       $oid{method}, INTEGER, 1,

                       ## select source file location
                       $oid{source}, INTEGER, $filelocation{$args->{s}},

                       ## select destination file location
                       $oid{destination}, INTEGER, $filelocation{$args->{d}},

                       ## set tftpserver ip address
                       $oid{ipaddress}, IPADDRESS, $tftpserver,

                       ## set the filename being written
                       $oid{filename}, OCTET_STRING, "$tftppath/$filename",

                       ## set the session status
                       $oid{rowstatus}, INTEGER, 4,
                    );

  ## grab an error message if it exists
  my $error = $s->error;

  ## add error message into $args hash
  $args->{e} = $error;

  ## if no error...
  if (!$error) {

    ## set default status as "running"
    my $result = "1";

    ## check for the results status
    while(($result == "$state{running}") || ($result == "$state{waiting}")) {

      ## get the current status of the tftp server's action
      my $current_state = $s->get_request ($oid{state});

      $result = $current_state->{$oid{state}};

    ## end while
    }

    ## failure!
    if ($result != "3") {
 
      ## send snmp reqest to find cause of problem
      my $cause_req = $s->get_request ($oid{cause});

      ## assign result to the value returned from the query
      $result = $cause_req->{$oid{cause}};

      ## add error message into $args hash
      $args->{e} = "$cause{$result}";

    ## endif
    }

    ## clear the rowstatus for the remote device
    $s->set_request ($oid{rowstatus}, INTEGER, 6);
    
  ## endif
  }

  ## close the snmp session
  $s->close;

  ## log output to screen and possibly external file
  &log_action($args);

}

sub log_action { 
my $args = shift;

  ## set value of filename on tftpserver
  my $filename;
  if ($filehandle) {
    $filename = $filehandle;

  } else {
    $filename = "$args->{h}$suffix";

  }

#############  clean-up args for display #################

## prepare args for better display
$args->{h} = uc($args->{h})
  if ($args->{h} =~ /[a-zA-z]/);

$unresolved = uc($unresolved);

my $displaypath = "$tftppath/$filename";

## get rid of the leading slash
if (substr($displaypath,0,1) eq "/") {
  $displaypath = reverse("$displaypath");
  chop $displaypath;
  $displaypath = reverse("$displaypath");
}

##########################################################

  ## open log file for writing and put cursor at end of file
  if ($logging) {

    open(FH, ">>$logfile") 
      or warn "\n\nCant open file specified for logging.\nPlease check the path specified.\n\n";
    flock(FH,2);
    seek(FH,0,2);

  }

  ## log if error 
  if ($args->{e}) {
    ## log to screen
    print "\nERROR: $args->{e}\n\n";
    ## log to external file
    print FH "$format (ERROR) $args->{e}\n"
      if ($logging);

  ## log if source is tftp...
  } elsif ($args->{s} eq "tftp") {

    ## ...and destination is run...
    if ($args->{d} eq "run") {

      ## log to screen
      print "\nSUCCESS: copied $unresolved:$displaypath to $args->{h}:running-config\n\n";
      ## log to external file
      print FH "$format (SUCCESS) copied $unresolved:$displaypath to $args->{h}:running-config by $user\n"
        if ($logging);
  
    ## ...and destination is start...
    } elsif ($args->{d} eq "start") {

      ## log to screen
      print "\nSUCCESS: copied $unresolved:$displaypath $args->{h}:startup-config\n\n";
      ## log to external file
      print FH "$format (SUCCESS) copied $unresolved:$displaypath $args->{h}:startup-config by $user\n"
        if ($logging);
    }

  ## log if source destination is tftp...
  } elsif ($args->{d} eq "tftp") {

    ## ...and source is run....
    if ($args->{s} eq "run") {

      ## log to screen
      print "\nSUCCESS: copied $args->{h}:running-config to $unresolved:$displaypath\n\n";
      ## log to external file
      print FH "$format (SUCCESS) copied $args->{h}:running-config to $unresolved:$displaypath by $user\n"
        if ($logging);

    ## ...and source is start...
    } elsif ($args->{s} eq "start") {

      ## log to screen
      print "\nSUCCESS: copied $args->{h}:startup-config to $unresolved:$displaypath\n\n";
      ## log to external file
      print FH "$format (SUCCESS) copied $args->{h}:startup-config to $unresolved:$displaypath by $user\n"
        if ($logging);

    }

  ## end if for tftp loop
  }

  ## logging if writing to memory...
  if ($args->{s} eq "wrmem") {
    ## log to screen
    print "\nSUCCESS: copied $args->{h}:running-config to $args->{h}:startup-config\n\n";
    ## log to external file
    print FH "$format (SUCCESS) copied $args->{h}:running-config to $args->{h}:startup-config by $user\n"
      if ($logging);
  }

  ## logging if reloading...
  if ($args->{s} eq "reload") {
    if (!$args->{e}) {
      ## log to screen
      print "\nSUCCESS: initialized a reload of $args->{h}.\n\n";
      ## log to external file
      print FH "$format (SUCCESS) initialized reload on $args->{h} by $user\n"
        if ($logging);
    }
  }

  ## close loggin file
  if ($logging) {
    flock(FH,8);
    close(FH);
  }

## end log_action
}

sub usage {
print <<USAGE;

options   [ --upload | --download ]
	  [ --commit ]
          [ --start]
          [ --reload ]
          [ --filename <filename> ]
          [ --list <list> ]
          [ --host <hostname> ]
          [ --server <ip/fqdn/hostname> ]
          [ --string <snmp community> ]
	  [ --path <path within tftproot> ]
          [ --regex <regular expression> ]
	  [ --fork ]
          [ --version ]
          [ --help ]

requires  [ perl, net::snmp ]

USAGE
exit;
}

######################################################################
######################################################################

=head1 NAME

Pancho - Cisco Management through SNMP 

=head1 SYNOPSIS

 [ --upload | --download ]
 [ --start ]
 [ --commit ]
 [ --reload ]
 [ --list (filename) ]
 [ --host (hostname/ipaddress) ]
 [ --file (filename) ]
 [ --server (ip/fqdn/hostname) ]
 [ --string (rw snmp community) ]
 [ --path (path within tftproot) ]
 [ --regex (regular expression) ]
 [ --fork ]
 [ --version ]
 [ --help ]

=head1 DESCRIPTION

pancho was written with the goal of allowing network
admnistrators make a change to a group of cisco devices
without being required to log into each host.
pancho also provides the flexibility to allow admins to
use its function against a single host, a select group
or the entire network. in addition to changes to current
configurations on remote devices, pancho is also capable
of archiving device configurations manually or through
automated runs.

=head1 OPTIONS

 -u, --upload
    specify that pancho send a configuration
    file TO the remote device(s).

 -d, --download
    specify that pancho retrieve a configuration
    file FROM the remote device(s).

 --start
    change source/destination to startup-config or
    the nvram of the remote router. this will
    essentially cause the router to either execute
    a "copy tftp startup-config" or "copy startup-
    config tftp".

 -c, --commit
    specify that pancho perform a 'write memory'
    or 'copy run start' on the remote device(s).

 --reload
    request the remote node to immediately reload.

 -l, --list
    specify a local file that hold a list of device
    hostnames or ip addresses that pancho should
    perform actions against.
 
 --host
    specify an individual host on which pancho
    will perform actions.

 -f, --filename
    specify the local filename that pancho should
    send to the remote device.
 
 --server
    specify a tftp server that pancho will push
    or pull configurations to and from. this may
    be in the format of an ip address, a fully
    qualified domain name, or a hostname able to
    be resolved by the machine upon which pancho
    is being ran.

 --string
    specify a snmp read-write string.

 -p, --path
    specify a path within the tftproot.

 --regex
    specify a regular expression that pancho can
    use to filter out specific hosts from the
    host file that actions should be taken against.

 --fork  fork a seperate child process for each device
    which is being processed in the effort of
    decreasing run time. note that this can cause
    pancho to exit before all files have been
    retrieved.

 --version
    display the current version of pancho.

 --help
    short synopsis of flag options.

=head1 USAGE

getting started with pancho does not require much work,
however some thought should be put into the process.
pancho is capable of having a few default settings
predefined that take effect each time pancho is ran.

open pancho in your favorite text editor and look for
the area near the beginning of the file that highlights
three fields that can be updated with your specific
information. The field B<$community>, B<$tftpserver> and B<@devices>
all define their matching information. For $community
and $tftpserver, simply put the corresponding strings
between the quotes to the right of the equal sign.

@devices takes a bit more work, however it should not
prove too difficult. this field should be populated with
the group of devices that you feel will be configured or
archived most often on your network. the devices can be
entered by hostname or by ip address within the parentheses
to the right of the equal sign. at least one single space
should seperate each entry such that it may look like:

B<@devices = qw( border1 edge-5.lunarmedia.net 10.4.101.1 );>

at first glance, pancho appears to have a significantly
long command line. however, many of the option flags
seen above can be set as default such that the flags
themselves would only be called to override the default
settings. for example a default tftpserver may be set
within the pancho configuration using B<$tftpserver>, however 
you will still be able to specify:

B<--server 172.16.254.16>

in case you would like to push/pull your config to an
alternate server.

pancho can be set up to have a default group of nodes
that it will affect when ran. this list can be altered
by specifying the B<--host> flag to indicate a single node
or the B<--list> flag which will let you direct pancho to
read the group to be effected from a plain text file.
this text file should have each node listed on its own
line either by hostname or ip address (comments may be 
specified using "#" either at the beginning of the line or
midway through the line with all text after the "#" being
regarded as a comment).

another method which allows pancho to further limit 
the group of devices touched is the B<--regex> option. this
feature is very powerful in the hands of someone with
a fair amount of knowledge concerning regular expressions
and nodes grouped with logical naming conventions.

B<--regex ^core.*>

would indicate that within the default list of nodes, or
within the list obtained from an external text file, only
those with a name beginning in "core" would be affected.

using the B<--start> flag will cause pancho to send/receive
a configuration to/from a remote device's startup-config,
or nvram, rather than from the machine's running-config.
the caveat to the B<--start> flag is that this operation can
only be performed on devices using the newer cisco-copy-
config mibs. this means that most ios revisions prior to
12.x will not allow it.

the final optional flag is B<--filename>. if unspecified,
this value defaults to the hostname of the device being
touched with an extension of ".cfg". the B<--filename> flag
argument should always be used when a single configuration 
file will be uploaded to a group of devices. additionally,
B<--filename> may be used to specify an entire path to a 
specific individual file within a subdirectory of the 
tftproot:

B<--filename /cfg/2500/tacacs_config.txt>

in situations where you are not specifying a specific filename
but would like your configs to be written/read from a directory
beneath the tftproot, B<--path> allows you to specify an 
alternative:

B<--path /cfg/7200>

pancho's only mandatory flag is one of two options,
B<--upload> or B<--download>. this specifies whether or not
pancho will push a configuration to the device or pull
the remote device's config down to the local tftp server.
pancho lives remotely from the device and therefore views
the world from that perspective. B<--upload> signifies that
a file will be uploaded B<TO> the device. conversely,
B<--download> indicates that the config will be brought down
B<FROM> the device.

the B<--reload> option is available strictly on devices that
have the "snmp-server system-shutdown" parameter configured
on the remote router. this is obviously a significantly 
powerful option and is offered strictly for the ability to
upload a configuration to startup-config and then initialize
a reload to put the new config into effect.

the final option of B<--commit> is used to perform a remote
"write memory" or "copy run start". this option can be
used either individually, or in conjunction with B<--upload>
essentially committing to memory the changes as they are
being made.

=head1 EXAMPLES

in order to utilize pancho's full capacity for device 
configuration, one needs to be familiar with cisco's
'copy tftp run' or 'config net' syntax. in depth
discussion on the procedure can be found on cisco's
website at http://www.cisco.com.

a summary of the process is as follows. a remote
device will tftp a configuration file consisting of
standard ios commands into its running-config. the
device will merge the current running-config and the
command options received from the text file and then
apply the whole to its new running configuration.

an example may better state the process.

on the tftp server, we create a plain text file to
update the access-list 5 on a group of remote devices.
the plain text file, is as follows:

 !
 no access-list 5
 access-list 5 permit 10.6.21.64 0.0.0.31
 access-list 5 permit 10.12.71.0 0.0.0.255
 !
 end

since we plan on completely rewriting access-list 5
on the remote devices with this new acl, our first
statement is 'no access-list 5' which allows us to
start a new acl listing. the following acl statements
will then build the new access-list.

since cisco's 'copy tftp run' process first merges the
current running-config and our plain text commands before
it applies the newly created configuration, there will
be no interruption in traffic being inspected by access-
list 5. very different than just copying and pasting the
same commands into the cisco command prompt which applies
each statement with the hit of the carriage return
allowing for the possibility of serious consequences.

with knowledge of cisco's ios syntax, the possibilities for
remote configuration are endless, and with pancho's
capability for customization, an entire network may be
updated or archived from a single point of management.

the following a just a few of pancho's command line options
in running syntax:

update all devices with changes held in a flat file

B<pancho -u -f acl_update.txt>

archive all device configurations locally

B<pancho -d>

or to a remote tftp server

B<pancho -d --server ns1.lunarmedia.net>

download a host's configuration to a directory
within the tftproot called configs

B<pancho -d --host rsm716.pdx --path /configs>

commit changes on a group of hosts held within
an external file

B<pancho --commit --list hostfile.txt>

update all border devices with a new motd using an
alternate snmp community

B<pancho -u -f motd.txt --regex border --string f00B@r>

=head1 COMMENTS

For support installing, configuring and using Pancho, try subscribing
to the official mailing list. Sign up at:

B<http://pancho.lunarmedia.net/maillist.shtml>

Please send all comments regarding pancho to:

B<pancho@lunarmedia.net>

Check for new releases of pancho at:

B<http://pancho.lunarmedia.net/>

=head1 BUGS

Bugs found will be reported on the project website.

=head1 SEE ALSO

Perl,
Net::SNMP

=head1 AUTHOR

Charles J. Menzes <menzes@lunarmedia.net>

Pancho Copyright(C) 2001-2002

=cut
