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

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 exe +cution. #Make a copy if you may need it again. #STATIC CLASS PRESETS SETTING DEFINITIONS ##select the actions the script needs to perform automatically (en +able 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, enab +led 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,SPA +CE,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 expect +s (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 custom +ized 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 THEI +R 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 in +struction. i.e. output does NOT contain pattern. # # <= escapes any of the above Metacharacters. This needs to be p +ut infront of Metacharacters that you do NOT want the script to inter +prete as such. (e.g. #= will escape the =) #IP:COMMAND=MATCH=!DONTMATCH ################################################################## +################################################# #---END CONFIG---------------------------------------------------- +------------------------------------------------# ################################################################## +################################################# "; return $TEMPLATE; } ###################################################################### +################################################ ## / TEMPLATES ##################################################### +################################################ ###################################################################### +################################################ 1;

vv functions I have taken out of the actual script to make it structured vv

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 handlin +g is unix standard.\n mandatory arguments: --readfile|-r read cli commands from file and perform t +hem on all CPEs (default = $NAME.cfg. see section config file for hel +p) --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 devi +ce access when asked. (default = enabled) WARNING: Supercedes LOGIN details in config file y +ou 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 outpu +t to. (default = $NAME.csv) --quiet|-q log to logfile (default = enabled) --no-quiet|-n log to STDOUT instead of logfile (mandato +ry 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 outputfil +e. Depending on the executed commands this can be several hundred cha +racters!!! (default = disabled) --Version|-V display extenensive version information an +d exit\n"; print " advanced optional arguments (case sensitive): --sendmail|-s expects comma separated list of e-mail ad +dresses to send generated report to as argument (default = disabled) alternatively it can be invoked multiple times wit +h different addresses. (First occurence will always be TO, all others + CC) --max-connections|-m run script in forked mode (massively e +nhances performance) with the specified ammount of max child processe +s (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 i +t to the script with option -r \n"; print "Note: please report all unexpected behavior to e-mail t +y\n\n"; return 1; } ###################################################################### +################################################ ## / SUB HELP ###################################################### +################################################ ###################################################################### +################################################ ###################################################################### +################################################ ## SUB SANATIZE ### DOES: substitute ctrl-chars for escaped metachar +s in $LINE #################################### ###################################################################### +################################################ sub sanatize { my ($LINE,$METACHARS,$TRANSLATIONS) = @_; for (my $I=0; $I<@$METACHARS;$I++){ $LINE =~ s/\#\Q$$METACHARS[$I]/$$TRANSLATIONS[$I]/g; ## hardco +ded ESCSEQ => # } return $LINE; } ###################################################################### +################################################ ## / SUB SANATIZE ################################################## +################################################ ###################################################################### +################################################ ###################################################################### +################################################ ## SUB TAINT ### DOES: substitute ctrl-chars for unescaped metachar +s 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, $da +yOfWeek, $dayOfYear, $daylightSavings) = localtime(); my $year = 1900 + $yearOffset; my $T_GER = "$hour:$minute:$second, $weekDays[$dayOfWeek] $dayOfMo +nth/$months[$month]/$year"; my $T_UK = "$weekDays[$dayOfWeek] $months[$month]/$dayOfMonth/$yea +r $hour:$minute:$second"; return $T_UK; } ###################################################################### +################################################ ## / SUB GETTIME ################################################### +################################################ ###################################################################### +################################################ ###################################################################### +################################################ ## SUB PARSETEMPDB ### DOES: parses tempdb and generates humanly re +adible REPORT in %REPORT ###################### ###################################################################### +################################################ sub parsetempdb { my ($REPORT,$CONFIG) = @_; my $STRING; open(my $CSV, ">$$CONFIG{OUTCSV}") || die("$0::PARSETEMPDB:ERROR U +nable to open $$CONFIG{OUTCSV} for writing!\n"); my $TH = "DATE;IP"; open(my $DB, "<$$CONFIG{TEMPDB}") || die("$0::PARSETEMPDB:ERROR Un +able 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 recipie +nts 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} devic +es. $$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 ma +il object: $!/$?\n"); $mh->send; }else{ ##CASE: Script execution aborted because tacacs verificatio +n failed $$CONFIG{MAIL_DATA} .= "ERROR: TACACS SERVICE VERIFICATION FAI +LED!\nReason:"; if ($RESULT == 1){ $$CONFIG{MAIL_DATA} .= "HUBSITE: $$CONFIG{H +UBSITE} is unreachable\n";} if ($RESULT == 2){ $$CONFIG{MAIL_DATA} .= "Provided Username/p +assword combination failed Authentication\n";} if ($RESULT == 2){ $$CONFIG{MAIL_DATA} .= "Provided Username/p +assword combination passed Authentication, but tacacs profile doesn't + allow privileged mode\n";} $$CONFIG{MAIL_SUB} .= "$$CONFIG{NAME}: tacacs service verifica +tion 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;

vv main script 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 ################################################## +################################################ ###################################################################### +################################################


In reply to RFC: beginner level script improvement by georgecarlin

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.