#!/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 ################################################################################################## ######################################################################################################################