package eod_templates;
use strict;
use warnings;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 1.01;
@ISA = qw(Exporter);
@EXPORT = ();
@EXPORT_OK = qw(printversion printtmpl);
%EXPORT_TAGS = (
ALL => [qw(&printversion &printtmpl)],
);
######################################################################################################################
## TEMPLATES #######################################################################################################
######################################################################################################################
sub printversion {
my $HEADER= '
################# HEADER ################################
#Does: Perform any amount of tasks in any mode on any #
# amount of devices of any OS by logging in to #
# them via SSH or telnet #
# Optionally perform pattern matching on results #
Requires: #
# r/w-permisson on Logfile, csvfile, lockfile, #
# tempdb and inputfile #
# eod_templates.pm and eod_functions.pm need #
# to be in the same dir as the script itself #
#To DOs: #
# clean up namespace and sub scopes #
# make connections more abstract and put into #
# eod_functions package #
# complete CLASS PRESETS #
# throroughly test escaping #
# excessive telnet/ssh testing #
# excessive character/behavior testing #
# excessive error/exception handling testing #
# assess PERM requirement. #
#Exit states: #
# 0 = all tasks done, no errors encountered #
# 0 = invoked with -v/-h or unsupported args #
# 1 = defined error (e.g. file handle error) #
# 1 = undefined error $? #
#Version: 0.7.1 ALPHA #
#last change: #
#Author: #
################# /HEADER ###############################
';
$HEADER .= "\n";
return $HEADER;
}
sub printtmpl {
my $TEMPLATE= "\n
###################################################################################################################
#---CONFIG FILE FOR EXECUTE ON DEVICE SCRIPT----------------------------------------------------------------------#
###################################################################################################################
###################################################################################################################
#---HELP----------------------------------------------------------------------------------------------------------#
###################################################################################################################
#Remove leading >#< to enable lines or add # to have script ignore them.
#Remember that this file is reset to its default during script execution.
#Make a copy if you may need it again.
#STATIC CLASS PRESETS SETTING DEFINITIONS
##select the actions the script needs to perform automatically (enable CLI, enter config mode, write when configuration was applied)
#DEFINE MODE_EN=
#DEFINE MODE_CFG=
#DEFINE MODE_WR=
## set the last character of the device's prompt in disabled, enabled and configure mode. (e.g. >,\$,#)
#DEFINE CHAR_DIS=
#DEFINE CHAR_EN=
#DEFINE CHAR_CFG=
## set the command to disable paging (e.g. terminal length 0)
#DEFINE CMD_SET_NO_PAGE=
## set the device specific message, that is displayed when paging occurs (e.g. PRESS ANY KEY TO CONTINUE, --more--)
#DEFINE CHAR_PAGE=
## set the command to jump to the end of paged output (e.g. NL,SPACE,skip)
#DEFINE CMD_SKIP=
## set the command to log off the device (e.g. exit, logout)
#DEFINE CMD_QUIT=
## set the Newline/Carriage Return character(s) this device expects (e.g. NL = \n, CR = \r, CRLF=\r\l)
#DEFINE CHAR_NL=
## set the message the device displays when a command violates the input syntax (e.g. INVALID COMAND, Bad Command)
#DEFINE CHAR_INVALID=
## set the enable command this device expects (e.g. enable)
#DEFINE CMD_EN=
## set the configure mode command (e.g. conf t, conf priv)
#DEFINE CMD_CFG=
## set the command to save configuration changes (e.g. wr, commit)
#DEFINE CMD_WR=
##set the command to return to the root path (e.g. end, cd)
#DEFINE CMD_ROOTDIR=
##set the prompt the device displays when it requires confirmation (e.g. [yes/no]:)
#DEFINE CHAR_CONFIRM=
##set the command the device expects to confirm/negate decisions
#DEFINE CMD_CONFIRM=
##If none of the existing class presets apply you can use a customized class.
#If you do not need a specific definition in your customized CLASS set it to ?.
###################################################################################################################
#---BEGIN CONFIG--------------------------------------------------------------------------------------------------#
###################################################################################################################
###################################################################################################################
#-<-SECTION LOGIN->-----------------------------------------------------------------------------------------------#
###################################################################################################################
YOUR-USERNAME=
YOUR-PASSWORD=
###################################################################################################################
#-<-SECTION STATIC CLASSES->--------------------------------------------------------------------------------------#
###################################################################################################################
#--<-STATIC CLASS PRESET CISCO 19xx->-----------------------------------------------------------------------------#
###################################################################################################################
DEFINE MODE_EN=yes
DEFINE MODE_CFG=yes
DEFINE MODE_WR=no
DEFINE CHAR_DIS=>
DEFINE CHAR_EN=#
DEFINE CHAR_CFG=#
DEFINE CMD_EN=en
DEFINE CMD_CFG=conf t
DEFINE CMD_WR=wr
DEFINE CMD_SET_NO_PAGE=terminal length 0
DEFINE CHAR_PAGE=--more--
DEFINE CMD_SKIP=NL
DEFINE CMD_QUIT=exit
DEFINE CHAR_NL=NL
DEFINE CHAR_INVALID=INVALID COMMAND
DEFINE CHAR_CONFIRM=[yes/no]:
DEFINE CMD_CONFIRM=yes
DEFINE CMD_ROOTDIR=end
###################################################################################################################
#--<-STATIC CLASS PRESET ASPEN-->---------------------------------------------------------------------------------#
###################################################################################################################
#DEFINE MODE_EN=no
#DEFINE MODE_CFG=no
#DEFINE MODE_WR=no
#DEFINE CHAR_DIS=>
#DEFINE CHAR_EN=>
#DEFINE CHAR_CFG=?
#DEFINE CMD_EN=?
#DEFINE CMD_CFG=?
#DEFINE CMD_WR=?
#DEFINE CMD_SET_NO_PAGE=?
#DEFINE CHAR_PAGE=Press
#DEFINE CMD_SKIP=SPACE
#DEFINE CMD_QUIT=logout
#DEFINE CHAR_NL=CR
#DEFINE CHAR_INVALID=Bad Command
#DEFINE CHAR_CONFIRM=?
#DEFINE CMD_CONFIRM=?
###################################################################################################################
#--<-STATIC CLASS CUSTOMIZED->------------------------------------------------------------------------------------#
###################################################################################################################
#DEFINE MODE_EN=
#DEFINE MODE_CFG=
#DEFINE MODE_WR=
#DEFINE CHAR_DIS=
#DEFINE CHAR_EN=
#DEFINE CHAR_CFG=
#DEFINE CMD_EN=
#DEFINE CMD_CFG=
#DEFINE CMD_WR=
#DEFINE CMD_SET_NO_PAGE=
#DEFINE CHAR_PAGE=
#DEFINE CMD_SKIP=
#DEFINE CMD_QUIT=
#DEFINE CHAR_NL=
#DEFINE CHAR_INVALID=
#DEFINE CHAR_CONFIRM=
#DEFINE CMD_CONFIRM=
###################################################################################################################
#---SECTION COMMAND SETS------------------------------------------------------------------------------------------#
###################################################################################################################
#IP:COMMAND1=MATCH1=MATCH2=MATCH...N,COMMAND2=MATCH2
# LIST OF METACHARACTERS!!!DO NOT USE ANY OF THESE EXCEPT FOR THEIR INTENTED PURPOSE!!!
# : <= separates the device IP from the COMMAND INSTANCES
# , <= separates all COMMAND Instances from each other (optionally these instances may include pattern matching instructions)
# = <= separates patterns. Matching is performed on the OUTPUT of the COMMAND. Applies ONLY to scope of the COMMAND instance.
# ! <= makes this pattern matching instance an inverse matching instruction. i.e. output does NOT contain pattern.
# # <= escapes any of the above Metacharacters. This needs to be put infront of Metacharacters that you do NOT want the script to interprete as such. (e.g. #= will escape the =)
#IP:COMMAND=MATCH=!DONTMATCH
###################################################################################################################
#---END CONFIG----------------------------------------------------------------------------------------------------#
###################################################################################################################
";
return $TEMPLATE;
}
######################################################################################################################
## / TEMPLATES #####################################################################################################
######################################################################################################################
1;
####
package eod_functions;
use strict;
use warnings;
use Data::Dumper;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = 1.10;
@ISA = qw(Exporter);
@EXPORT = ();
@EXPORT_OK = qw(help sanatize taint gettime parsetempdb);
%EXPORT_TAGS = (
ALL => [qw(&help &sanatize &taint &gettime &parsetempdb)],
);
######################################################################################################################
## BEGIN FUNCTIONS #################################################################################################
######################################################################################################################
######################################################################################################################
## SUB HELP ########################################################################################################
######################################################################################################################
sub help {
my $NAME = $_[0];
$NAME =~ s/\.pl//;
$NAME =~ s/\.\///;
print "\nSECTION-USAGE: $0 \nThis script is highly versatile, make sure you familarize yourself with it properly, before using it\n";
print "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)
--quiet|-q log to logfile (default = enabled)
--no-quiet|-n log to STDOUT instead of logfile (mandatory if --debug is enabled, default = disabled)
--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";
print "
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)\n";
print "
\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
\n";
print "Note: please report all unexpected behavior to e-mail ty\n\n";
return 1;
}
######################################################################################################################
## / SUB HELP ######################################################################################################
######################################################################################################################
######################################################################################################################
## SUB SANATIZE ### DOES: substitute ctrl-chars for escaped metachars in $LINE ####################################
######################################################################################################################
sub sanatize {
my ($LINE,$METACHARS,$TRANSLATIONS) = @_;
for (my $I=0; $I<@$METACHARS;$I++){
$LINE =~ s/\#\Q$$METACHARS[$I]/$$TRANSLATIONS[$I]/g; ## hardcoded ESCSEQ => #
}
return $LINE;
}
######################################################################################################################
## / SUB SANATIZE ##################################################################################################
######################################################################################################################
######################################################################################################################
## SUB TAINT ### DOES: substitute ctrl-chars for unescaped metachars in $LINE ####################################
######################################################################################################################
sub taint {
my ($LINE,$METACHARS,$TRANSLATIONS) = @_;
for (my $I=0; $I<@$TRANSLATIONS;$I++){
$LINE =~ s/$$TRANSLATIONS[$I]/$$METACHARS[$I]/g;
}
return $LINE;
}
######################################################################################################################
## / SUB TAINT #####################################################################################################
######################################################################################################################
######################################################################################################################
## SUB GETTIME ### DOES: returns SQL compatible TS in UK format ##################################################
######################################################################################################################
sub gettime {
my @months = qw(01 02 03 04 05 06 07 08 09 10 11 12);
my @weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
my ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
my $year = 1900 + $yearOffset;
my $T_GER = "$hour:$minute:$second, $weekDays[$dayOfWeek] $dayOfMonth/$months[$month]/$year";
my $T_UK = "$weekDays[$dayOfWeek] $months[$month]/$dayOfMonth/$year $hour:$minute:$second";
return $T_UK;
}
######################################################################################################################
## / SUB GETTIME ###################################################################################################
######################################################################################################################
######################################################################################################################
## SUB PARSETEMPDB ### DOES: parses tempdb and generates humanly readible REPORT in %REPORT ######################
######################################################################################################################
sub parsetempdb {
my ($REPORT,$CONFIG) = @_;
my $STRING;
open(my $CSV, ">$$CONFIG{OUTCSV}") || die("$0::PARSETEMPDB:ERROR Unable to open $$CONFIG{OUTCSV} for writing!\n");
my $TH = "DATE;IP";
open(my $DB, "<$$CONFIG{TEMPDB}") || die("$0::PARSETEMPDB:ERROR Unable to open $$CONFIG{TEMPDB}!\n");
my @LINES;
$LINES[0]="TH";
my ($COUNT,$COUNTREF)=(0,0);
while (<$DB>){
$_ =~ s/\|\|\|//;
my @TEMP = split(/\|\|/, $_);
$COUNT = @TEMP;
if ($COUNT > $COUNTREF){$COUNTREF = $COUNT;}
if ($_ =~ /h00kme/i ){
$_ =~ s/h00kme/IP is unreachable/;
push(@LINES, $_);
$$REPORT{UNREACHABLE}++;
next;
}
if ($_ =~ /noble/i ){
$_ =~ s/noble/Changing into privileged mode failed/;
push(@LINES, $_);
$$REPORT{ERROR}++;
next;
}
$STRING = "";
for (my $C=0; $C <= @TEMP; $C++){
unless(! defined($TEMP[$C])){ $STRING .= "$TEMP[$C];";}
}
$STRING =~ s/\s+/ /g;
push(@LINES, $STRING);
if ( $STRING =~ /NOK/ ) { $$REPORT{NOK}++; next; }
if ( $STRING =~ /OK/ ) { $$REPORT{OK}++; next; }
if ( $STRING =~ /ERROR/ ) { $$REPORT{ERROR}++; }
}
$TH = "DATE;IP";
for ( my $C = 2; $C <= $COUNTREF; $C++ ){
$TH .= ";COMMAND;RESULT;DETAILS";
if($$CONFIG{VERBOSE} == 1){$TH .= ";VERBOSE FEEDBACK"; $C +=1;}
$C += 2;
}
$LINES[0]=$TH;
foreach (@LINES){
print $CSV "$_\n";
}
close($DB);
close($CSV);
return %$REPORT;
}
######################################################################################################################
## / SUB PARSETEMPDB ###############################################################################################
######################################################################################################################
######################################################################################################################
## SUB SENDMAIL ### DOES: generates and sends e-mail to all recipients with attached csv report ##################
######################################################################################################################
#####NOT EXPORTED YET
sub sendmail {
my ($REPORT,$CONFIG,$RESULT) = @_;
my $T = gettime();
my $MAIL_TO = $$CONFIG{MAIL_TO}[0];
delete $$CONFIG{MAIL_TO}[0];
my @MAIL_CC = $$CONFIG{MAIL_TO};
if ($RESULT == 1){ ##CASE: Script execution OK
$$CONFIG{MAIL_DATA} .= "
Overview - completed $T:
Pattern matches successful: $$REPORT{OK}
Pattern matches unsuccessful: $$REPORT{NOK}
DEVICE UNREACHABLE: $$REPORT{UNREACHABLE}
ERRORS: $$REPORT{ERROR}
command success, but no output: $$REPORT{NOFEEDBACK}
";
$$CONFIG{MAIL_SUB} .= "processed $$CONFIG{DEVICES_COUNT} devices. $$REPORT{UNREACHABLE} unreachable. ";
my $mh = MIME::Lite->new(
From => $$CONFIG{MAIL_FROM},
To => $$MAIL_TO,
Cc => @MAIL_CC,
Subject => $$CONFIG{MAIL_SUB},
Type => 'multipart/mixed'
) or die("$0::SENDMAIL:ERROR creating new mail object: $!/$?\n");
$mh->attach (
Type => 'TEXT',
Data => $$CONFIG{MAIL_DATA},
) or die("$0::SENDMAIL:ERROR adding the text message part: $!/$?\n");
$mh->attach (
Type => "text/csv",
Path => $$CONFIG{OUTCSV},
Filename => $$CONFIG{OUTCSV},
Disposition => 'attachment'
) or die("$0::SENDMAIL:ERROR adding $$CONFIG{OUTCSV} to mail object: $!/$?\n");
$mh->send;
}else{ ##CASE: Script execution aborted because tacacs verification failed
$$CONFIG{MAIL_DATA} .= "ERROR: TACACS SERVICE VERIFICATION FAILED!\nReason:";
if ($RESULT == 1){ $$CONFIG{MAIL_DATA} .= "HUBSITE: $$CONFIG{HUBSITE} is unreachable\n";}
if ($RESULT == 2){ $$CONFIG{MAIL_DATA} .= "Provided Username/password combination failed Authentication\n";}
if ($RESULT == 2){ $$CONFIG{MAIL_DATA} .= "Provided Username/password combination passed Authentication, but tacacs profile doesn't allow privileged mode\n";}
$$CONFIG{MAIL_SUB} .= "$$CONFIG{NAME}: tacacs service verification failed. script execution aborted";
my $mh = MIME::Lite->new(
From => $$CONFIG{MAIL_FROM},
To => $MAIL_TO,
Cc => @MAIL_CC,
Subject => $$CONFIG{MAIL_SUB},
Type => 'multipart/mixed'
) or die("$0::SENDMAIL:ERROR creating new mail object: $!/$?\n");
$mh->attach (
Type => 'TEXT',
Data => $$CONFIG{MAIL_DATA},
) or die("$0::SENDMAIL:ERROR adding the text message part: $!/$?\n");
$mh->send;
}
return $RESULT;
}
######################################################################################################################
## / SUB SENDMAIL ##################################################################################################
######################################################################################################################
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.pm" or $_ eq "eod_functions.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"); ##=> not supported on NMS5, ("ignorecase_always");
use eod_templates qw(:ALL);
use eod_functions qw(:ALL);
######################################################################################################################
## CLA CHECK ########################################################################################################
######################################################################################################################
if ($#ARGV < 0 ) { &help($0); exit 0;}
my $PATH = Cwd::getcwd();
my $NAME = $0;
$NAME =~ s/\.pl//;
$NAME =~ s/\.\///;
my ($TACUSER,$TACPASS,$VERSION,%DEFINES,%DATA,@MAILDST);
my ($QUIET,$DEBUG,$PROMPT,$BATCHSIZE,$OUTFILE,$CONFIGFILE,$TACACS,$PROTO,$VERBOSE) = (1,0,1,0,"$NAME.csv","$NAME.cfg",0,"NONE",0);
GetOptions ( 'file-based-auth|f' => sub { $PROMPT = 0;},
'prompt-based-auth|p' => \$PROMPT,
'readfile|r=s' => \$CONFIGFILE,
'quiet|q' => \$QUIET,
'outfile|o=s' => \$OUTFILE,
'no-quiet|n' => sub { $QUIET = 0; },
'debug|d' => \$DEBUG,
'Version|V' => \$VERSION,
'max-connections|m=s' => \$BATCHSIZE,
'sendmail|s=s' => \@MAILDST,
'connection-proto|c=s' => \$PROTO,
'tacacs|t' => \$TACACS,
'verbose|v' => \$VERBOSE,
'help|h' => sub { &help($0); exit 0;}
) || die(&help($0));
#perform various sanity checks
if ( $VERSION ){ print printversion(); exit 0; }
unless($PROTO =~ /ssh|telnet/i){ die("$0::MAIN:ERROR: Connection protocol must be specified.\n");}
unless( -e $CONFIGFILE){ die("$0::MAIN:ERROR: $CONFIGFILE not found in $PATH. typo or missing path?\n");}
if ($DEBUG == 1 && $QUIET == 1){ die("$0::MAIN:ERROR: log-level=debug can NOT be used in quiet mode to avoid massive logfiles!\n");}
if ( $BATCHSIZE > 30 ){
warn("$0::MAIN:WARNING: More than 25 simultanous connections are NOT allowed!\nReducing Max_Connections to 25.\n");
$BATCHSIZE=25;
}elsif ( $BATCHSIZE < 0 ){
warn("$0::MAIN:WARNING: Negative Max_Connections are not permitted, forking has been disabled.\n");
$BATCHSIZE = 0;
}
@MAILDST=split(/,/,join(',',@MAILDST));
## GET LOGIN DATA ##################################################################################################
open(my $FH, "<$CONFIGFILE") || die("$0::MAIN::ERROR Unable to read from $CONFIGFILE\n");
if ( $PROMPT == 1 ){
print "Starting Prompt based authentication (default. consult help for more information):\n";
print "please enter device or tacacs username\n";
$TACUSER = ;
chomp $TACUSER;
print "please enter device or tacacs password\n";
$TACPASS = ;
chomp $TACPASS;
}else{
while (<$FH>) {
if ( $_ =~ /your-username/i ){
my @TMP = split(/=/,$_);
if ( $TMP[1] =~ /username/i || @TMP < 2 ){ die("$0::MAIN:ERROR: file-based-authentication was selected but no username was provided in the config file!\n");}
$TACUSER = $TMP[1];
chomp $TACUSER;
}elsif ( $_ =~ /your-password/i ){
my @TMP1 = split(/=/,$_);
if ( $TMP1[1] =~ /username/i || @TMP1 < 2){ die("$0::MAIN:ERROR: file-based-authentication was selected but no password was provided in the config file!\n");}
$TACPASS = $TMP1[1];
chomp $TACPASS;
}else{
next;
}
}
}
unless( $TACUSER && $TACPASS ){
die("$0::MAIN:ERROR: Retrieval of LOGIN DATA from $CONFIGFILE failed, please try editing it again or switch to prompt based authentication\n");
}
close($FH);
## / GET LOGIN DATA ################################################################################################
######################################################################################################################
## / CLA CHECK #####################################################################################################
######################################################################################################################
######################################################################################################################
## STATIC CONFIGURATION AND DATA STRUCT INIT #######################################################################
######################################################################################################################
my $TEMPLATE = printtmpl();
our (@METACHARS,@TRANSLATIONS);
($METACHARS[0],$METACHARS[1],$METACHARS[2],$METACHARS[3]) = (':','=','!',',');
($TRANSLATIONS[0],$TRANSLATIONS[1],$TRANSLATIONS[2],$TRANSLATIONS[3]) = ('#00','#01','#02','#03');
my %STATIC = ( ##initalize static device-config
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 => "",
);
my $RANGE = '1000000';
my $RAND = int(rand($RANGE)); ##generate random temp-db filename to allow for simultanous script execution
my %CONFIG = ( ## initalize dynamic config
VERBOSE => "$VERBOSE",
QUIET => "$QUIET",
HUBSITE => '1.1.1.1',
LOG => "$NAME.log",
LOCKFILE => "$NAME.lock",
TEMPDB => "$PATH/$RAND.db",
DEBUG => "$DEBUG",
USER => "$TACUSER",
PASS => "$TACPASS",
TACUSER => "user",
TACPASS => "pass",
CFG => "$CONFIGFILE",
OUTCSV => "$OUTFILE",
BATCHSIZE => "$BATCHSIZE",
MAIL_TO => \@MAILDST,
MAIL_FROM => 'NMS-script@me.net',
MAIL_SUB => "",
MAIL_CC => "",
MAIL_DATA => "",
PRESET => "$TEMPLATE",
PROTO => "$PROTO",
TACACS => "$TACACS",
);
my %REPORT = (
UNREACHABLE => '0',
OK => '0',
NOK => '0',
NOFEEDBACK => '0',
ERROR => '0',
);
######################################################################################################################
## / STATIC CONFIGURATION AND DATA STRUCT INIT #####################################################################
######################################################################################################################
######################################################################################################################
## MAIN ############################################################################################################
######################################################################################################################
open(my $PERM, ">>$CONFIG{OUTCSV}") || die("$0::MAIN:Unable to create/write to $CONFIG{OUTCSV}. ERROR: $?");
print $PERM "";
close($PERM);
our $OUT;
my ($RESULT,@ERRORS,$LF) = (0);
if ( $QUIET == 1 ){
open($OUT, ">>$CONFIG{LOG}") || die("$0::MAIN:ERROR Unable to open $CONFIG{LOG} for writing");
open(STDERR, ">>$CONFIG{LOG}") || die("$0::MAIN:ERROR Unable to append STDERR to $CONFIG{LOG}\n");
# open($LF, ">$CONFIG{LOG}.lock") || warn("$0::main unable to open lockfile to ensure log clearing consistency!\n");
}else{
open($OUT, ">&STDOUT") || die("$0::MAIN:ERROR Unable to append output to STDOUT\n");
}
$OUT->autoflush(1);
#print Dumper \%STATIC;
$RESULT = &getdata(\%DATA,\%CONFIG,\%STATIC); ##read config-file
print Dumper \%DATA;
#print Dumper \%CONFIG;
#print Dumper \%STATIC;
if($CONFIG{TACACS} == 1){
$RESULT = &verify_tacacs(\%CONFIG); ##connect to static site to verify general tacacs functionality
}
if($RESULT != 0 && $TACACS == 1){
my $RESPONSE = &sendmail(\%REPORT,\%CONFIG,$RESULT); ## tacacs failed, send mail + exit with ERROR
die("$0::MAIN:ERROR tacacs service functionality verification failed. Aborting script execution!\n");
}
##tacacs functionality verified process all tasks on all devices (in childs) and print output to temp-db
if ($PROTO =~ m/ssh/i){
%DATA = &connect2cpe_SSH(\%DATA,\%CONFIG,\%STATIC);
}else{
%DATA = &connect2cpe_TELNET(\%DATA,\%CONFIG,\%STATIC);
}
%REPORT = parsetempdb(\%REPORT,\%CONFIG); ##join tempdb in humanly readable format and generate final output
unlink $CONFIG{LOCKFILE} || warn("$0::MAIN:WARNING Unable to delete $CONFIG{LOCKFILE}\n");
unlink $CONFIG{TEMPDB} || warn("$0::MAIN:WARNING Unable to delete $CONFIG{TEMPDB}\n");
if ( @MAILDST > 0 ){
$RESULT = &sendmail(\%REPORT,\%CONFIG,"1"); ##generate report, attach to generated email send email
}
close($OUT);
#if ($CONFIG{QUIET} == 1){ close($LF); }
exit($RESULT);
######################################################################################################################
## / MAIN ##########################################################################################################
######################################################################################################################
######################################################################################################################
## SUB GETDATA #####################################################################################################
######################################################################################################################
sub getdata {
my ($DATA,$CONFIG,$STATIC) = @_;
my $COUNT = 0;
my (@TEMP,@TEMP1,@TEMP2,$IP);
if ( $CONFIG{CFG} ne "" ){
open(my $INPUT, "<$CONFIG{CFG}") || die("$0::GETDATA:ERROR unable to read from $CONFIG{CFG}. ERR: $!");
while (<$INPUT>){
chomp $_;
if ( $_ =~ m/^#/ || $_ =~ /your-username/i || $_ =~ /your-password/i || ! $_ =~ /[a-z]|[A-Z]/ ){ next; }
if ( $_ =~ m/^DEFINE/ && $_ =~ /\=/){
$_ =~ s/DEFINE//;
my @T=split(/=/,$_);
$T[0] =~ s/\s*//;
$T[1] =~ s/\\//;
if ( exists $STATIC{$T[0]} ){
$STATIC->{$T[0]}=$T[1];
}else{
die("$0::GETDATA:ERROR: $T[0] is NOT a valid static configuration option!\n");
}
}
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]?):/ ){
my @COMMANDS;
my @MATCHES;
$_ = sanatize($_,\@METACHARS,\@TRANSLATIONS);
@TEMP = split(/,/, $_);
for (my $I=0; $I < @TEMP; $I++){
if ($I == 0){
@TEMP1 = split(/:/, $TEMP[0]);
$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]?)://;
}
my @ARR;
if ($TEMP[$I] =~ /\=/ ){
@TEMP2 = split(/\=/, $TEMP[$I]);
$TEMP2[0] = taint($TEMP2[0],\@METACHARS,\@TRANSLATIONS);
$COMMANDS[$I] = $TEMP2[0];
for (my $C=1; $C < @TEMP2; $C++){
$TEMP2[$C] = taint($TEMP2[$C],\@METACHARS,\@TRANSLATIONS);
$ARR[$C] = $TEMP2[$C];
}
for(my $I=0; $I<@ARR;$I++){
unless(defined($_)){ delete $ARR[$I]; }
}
$MATCHES[$I] = \@ARR;
}else{
##no-match-hook
$TEMP[$I] = taint($TEMP[$I],\@METACHARS,\@TRANSLATIONS);
$COMMANDS[$I] = $TEMP[$I];
$ARR[$I] = "no-match-hook";
$MATCHES[$I] = \@ARR;
}
}
$DATA{$IP}->{COMMANDS} = \@COMMANDS;
$DATA{$IP}->{MATCHES} = \@MATCHES;
}
}
$CONFIG{DEVICES_COUNT} = $COUNT;
if ( $COUNT == 0 ) { die("$0::GETDATA:ERROR $CONFIG{CFG} does not contain valid Device-instruction-sets\n");}
close($INPUT);
open(my $INPUT1, ">$CONFIG{CFG}") || warn("$0::GETDATA:WARNING unable to write to $CONFIG{CFG}. CLEAR IT manually! ERR: $!/$?");
print $INPUT1 "$CONFIG{PRESET}";
close($INPUT1);
while ( my($KEY,$VALUE) = (each %STATIC)){
unless(defined($VALUE) && $VALUE ne ""){ die("$0::GETDAT:ERROR: $KEY definition missing in $CONFIG{CFG}.Aborting Execution.\n");}
}
}
return 1;
}
######################################################################################################################
## / SUB GETDATA ###################################################################################################
######################################################################################################################
######################################################################################################################
## SUB VERIFY TACACS ###############################################################################################
######################################################################################################################
sub verify_tacacs {
my ($CONFIG) = @_;
my $SHELL = 1;
my $exp = new Expect;
$exp->log_stdout($CONFIG{DEBUG}); ##For debugging only
$exp->exp_internal($CONFIG{DEBUG}); ##For debugging only
$exp->log_user($CONFIG{DEBUG}); ##For Debugging only
$exp->raw_pty(1);
$exp->match_max(1000000);
$exp->spawn("ssh", "-l$CONFIG{TACUSER}", "$CONFIG{HUBSITE}") || die("$0::VERIFY_TACACS:ERROR can not spawn ssh to $CONFIG{HUBSITE}. ERR/SYS: $! / $?");
sleep 0.1;
$exp->expect(10,
[ qr/\? /, sub {
$exp->send("yes\n");
sleep 1;
$exp->send("$CONFIG{TACPASS}\n");
$SHELL = '0';}],
[ qr/assword:/, sub {
sleep 0.1;
$exp->send("$CONFIG{TACPASS}\n");
$SHELL = '0';}],
);
unless ( $SHELL == '1' ) ##auth-req success, SHELL = 0, proceed
{
$exp->expect(10,
[ qr/>/, sub {
$exp->send("en\n");
sleep 0.1;
$exp->send("$CONFIG{PASS}\n"); }], ## verify user has correct privileges in tacacs
[ qr/#/, sub { $exp->send("\n");}], ##all good Shell stays at 0
[ qr/assword:/, sub { ##auth failed => SHELL = 2
unless($exp->soft_close()){$exp->hard_close();}
$SHELL = 2;
}],
);
if ($SHELL == 1){
$exp->expect(10,
[ qr/>/, sub {
$exp->send("exit\n"); ## auth for priv mode failed => SHell = 3
$SHELL = 3; }],
[ qr/#/, sub {
$exp->send("exit\n"); ## priv auth successful => Shell = 0
$SHELL = 1;}],
);
}
}
return $SHELL;
## ERRORCODES:
# 0 = OK
# 1 = NOK - HUBSITE unreachable
# 2 = NOK - login to HUBSITE failed
# 3 = NOK - changing privileges failed
}
######################################################################################################################
## / SUB VERIFY TACACS ##############################################################################################
######################################################################################################################
######################################################################################################################
## SUB CONNECT2CPE_SSH #############################################################################################
######################################################################################################################
sub connect2cpe_SSH {
my ($DATA,$CONFIG) = @_;
my ($SHELL,$FB,$T,$STRING,$ROUTER,$COMMAND,$MATCH,%TEMP,$RESULT,@RESULTS) = (0);
my $pm = new Parallel::ForkManager($CONFIG{BATCHSIZE});
$pm->run_on_finish(
sub { my ($PID, $EXITCODE) = @_;
if ($CONFIG{QUIET} == 0){
if ($EXITCODE == 1){
print $OUT "child(PID = $PID) terminated with exit state: NOK\n";
}else{
print $OUT "child(PID = $PID) terminated with exit state: OK\n";
}
}
}
);
$pm->run_on_start(
sub {
if ($CONFIG{QUIET} == 0){
print $OUT "Forking child...\n";
}
}
);
$pm->run_on_wait(
sub {
#print "control\n";
},
0.1
);
for my $ID (keys %DATA){
next unless $ID ne 'CFG';
#####following code is run in child-processes#####
my $PID = $pm->start($ID) and next;
my $EXIT = 0;
my $exp = new Expect;
$exp->log_stdout($CONFIG{DEBUG}); ##For debugging only
$exp->exp_internal($CONFIG{DEBUG}); ##For debugging only
$exp->log_user($CONFIG{DEBUG}); ##For Debugging only
$exp->raw_pty(1);
$exp->match_max(1000000);
$exp->spawn("ssh", "-l$CONFIG{USER}", "$ID") || die("$0::CONNECT2CPE_SSH:ERROR can not spawn ssh process to $ID. ERR/SYS: $! / $?");
sleep 0.1;
my $SBC = $STATIC{CHAR_DIS};
my ($NL,$SKIP) = ($STATIC{NL},$STATIC{SKIP});
if ($STATIC{CHAR_NL} eq "NL"){
$NL = "\n";
}elsif($STATIC{CHAR_NL} eq "CR"){
$NL = "\r";
}elsif($STATIC{CHAR_NL} eq "CRLF"){
$NL = "\r\l";
}elsif($STATIC{CHAR_NL} eq "HEXNL"){ ##still needs testing
$NL = "";
}
if ($STATIC{CMD_SKIP} eq "SPACE"){
$SKIP = "\032";
}elsif($STATIC{CMD_SKIP} eq "NL"){
$SKIP = $NL;
}else{
$SKIP = "$STATIC{CMD_SKIP}$NL";
}
$exp->expect(10,
[ qr/\? /, sub {
$exp->send("yes$NL");
sleep 1;
$SHELL = 1;
$exp->send("$CONFIG{PASS}$NL");}],
[ qr/assword:/, sub {
$SHELL = 1;
sleep 0.1;
$exp->send("$CONFIG{PASS}$NL");}],
);
$exp->expect(5,
[ qr/assword:/, sub {
unless($exp->soft_close()){$exp->hard_close();}
$SHELL = '2';}],
[ qr/$STATIC{CHAR_DIS}/, sub {
$exp->send("$NL");
$SHELL = '0';}],
[ qr/$STATIC{CHAR_EN}/, sub {
$exp->send("$NL");
$SHELL = '0';}],
);
unless ( $SHELL == '2' || $SHELL == '1' )
{
if ($STATIC{MODE_EN} eq "yes"){
$exp->expect(5,
[ qr/$SBC/, sub {
$exp->send("$STATIC{CMD_EN}$NL");}],
[ qr/$STATIC{CHAR_EN}/, sub {
$exp->send("$NL");}],
);
$exp->expect(5,
[ qr/assword:/, sub {
$exp->send("$CONFIG{PASS}$NL");}],
[ qr/$STATIC{CHAR_EN}/, sub {
$exp->send("$NL");}],
);
$SBC = $STATIC{CHAR_EN};
}
if ($STATIC{CMD_SET_NO_PAGE} ne '?'){ $exp->send("$STATIC{CMD_SET_NO_PAGE}$NL");}
if ($STATIC{MODE_CFG} eq "yes"){
$exp->expect(5,
[ qr/$SBC/, sub {
$exp->send("$STATIC{CMD_CFG}$NL");}],
[ qr/$STATIC{CHAR_CFG}/, sub {
$exp->send("$NL");}],
);
$exp->expect(5,
[ qr/assword:/, sub {
$exp->send("$CONFIG{PASS}$NL");}],
[ qr/$STATIC{CHAR_CFG}/, sub {
$exp->send("$NL");}],
);
$SBC = $STATIC{CHAR_CFG};
}
$exp->clear_accum();
$exp->send("$NL");
$exp->expect(5,
[ qr/$SBC/, sub { $ROUTER = $exp->before();}]
);
$ROUTER =~ s/\s//g;
for (my $C=0; $C < @{ $DATA{$ID}{COMMANDS} }; $C++){
my $COMMAND = $DATA{$ID}{COMMANDS}[$C];
my @MATCHES = $DATA{$ID}{MATCHES}[$C];
#print Dumper \@MATCHES;
$exp->clear_accum();
$exp->send("$COMMAND$NL");
sleep 2; ##wait for router to execute command
my $LB = 0;
$FB = '';
LOOPCTRL: while ($LB < 10){
$exp->expect(5,
[ qr/\Q$STATIC{CHAR_PAGE}/, sub { ##CASE 1: PAGED OUTPUT, send SKIP and next with COUNTER+1
$FB .= $exp->before();
$exp->send("$SKIP"); $LB++;
next LOOPCTRL; }],
[ qr/\Q$STATIC{CHAR_CONFIRM}/, sub { ##CASE 2: CONFIRM, send COMMAND and exit loop
$FB .= $exp->before();
$exp->clear_accum();
$exp->send("$STATIC{CMD_CONFIRM}$NL");
$LB = 9;
next LOOPCTRL }],
);
$exp->expect(1, ##this needs to be here because it matches before above conditons do
[ qr/$SBC/, sub { ##PAGED + CONFIRM done if they occured, now one forced SBC match
$FB .= $exp->before();
$FB .= $exp->after(); #################some devices echo commands, this needs to be fixed by dynamic pty settings
last LOOPCTRL; }], ##none of the aboce occured, exit loop
);
}
my ($PATTERNS,$NEGPATTERNS);
$FB =~ s/$ROUTER/ /;
my ($METHOD,$ADDPOS,$ADDNEG,$CASE,$RES) = (0,"inclusive patterns: * ","exclusive patterns: * ",1, "OK ");
foreach (@{ $MATCHES[0] }){
next unless defined($_);
$MATCH = "$_";
if ( $MATCH =~ m/^\!/ ) {
$MATCH =~ s/\!//;
$METHOD = "1";
$NEGPATTERNS .= "$MATCH*";
}else{
$PATTERNS .= "$MATCH*";
$METHOD = "0";
}
if ( $FB ne '' ){
if ($MATCH eq 'no-match-hook' && ! ( $FB =~ /$STATIC{CHAR_INVALID}/i ) ){
$CASE = 2;
last;
}elsif ($MATCH eq 'no-match-hook' && $FB =~ /$STATIC{CHAR_INVALID}/i){
$RES = "ERROR";
$FB = "INVALID COMMAND";
$CASE = 0;
last;
}
if ( $METHOD == 0){ ##method pos. pattern
if ( $FB =~ /$MATCH/i ){
$ADDPOS .= "$MATCH*";
}else{
$RES = "NOK ";
}
}elsif ($METHOD == 1 && $CASE == 1){ ##method exclusive pattern match
if ( ! ($FB =~ /$MATCH/i) ){
$ADDNEG .= "$MATCH*";
}else{
$RES = "NOK ";
}
}
}else{
$RES = 'ERROR ';
$FB = "Device didn't react to command";
$CASE = 0;
last;
}
}
my $RESREF;
if ($CASE == 0){
$RESREF = "$RES||$FB";
}elsif ($CASE == 2){
$RESREF = "$RES||No patterns were provided";
}else{
$RES .= "matched ";
my $TMP = "specified patterns were =>";
if(defined($PATTERNS)){$RES .=$ADDPOS; $TMP .= "inclusive: $PATTERNS"; }
if(defined($NEGPATTERNS)){$RES .=$ADDNEG; $TMP .= "exclusive: $NEGPATTERNS";}
$RESREF = "$RES||$TMP";
}
if ($CONFIG{VERBOSE} == 1){
$FB =~ s/\n/ /g;
push(@RESULTS,"$RESREF||$FB");
}else{
push(@RESULTS,"$RESREF");
}
sleep 0.1;
}
sleep 0.1;
unless($STATIC{MODE_CFG} eq "no"){ $exp->send("$STATIC{CMD_ROOTDIR}$NL");}
unless($STATIC{MODE_WR} eq "no" ){ $exp->send("$STATIC{CMD_WR}$NL");}
$exp->send("$STATIC{CMD_QUIT}$NL");
sleep 0.1;
$TEMP{$ID}->{RESULTS} = \@RESULTS;
}else{
$EXIT = $SHELL;
}
unless($exp->soft_close()){$exp->hard_close();}
#print Dumper \%TEMP;
#####%TEMP is written to tempdb#####
my ($I,$CHK,$FH)=(0);
while ( $I<=50 ){
$I++;
sleep 0.1;
open($CHK, ">$CONFIG{LOCKFILE}") || next;
open($FH, ">>$CONFIG{TEMPDB}") || die("$0::CONNECT2CPE_SSH*child*($ID):ERROR Unable to write to file $CONFIG{TEMPDB}\n");
flock($FH, LOCK_EX) ||die("$0::CONNECT2CPE_SSH*child*($ID):ERROR LOCK_EX FAILED on $FH\n");
my $STRING;
if ( $EXIT == 0 )
{
while (my ($KEY,$VALUE) = (each %TEMP) ){
$STRING .= "$KEY";
for (my $C=0; $C < @{ $TEMP{$ID}{RESULTS} }; $C++){
my $COMMAND = $DATA{$ID}{COMMANDS}[$C];
my $RESULT = $TEMP{$ID}{RESULTS}[$C];
$RESULT =~ s/\r//g;
$RESULT =~ s/\n/ /g;
$STRING .= "||$COMMAND||$RESULT";
}
$T = gettime();
print $FH "$T||$STRING|||\n";
}
}elsif ($EXIT == 1){
$T = gettime();
print $FH "$T;$ID;h00kme\n";
}elsif ($EXIT == 2){
$T =gettime();
print $FH "$T;$ID;noble\n";
}
close($FH);
sleep 0.1;
close($CHK);
last;
}
sleep 0.1;
$pm->finish($EXIT); ### EXIT with 0 as OK and 1 as IP unreachable
}
$pm->wait_all_children;
return %DATA;
}
######################################################################################################################
## / SUB CONNECT2CPE_SSH ###########################################################################################
######################################################################################################################
######################################################################################################################
## SUB CONNECT2CPE_TELNET ##########################################################################################
######################################################################################################################
sub connect2cpe_TELNET {
my ($DATA,$CONFIG,$STATIC) = @_;
my ($SHELL,$FB,$T,$STRING,$ROUTER,$COMMAND,$MATCH,%TEMP,$RESULT,@RESULTS) = (1);
my $pm = new Parallel::ForkManager($CONFIG{BATCHSIZE});
$pm->run_on_finish(
sub { my ($PID, $EXITCODE) = @_;
if ($CONFIG{QUIET} == 0){
if ($EXITCODE == 1){
print $OUT "child(PID = $PID) terminated with exit state: NOK\n";
}else{
print $OUT "child(PID = $PID) terminated with exit state: OK\n";
}
}
}
);
$pm->run_on_start(
sub {
if ($CONFIG{QUIET} == 0){
print $OUT "Forking child...\n";
}
}
);
$pm->run_on_wait(
sub {
#print "control\n";
},
0.1
);
for my $ID (keys %DATA){
next unless $ID ne 'CFG';
#####following code is run in child-processes#####
my $PID = $pm->start($ID) and next;
my $EXIT = 0;
my $exp = new Expect;
$exp->log_stdout($CONFIG{DEBUG}); ##For debugging only
$exp->exp_internal($CONFIG{DEBUG}); ##For debugging only
$exp->log_user($CONFIG{DEBUG}); ##For Debugging only
$exp->raw_pty(0);
$exp->match_max(1000000);
$exp->spawn("telnet", "$ID") || die("$0::CONNECT2CPE_TELNET:ERROR Unable to spawn telnet session to $ID. ERR/SYS: $! / $?");
sleep 0.1;
my $SBC = $STATIC{CHAR_DIS};
my ($NL,$SKIP) = ($STATIC{NL},$STATIC{SKIP});
if ($STATIC{CHAR_NL} eq "NL"){
$NL = "\n";
}elsif($STATIC{CHAR_NL} eq "CR"){
$NL = "\r";
}elsif($STATIC{CHAR_NL} eq "CRLF"){
$NL = "\r\l";
}elsif($STATIC{CHAR_NL} eq "HEXNL"){ ##still needs testing
$NL = "\0xa";
}
if ($STATIC{CMD_SKIP} eq "SPACE"){
$SKIP = "\032";
}elsif($STATIC{CMD_SKIP} eq "NL"){
$SKIP = $NL;
}else{
$SKIP = "$STATIC{CMD_SKIP}$NL";
}
$exp->expect(10,
[ qr/ogin:/, sub {
$exp->send("$CONFIG{USER}$NL");
$SHELL = '2';}],
[ qr/ser:/, sub {
$exp->send("$CONFIG{USER}$NL");
$SHELL = '2';}],
[ qr/name:/, sub {
$exp->send("$CONFIG{USER}$NL");
$SHELL = '2';}],
);
unless ( $SHELL == '1' ){
$exp->expect(10,
[ qr/assword:/, sub {
$exp->send("$CONFIG{PASS}$NL");
$SHELL = '0';}],
[ qr/ogin: /, sub {
$exp->send("$CONFIG{PASS}$NL");
$SHELL = '0';}],
);
}
unless ( $SHELL == '2' || $SHELL == '1' )
{
if ($STATIC{MODE_EN} eq "yes"){
$exp->expect(10,
[ qr/$SBC/, sub {
$exp->send("$STATIC{CMD_EN}$NL");}],
[ qr/$STATIC{CHAR_EN}/, sub {
$exp->send("$NL");}],
);
$SBC = $STATIC{CHAR_EN};
}
if ($STATIC{MODE_CFG} eq "yes"){
$exp->expect(10,
[ qr/$SBC/, sub {
$exp->send("$STATIC{CMD_CFG}$NL");}],
[ qr/$STATIC{CHAR_CFG}/, sub {
$exp->send("$NL");}],
);
$SBC = $STATIC{CHAR_CFG};
}
$exp->clear_accum();
$exp->send("$NL");
$exp->expect(10,
[ qr/$SBC/, sub { $ROUTER = $exp->before();}]
);
$ROUTER =~ s/\s//g;
if ( $STATIC{CMD_SET_NO_PAGE} ne '?' ){ print '$exp->send("$STATIC{CMD_SET_NO_PAGE}$NL")';} ##still needs to be verified
for (my $C=0; $C < @{ $DATA{$ID}{COMMANDS} }; $C++){
my $COMMAND = $DATA{$ID}{COMMANDS}[$C];
my @MATCHES = $DATA{$ID}{MATCHES}[$C];
#print Dumper \@MATCHES;
my $CNT = 0;
while ($CNT < 4){
$exp->clear_accum();
$exp->send("$COMMAND$NL");
sleep 2; ##wait for router to execute command
my $LB = 0;
$FB = '';
while($LB < 10){
$exp->expect(5,
[ qr/$SBC/, sub {
$FB .= $exp->before();
$LB = 10; }],
[ qr/$STATIC{CHAR_PAGE}/, sub {
$FB .= $exp->before();
$exp->send("$SKIP"); $LB++;}],
);
}
if ( $FB ne ''){ last; }
$CNT++;
}
my ($PATTERNS,$NEGPATTERNS);
$FB =~ s/$ROUTER/ /;
my ($METHOD,$ADDPOS,$ADDNEG,$CASE,$RES) = (0,"inclusive patterns: * ","exclusive patterns: * ",1, "OK ");
foreach (@{ $MATCHES[0] }){
next unless defined($_);
$MATCH = "$_";
if ( $MATCH =~ m/^\!/ ) {
$MATCH =~ s/\!//;
$METHOD = "1";
$NEGPATTERNS .= "$MATCH*";
}else{
$PATTERNS .= "$MATCH*";
$METHOD = "0";
}
if ( $FB ne '' ){
if ($MATCH eq 'no-match-hook' && ! ( $FB =~ /$STATIC{CHAR_INVALID}/i ) ){
$CASE = 2;
last;
}elsif ($MATCH eq 'no-match-hook' && $FB =~ /$STATIC{CHAR_INVALID}/i){
$RES = "ERROR";
$FB = "INVALID COMMAND";
$CASE = 0;
last;
}
if ( $METHOD == 0){ ##method pos. pattern
if ( $FB =~ /$MATCH/i ){
$ADDPOS .= "$MATCH*";
}else{
$RES = "NOK ";
}
}else{ ##method exclusive pattern match
if ( ! ($FB =~ /$MATCH/i) ){
$ADDNEG .= "$MATCH*";
}else{
$RES = "NOK ";
}
}
}else{
$RES = 'ERROR ';
$FB = "Device didn't react to command";
$CASE = 0;
last;
}
}
my $RESREF;
if ($CASE == 0){
$RESREF = "$RES||$FB";
}elsif ($CASE == 2){
$RESREF = "$RES||No patterns were provided";
}else{
$RES .= "matched ";
my $TMP = "specified patterns were =>";
if(defined($PATTERNS)){$RES .=$ADDPOS; $TMP .= "inclusive: $PATTERNS"; }
if(defined($NEGPATTERNS)){$RES .=$ADDNEG; $TMP .= "exclusive: $NEGPATTERNS";}
$RESREF = "$RES||$TMP";
}
if ($CONFIG{VERBOSE} == 1){
$FB =~ s/\n/ /g;
push(@RESULTS,"$RESREF||$FB");
}else{
push(@RESULTS,"$RESREF");
}
sleep 0.1;
}
sleep 0.1;
unless($STATIC{MODE_CFG} eq "no"){ $exp->send("$STATIC{CMD_ROOTDIR}$NL");}
unless($STATIC{MODE_WR} eq "no" ){ $exp->send("$STATIC{CMD_WR}$NL");}
$exp->send("$STATIC{CMD_QUIT}$NL");
sleep 0.1;
$TEMP{$ID}->{RESULTS} = \@RESULTS;
}else{
$EXIT = $SHELL;
}
unless($exp->soft_close()){$exp->hard_close();}
#print Dumper \%TEMP;
#####%TEMP is written to tempdb#####
my ($I,$CHK,$FH)=(0);
while ( $I<=50 ){
$I++;
sleep 0.1;
$T = gettime();
open($CHK, ">$CONFIG{LOCKFILE}") || next;
open($FH, ">>$CONFIG{TEMPDB}") || die("$0::CONNECT2CPE_TELNET*child*($ID):ERROR Unable to write to file $CONFIG{TEMPDB}\n");
flock($FH, LOCK_EX) ||die("$0::CONNECT2CPE_TELNET*child**($ID):ERROR LOCK_EX FAILED $!/$?\n");
my $STRING;
if ( $EXIT == 0 )
{
while (my ($KEY,$VALUE) = (each %TEMP) ){
$STRING .= "$KEY";
for (my $C=0; $C < @{ $TEMP{$ID}{RESULTS} }; $C++){
my $COMMAND = $DATA{$ID}{COMMANDS}[$C];
my $RESULT = $TEMP{$ID}{RESULTS}[$C];
$RESULT =~ s/\r//g;
$RESULT =~ s/\n/ /g;
$STRING .= "||$COMMAND||$RESULT";
}
$T = gettime();
print $FH "$T||$STRING|||\n";
}
}elsif ($EXIT == 1){
$T = gettime();
print $FH "$T;$ID;h00kme\n";
}elsif ($EXIT == 2){
$T =gettime();
print $FH "$T;$ID;noble\n";
}
close($FH);
sleep 0.1;
close($CHK);
last;
}
sleep 0.1;
$pm->finish($EXIT); ### EXIT with 0 as OK and 1 as IP unreachable
}
$pm->wait_all_children;
return %DATA;
}
######################################################################################################################
## / SUB CONNECT2CPE_TELNET ########################################################################################
######################################################################################################################
######################################################################################################################
## SUB SENDMAIL ####################################################################################################
######################################################################################################################
sub sendmail {
my ($REPORT,$CONFIG,$RESULT) = @_;
my $T = gettime();
my $MAIL_TO = $CONFIG{MAIL_TO}[0];
delete $CONFIG{MAIL_TO}[0];
my @MAIL_CC = $CONFIG{MAIL_TO};
if ($RESULT == 1){
$CONFIG{MAIL_DATA} .= "
Overview - completed $T:
Pattern matches successful: $REPORT{OK}
Pattern matches unsuccessful: $REPORT{NOK}
DEVICE UNREACHABLE: $REPORT{UNREACHABLE}
ERRORS: $REPORT{ERROR}
command success, but no output: $REPORT{NOFEEDBACK}
";
$CONFIG{MAIL_SUB} .= "processed $CONFIG{DEVICES_COUNT} devices. $REPORT{UNREACHABLE} unreachable. ";
my $mh = MIME::Lite->new(
From => $CONFIG{MAIL_FROM},
To => $MAIL_TO,
Cc => @MAIL_CC,
Subject => $CONFIG{MAIL_SUB},
Type => 'multipart/mixed'
) or die("$0::SENDMAIL:ERROR creating new mail object: $!/$?\n");
$mh->attach (
Type => 'TEXT',
Data => $CONFIG{MAIL_DATA},
) or die("$0::SENDMAIL:ERROR adding the text message part: $!/$?\n");
$mh->attach (
Type => "text/csv",
Path => $CONFIG{OUTCSV},
Filename => $CONFIG{OUTCSV},
Disposition => 'attachment'
) or die("$0::SENDMAIL:ERROR adding $CONFIG{OUTCSV} to mail object: $!/$?\n");
$mh->send;
}else{
$CONFIG{MAIL_DATA} .= "ERROR: TACACS SERVICE VERIFICATION FAILED!\nReason:";
if ($RESULT == 1){ $CONFIG{MAIL_DATA} .= "HUBSITE: $CONFIG{HUBSITE} is unreachable\n";}
if ($RESULT == 2){ $CONFIG{MAIL_DATA} .= "Provided Username/password combination failed Authentication\n";}
if ($RESULT == 2){ $CONFIG{MAIL_DATA} .= "Provided Username/password combination passed Authentication, but tacacs profile doesn't allow privileged mode\n";}
$CONFIG{MAIL_SUB} .= "$CONFIG{NAME}: tacacs service verification failed. script execution aborted";
my $mh = MIME::Lite->new(
From => $CONFIG{MAIL_FROM},
To => $MAIL_TO,
Cc => @MAIL_CC,
Subject => $CONFIG{MAIL_SUB},
Type => 'multipart/mixed'
) or die("$0::SENDMAIL:ERROR creating new mail object: $!/$?\n");
$mh->attach (
Type => 'TEXT',
Data => $CONFIG{MAIL_DATA},
) or die("$0::SENDMAIL:ERROR adding the text message part: $!/$?\n");
$mh->send;
}
return $RESULT;
}
######################################################################################################################
## / SUB SENDMAIL ##################################################################################################
######################################################################################################################