package eod_templates_V2;
use strict;
use warnings;
use Data::Dumper;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 1.13;
@ISA = qw(Exporter);
@EXPORT = ();
@EXPORT_OK = qw(get_tmpl);
%EXPORT_TAGS = (
ALL => [qw(&get_tmpl)],
);
sub get_tmpl {
my $request = $_[0];
my %templates = (
version => "same bla as before\n",
config => 'same wall of text as before',
);
return "$templates{$request}";
}
1;
####
package eod_functions_V2;
use strict;
use warnings;
use Cwd 'abs_path';
use File::Find;
BEGIN { ##Discover and use required modules automatically at compile time
my $ABS_PATH = abs_path($0);
find(\&wanted, $ABS_PATH);
sub wanted {
if ( $_ eq "eod_templates_V2.pm"){unshift(@INC,$File::Find::dir);}
}
}
use Data::Dumper;
use Exporter;
use eod_templates_V2 qw(:ALL);
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 1.10;
@ISA = qw(Exporter);
@EXPORT = ();
@EXPORT_OK = qw(print_help_and_exit populate_static_hashes populate_config_hash validate_command_line_args get_config_from_file);
%EXPORT_TAGS = (
ALL => [qw(&print_help_and_exit &populate_static_hashes &populate_config_hash &validate_command_line_args &get_config_from_file)],
);
## BEGIN FUNCTIONS #################################################################################################
## SUB HELP ########################################################################################################
sub print_help_and_exit {
my $name = $_[0];
print "
\nSECTION-USAGE:
This script is highly versatile, make sure you familarize yourself with it properly, before using it\n
All options are case sensitive. Option/Argument handling is unix standard.\n
mandatory arguments:
--readfile|-r read cli commands from file and perform them on all CPEs (default = $name.cfg. see section config file for help)
--connection-proto|-c define the protocol to use to connect to the devices. (ssh OR telnet. No default setting)
optional arguments:
--prompt-based-auth|-p provide username + password for device access when asked. (default = enabled)
WARNING: Supercedes LOGIN details in config file you provide!
--file-based-auth|-f provide username + password for device access via the config-file. (default = disabled)
--outfile|-o expects filename of csv to write the output to. (default = $name.csv)
--no-quiet|-n log to STDOUT instead of logfile (mandatory if --debug is enabled, default = quiet)
--debug|-d activate debug-log-level WARNING: do NOT use with multiple CPEsoutput is massive!!! (default = disabled)
--help|-h print this help and exit
--verbose|-v enable verbose feedback in outputfile. Depending on the executed commands this can be several hundred characters!!! (default = disabled)
--Version|-V display extenensive version information and exit\n
advanced optional arguments (case sensitive):
--sendmail|-s expects comma separated list of e-mail addresses to send generated report to as argument (default = disabled)
alternatively it can be invoked multiple times with different addresses. (First occurence will always be TO, all others CC)
--max-connections|-m run script in forked mode (massively enhances performance) with the specified ammount of max child processes (1-25) (default = disabled)
--tacacs|-t verify tacacs functionality on VPN-Hubsite before attempting to process devices. will abort script execution if tacacs service is unresponsive
(default = disabled)
\nSECTION-CONFIG-FILE:
Keep in mind some devices handle commands case sensitive!
The file that you provide will be reset to its defaults during script execution!!!
open the default config file $name.CFG with an editor of your choice and study the instructions it contains.
You may modify the default file or create a new one and pass it to the script with option -r
Note: please report all unexpected behavior to ###. ty\n\n";
exit 0;
}
### SUB create_data_structs ## DOES: populate data structs ######################################################
sub populate_static_hashes {
my ($STATIC,$REPORT) = @_;
%$STATIC = (
MODE_EN => "",
MODE_CFG => "",
MODE_WR => "",
CHAR_DIS => "",
CHAR_EN => "",
CHAR_CFG => "",
CMD_EN => "",
CMD_CFG => "",
CMD_WR => "",
CHAR_NL => "",
CHAR_INVALID => "",
CHAR_PAGE => "",
CMD_QUIT => "",
CHAR_CONFIRM => "",
CMD_CONFIRM => "",
CMD_ROOTDIR => "",
CMD_SKIP => "",
CMD_SET_NO_PAGE => "",
);
%$REPORT = (
UNREACHABLE => '0',
OK => '0',
NOK => '0',
NOFEEDBACK => '0',
ERROR => '0',
);
}
### SUB populate_config ### Does: populate config hash with command line arguments #################################
sub populate_config_hash {
my ($CONFIG,$arrayref) = @_;
my ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$tacacs,$verbose,$header,$mailref,$path) = @{ $arrayref };
my $range = '1000000';
my $rand = int(rand($range)); ##generate random temp-db filename to allow for simultanous script execution
#put first element in array as mail_to and all others as CC
my $mailto = shift @{ $mailref };
my $mailcc = join '', @{ $mailref};
%$CONFIG = (
username => "",
password =>"",
verbose => "$verbose",
quiet => "$quiet",
hubsite => '1.1.1.1',
log => "$name.log",
lockfile => "$name.lock",
tempdb => "$path/$rand.db",
tacuser => "user",
tacpass => "pass",
cfg => "$configf",
outcsv => "$outf",
batchsize => "$batchsize",
mail_to => "$mailto",
mail_from => 'NMS-script@me.net',
mail_sub => "",
mail_cc => "$mailcc",
mail_data => "",
proto => "$proto",
tacacs => "$tacacs",
);
if ($prompt == 1){
get_username_and_password_from_stdin(\%$CONFIG); #if -p was selected, get username and password now otherwise when parsing config-file
}
}
### SUB get_username_and_password_from_stdin ### DOES: gets username and password from stdin ########################
sub get_username_and_password_from_stdin {
my $CONFIG = $_[0];
eval {
local $SIG{ALRM} = sub { die("timeout waiting for user input. Aborting script execution\n") };
alarm 10;
print "please enter device or tacacs username\n";
$CONFIG->{username} = ;
alarm 0;
chomp $CONFIG->{username};
alarm 10;
print "please enter device or tacacs password\n";
$CONFIG->{password} = ;
alarm 0;
chomp $CONFIG->{password};
};
if ($@){ die($@); }
}
### SUB get_username_and_password_from_file ### DOES: parses username and password string + store in hash ########
sub get_username_and_password_from_file {
my ($CONFIG,$line) = @_;
my @temp = split(/\=/, $line); #split setting and value
my $string = lc $temp[0];
$string =~ s/your-//;
unless(! defined( $temp[1]) ){ #config file setting supersedes prompt, unless its undefined
$CONFIG->{$string} = $temp[1]; #wichever it was is now in the CONFIG hash as a key(username or password) with the corresponding value
}
}
### SUB validate_command_line_args ### DOES: perform various sanity checks on command line arguments #############
sub validate_command_line_args {
my ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$tacacs,$verbose,$header,$maildst,$path) = @{ $_[0] };
if ( $header ){
print get_tmpl('version');
exit 0;
}
unless($proto =~ /ssh|telnet/i){
die("$0:ERROR: Connection protocol must be specified.\n");
}
unless( -e $configf){ die("$0:ERROR: $configf not found in $path. typo or missing path?\n");}
if ($debug == 1 && $quiet == 1){ die("$0:ERROR: log-level=debug can NOT be used in quiet mode to avoid massive logfiles!\n");}
if ( $batchsize > 30 ){
warn("$0:WARNING: More than 25 simultanous connections are NOT allowed!\nReducing Max_Connections to 25.\n");
$batchsize=25;
}elsif ( $batchsize < 0 ){
warn("$0:WARNING: Negative Max_Connections are not permitted, forking has been disabled.\n");
$batchsize = 0;
}
}
## SUB process_static_config_setting ### DOES: check if setting is valid and store in hash or discard #############
sub process_static_config_setting {
my ($STATIC,$line) = @_;
$line =~ s/DEFINE//;
my @t=split(/=/,$line); #split statement into key => value pairs
$t[0] =~ s/\s*//;
$t[1] =~ s/\\//;
if ( exists $STATIC->{$t[0]} ){
$STATIC->{$t[0]}=$t[1]; #key is supported, set value
}else{
warn("$0:WARNING $t[0] is NOT a valid static configuration option and will be ignored!\n"); #key is not supported and will be ignored
}
}
## SUB get_config_from_file ### DOES: process config file and set populate hashes accordingly ######################
sub get_config_from_file {
my ($DATA,$CONFIG,$STATIC) = @_;
my $count = 0;
open(my $input, "<", "$CONFIG->{cfg}") or die("$0:ERROR unable to read from $CONFIG->{cfg}. ERR: $?/$!");
while (<$input>){
chomp $_;
if ( $_ =~ /your-username/i || $_ =~ /your-password/i ){ #this line contains either the username or the password, it's irrelevant which
get_username_and_password_from_file($CONFIG,$_);
next;
}
if ( $_ =~ m/^#/ || ! $_ =~ /[a-z]|[A-Z]/ ){ next; } #ignore commented and empty lines, as well as password + username.
if ( $_ =~ m/^DEFINE/ && $_ =~ /\=/){ #process config definitions
process_static_config_setting($STATIC,$_);
next;
}
if ( $_ =~ m/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):/ ){ #line contains a valid ipv4 IP addr, this is a device-commands set
my (@metachars,@translations);
populate_static_arrays(\@metachars,\@translations);
$_ = substitute_escaped_metachars($_,\@metachars,\@translations); #substitute escaped metachars to prevent config parser from interpreting them
my @temp = split(/,/, $_); #split line from config-file into command sub-sets, first one containing device IP as well
my (@commands,@matches,$ip);
for (my $i=0; $i < @temp; $i++){ #process each subset
if ($i == 0){
my @temp1 = split(/:/, $temp[0]); #this is the first subset, it contains the IP
$ip = $temp1[0];
$count++;
$temp[0] =~ s/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)://; #remove IP from subset
}
my @arr;
if ($temp[$i] =~ /\=/ ){ #this subset contains pattern-matching instructions
my @temp2 = split(/\=/, $temp[$i]); #split subset into further subsets, first containing the command, the rest are patterns
$temp2[0] = substitute_placeholders($temp2[0],\@metachars,\@translations); #all splitting is done on this string, revert it back to its original form
$commands[$i] = $temp2[0]; #this is the command
for (my $c=1; $c < @temp2; $c++){ #these are all the patterns
$temp2[$c] = substitute_placeholders($temp2[$c],\@metachars,\@translations); ##undo prior substitutions
$arr[$c] = $temp2[$c];
}
$matches[$i] = \@arr;
}else{ #this subset does not contain pattern matching instructions
$temp[$i] = substitute_placeholders($temp[$i],\@metachars,\@translations); ##undo prior substitutions
$commands[$i] = $temp[$i];
$arr[$i] = "no-match-hook"; ##set flag for easy identification later on in the script
$matches[$i] = \@arr;
}
}
$$DATA{$ip}->{commands} = \@commands;
$$DATA{$ip}->{matches} = \@matches;
}
}
$CONFIG->{devices_count} = $count;
if ( $count == 0 ) { die("$0:ERROR $CONFIG->{cfg} does not contain valid Device-instruction-sets\n");}
close($input);
open(my $input1, ">", "$CONFIG->{cfg}") or warn("$0:WARNING unable to write to $CONFIG->{cfg}. CLEAR IT manually! ERR: $!/$?");
print $input1 get_tmpl("config"); #replace specified config file with template for security reasons
close($input1);
while ( my($key,$value) = (each %$STATIC)){
unless(defined($value) && $value ne ""){ die("$0:ERROR: $key definition missing in $CONFIG->{cfg}.Aborting Execution.\n");} #missing key definition, this can not be tolerated
}
}
### SUB populate_static_arrays ### DOES: populate static arrays metachars and translations ########################
sub populate_static_arrays {
my ($meta_ref,$trans_ref) = @_;
@{ $meta_ref } = (':','=','!',','); #declare config metachars
@{ $trans_ref } = ('#00','#01','#02','#03') #declare bi-directional placeholders
}
### SUB substitute_escaped_metachars ### DOES: substitute ctrl-chars for escaped metachars in $LINE ###############
sub substitute_escaped_metachars {
my ($line,$meta_ref,$trans_ref) = @_;
for (my $i=0; $i<@{ $meta_ref };$i++){
$line =~ s/\#\Q$meta_ref->[$i]/$trans_ref->[$i]/g; ## hardcoded ESCSEQ => #
}
return $line;
}
### SUB substitute_placeholders ### DOES: revert string to its original form priot to escapeing ####################
sub substitute_placeholders {
my ($line,$meta_ref,$trans_ref) = @_;
for (my $i=0; $i<@{ $trans_ref };$i++){
$line =~ s/$trans_ref->[$i]/$meta_ref->[$i]/g;
}
return $line;
}
1;
##
##
#!/usr/bin/perl -w
use strict;
use File::Find;
use Cwd 'abs_path';
BEGIN { ##Discover and use required modules automatically at compile time
my $ABS_PATH = abs_path($0);
find(\&wanted, $ABS_PATH);
sub wanted {
if ( $_ eq "eod_templates_V2.pm" or $_ eq "eod_functions_V2.pm"){unshift(@INC,$File::Find::dir);}
}
}
use IO::Handle;
use Expect;
use Net::Telnet;
use Data::Dumper;
use Parallel::ForkManager;
use Fcntl;
use MIME::Lite;
use Fcntl qw(:DEFAULT :flock);
use Getopt::Long;
Getopt::Long::Configure ("bundling");
use eod_templates_V2 qw(:ALL);
use eod_functions_V2 qw(:ALL);
my $path = Cwd::getcwd();
my $name = $0;
$name =~ s/\.pl//;
$name =~ s/\.\///;
my $exitstate = 0;
unless( ! @ARGV ){
$exitstate = Main();
}else{
print_help_and_exit($name);
}
exit($exitstate);
sub process_command_line_args {
#set default values for dynamic config settings
my ($prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$tacacs,$verbose,$header,@maildst) = (1,"$name.cfg",1,"$name.csv",0,0,"none",0,0);
GetOptions (
'file-based-auth|f' => sub { $prompt = 0;},
'prompt-based-auth|p' => \$prompt,
'readfile|r=s' => \$configf,
'outfile|o=s' => \$outf,
'no-quiet|n' => sub { $quiet = 0; },
'debug|d' => \$debug,
'Version|V' => \$header,
'max-connections|m=s' => \$batchsize,
'sendmail|s=s' => \@maildst,
'connection-proto|c=s' => \$proto,
'tacacs|t' => \$tacacs,
'verbose|v' => \$verbose,
'help|h' => sub { print_help_and_exit($name);}
) or print_help_and_exit($name);
@maildst=split(/,/,join(',',@maildst)); #in case -s was invoked with a comma separated list instead of multiple times
my $mailref = \@maildst;
my @args = ($name,$prompt,$configf,$quiet,$outf,$debug,$batchsize,$proto,$tacacs,$verbose,$header,$mailref,$path);
return \@args;
}
sub Main {
#get user defined dynamic config settings and perform various sanity checks
my $argref = process_command_line_args();
validate_command_line_args($argref);
#all args check out now create and populate static hashes
my (%STATIC,%REPORT);
populate_static_hashes(\%STATIC,\%REPORT);
#fill dynamic hash
my %CONFIG;
populate_config_hash(\%CONFIG,$argref);
#retrieve config settings from file
my %DATA;
get_config_from_file(\%DATA,\%CONFIG,\%STATIC);
return $exitstate; #will be set depending on outcome of actual device operations
}