The script is running as intended (despite being a work in progress) but I would like to escape the general "self-taught-nastyness" it has and consequently improve my understanding and style. The purpose of the script is to connect to any amount of devices of any vendor with any OS by telnet or SSH and execute any amount of commands. Optionally pattern matching may be performed. It was tested and doesn't put much of a strain on the server its running on.
I would really aprecciate any and all critical comments and advise. With the exception of "use xyz.pm" unless its part of the core perl package. Additonal modules may not be installed (which is why it features some questionable stuff)
If the tabs make it too hard to read (written in vim) I can provide the correctly formatted files if anyone is interested.
vv contains config and templates vv
vv functions I have taken out of the actual script to make it structured vv
#!/usr/bin/perl -w
use strict;
use File::Find;
use Cwd 'abs_path';
BEGIN { ##Discover and use required modules automatically at compile t
+ime
my $ABS_PATH = abs_path($0);
find(\&wanted, $ABS_PATH);
sub wanted {
if ( $_ eq "eod_templates.pm" or $_ eq "eod_functions.pm"){uns
+hift(@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, ("ig
+norecase_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,$PRO
+TO,$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 proto
+col 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 N
+OT 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 fr
+om $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 = <STDIN>;
chomp $TACUSER;
print "please enter device or tacacs password\n";
$TACPASS = <STDIN>;
chomp $TACPASS;
}else{
while (<$FH>) {
if ( $_ =~ /your-username/i ){
my @TMP = split(/=/,$_);
if ( $TMP[1] =~ /username/i || @TMP < 2 ){ die("$0::MAIN:E
+RROR: file-based-authentication was selected but no username was prov
+ided 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 pro
+vided 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 fail
+ed, please try editing it again or switch to prompt based authenticat
+ion\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 al
+low 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 ap
+pend 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 out
+put 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 ve
+rify general tacacs functionality
}
if($RESULT != 0 && $TACACS == 1){
my $RESPONSE = &sendmail(\%REPORT,\%CONFIG,$RESULT); ## tacacs fai
+led, send mail + exit with ERROR
die("$0::MAIN:ERROR tacacs service functionality verification fail
+ed. Aborting script execution!\n");
}
##tacacs functionality verified process all tasks on all devices (in c
+hilds) 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 rea
+dable format and generate final output
unlink $CONFIG{LOCKFILE} || warn("$0::MAIN:WARNING Unable to delete $C
+ONFIG{LOCKFILE}\n");
unlink $CONFIG{TEMPDB} || warn("$0::MAIN:WARNING Unable to delete $CON
+FIG{TEMPDB}\n");
if ( @MAILDST > 0 ){
$RESULT = &sendmail(\%REPORT,\%CONFIG,"1"); ##generate report, att
+ach 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 un
+able 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 stati
+c 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,\@TRAN
+SLATIONS);
$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,\@TRAN
+SLATIONS);
$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{CF
+G} 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:E
+RROR: $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}") || di
+e("$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 stay
+s 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 => S
+hell = 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,@R
+ESULTS) = (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::CONNE
+CT2CPE_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("$STA
+TIC{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->b
+efore();}]
);
$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 comma
+nd
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 { ##CAS
+E 2: CONFIRM, send COMMAND and exit loop
$FB .= $exp->before();
$exp->clear_accum();
$exp->send("$STATIC{CMD_CONFIRM}$N
+L");
$LB = 9;
next LOOPCTRL }],
);
$exp->expect(1, ##this needs to be here bec
+ause it matches before above conditons do
[ qr/$SBC/, sub { ##PAGED + CONFIRM done i
+f 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 se
+ttings
last LOOPCTRL; }], ##none of the a
+boce occured, exit loop
);
}
my ($PATTERNS,$NEGPATTERNS);
$FB =~ s/$ROUTER/ /;
my ($METHOD,$ADDPOS,$ADDNEG,$CASE,$RES) = (0,"incl
+usive 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){ ##met
+hod exclusive pattern match
if ( ! ($FB =~ /$MATCH/i) ){
$ADDNEG .= "$MATCH*";
}else{
$RES = "NOK ";
}
}
}else{
$RES = 'ERROR ';
$FB = "Device didn't react to comma
+nd";
$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::CONNECT2
+CPE_SSH*child*($ID):ERROR Unable to write to file $CONFIG{TEMPDB}\n")
+;
flock($FH, LOCK_EX) ||die("$0::CONNECT2CPE_SSH*chil
+d*($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 u
+nreachable
}
$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,@R
+ESULTS) = (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:ER
+ROR 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->b
+efore();}]
);
$ROUTER =~ s/\s//g;
if ( $STATIC{CMD_SET_NO_PAGE} ne '?' ){ print '$exp->s
+end("$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 c
+ommand
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,"incl
+usive 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 comma
+nd";
$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::CONNECT2
+CPE_TELNET*child*($ID):ERROR Unable to write to file $CONFIG{TEMPDB}\
+n");
flock($FH, LOCK_EX) ||die("$0::CONNECT2CPE_TELNET*c
+hild**($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 u
+nreachable
}
$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 mai
+l object: $!/$?\n");
$mh->send;
}else{
$CONFIG{MAIL_DATA} .= "ERROR: TACACS SERVICE VERIFICATION FAIL
+ED!\nReason:";
if ($RESULT == 1){ $CONFIG{MAIL_DATA} .= "HUBSITE: $CONFIG{HUB
+SITE} is unreachable\n";}
if ($RESULT == 2){ $CONFIG{MAIL_DATA} .= "Provided Username/pa
+ssword combination failed Authentication\n";}
if ($RESULT == 2){ $CONFIG{MAIL_DATA} .= "Provided Username/pa
+ssword combination passed Authentication, but tacacs profile doesn't
+allow privileged mode\n";}
$CONFIG{MAIL_SUB} .= "$CONFIG{NAME}: tacacs service verificati
+on 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 ##################################################
+################################################
######################################################################
+################################################