monkerz57 has asked for the wisdom of the Perl Monks concerning the following question:
So I took on a new job as a network engineer, but had no base line as to what all was in production within this network. I was then tasked with "truing up" our SMARTnet contract. The task seemed tedious and after a few days sitting on the project I decided to write a perl script to inventory the network for me. Below is the product of that brainstorming session. The script crawls the network looking for devices with telnet and/or ssh ports open and compiles a list of those devices. The script then uses that list and attempts to connect via telnet if open otherwise uses ssh. Could you please review and let me know what I may need to change to improve the script or gotchas I may have overlooked? Any and all feedback is greatly appreciated as I am still very new to perl.
use strict; use warnings; use Net::SSH2; use Net::Telnet; my $directory = 'C:/myDir'; my $aaaUser = 'myAaaUser'; my $aaaPass = 'myAaaPass'; my $localUser = 'myLocalUser'; my $localPass1 = 'myLocalPass1'; my $localPass2 = 'myLocalPass2'; my $telnetLog = "$directory/telnet_log.txt"; my $inventory = "$directory/inventory_log.txt"; #-- Run nmap against supernets for open ports ssh/telnet and print to +file for archiving qx{nmap 10.146.0.0/15 10.120.0.0/14 10.95.0.0/16 -p 22-23 -n -oG inven +tory_script_port_scan.txt}; my $portScanFileIn = "$directory/inventory_script_port_scan.txt"; #-- Read nmap output file into array for formatting. my @rawPortScan = do { open( my $fi, "<", $portScanFileIn ) or die "Couldn't open $portScanFileIn: $!"; <$fi>; }; #-- Format array to <ipAddress> <sshStatus> <telnetStatus> for only li +nes containing 'open' my @filteredPortScan = grep { /open/ } @rawPortScan; chomp @filteredPortScan; s/Host: // for @filteredPortScan; s/\s.*: / / for @filteredPortScan; s/\/tcp\/\/ssh\/\/\/\,// for @filteredPortScan; s/\/tcp\/\/telnet\/\/\/// for @filteredPortScan; s/ /;/ for @filteredPortScan; s/ /;/ for @filteredPortScan; my $portScanFileOut = "$directory/inventory_script_port_scan_FILTERED. +txt"; #-- Print formatted array to file for archiving. open( my $fo, '>', "$portScanFileOut" ) or die "Cannot open $portScanFileOut: $!"; for my $line (@filteredPortScan) { print $fo "$line\n"; } close $fo; #-- Read formatted port scan into array for use. open my $fh, '<', "$portScanFileOut"; chomp( my @list = <$fh> ); close $fh; #-- Open log files to append open( my $log, ">>", $telnetLog ) or die "Couldn't open telnet log file: $!"; open( my $inv, ">>", $inventory ) or die "Couldn't open inventory log file: $!"; #-- Classify each ip to attempt telnet or ssh session #-- If telnet port open, will push to telnet sub; else will push to ss +h sub. for my $node (@list) { if ( $node =~ /^(\d+\.\d+\.\d+\.\d+);(22\/.+);(23\/.+)/ ) { my ( $ip, $sshStatus, $telnetStatus ) = ( $1, $2, $3 ); if ( $telnetStatus =~ /open/ ) { logging("Trying $ip:23"); audit_hardware_using_telnet($ip); } else { logging("Trying $ip:22"); audit_hardware_using_ssh($ip); } } } sub audit_hardware_using_ssh { my ($ip) = @_; my $ssh = Net::SSH2->new(); #-- Attempt ssh to ip; if cannot connect log and move on. if ( !$ssh->connect($ip) ) { logging(" --> ssh connection failed\n"); return; } else { logging(" --> ssh connected"); } #-- Attempt authentication; if cannot authenticate log and move on +. if ( !$ssh->auth_password( $aaaUser, $aaaPass ) ) { logging(" --> SSH Authentication Failed\n"); return; } else { logging(" --> ssh auth succeeded.\n"); } #-- Open channel and read all to array. my $channel = $ssh->channel(); $channel->blocking(0); $channel->shell(); sleep(2); print $channel "show inventory\n"; my @output = <$channel>; $channel->close; $ssh->disconnect; #-- Format output for use in inventory file. my @hostname = grep /[#>] $|[#>]$/, @output; s/[#>]// for @hostname; my @showInventory = grep /NAME:|PID:/i, @output; @showInventory = grep /\S/, @showInventory; #-- Move first two lines of array to scalars, then print hostname, + ip #-- and each hardware item to inventory file on single line delimi +ted. while (@showInventory) { my $invLine1 = shift @showInventory; my $invLine2 = shift @showInventory; $invLine1 =~ s/\r|\n//g; $invLine2 =~ s/\r|\n//g; print $inv "@hostname, $ip, $invLine1, $invLine2\n"; } } sub audit_hardware_using_telnet { my ($ip) = @_; my $t = new Net::Telnet( Host => $ip, Timeout => 15, Prompt => '/\S+[#] ?$/', Errmode => 'return' ); #-- Attempt telnet to ip; if cannot connect log and move on. if ( !defined $t ) { logging(" --> telnet connection failed.\n"); return; } else { logging(" --> telnet connected.\n"); } #-- Match initial login prompt and continue to respective login se +quence. my ( $prematch, $match ) = $t->waitfor( Match => '/Login as: ?|Username: ?|Login: ?/i', Match => '/Password: ?/' ); #-- If cannot match initial login prompt, log and move on. if ( !defined $match ) { logging("$ip: ERROR_1\n"); return; } #-- If initial prompt received was (Login as:, Username:, Login:) +push to the acs_local login sequence. if ( $match =~ /Login as: ?|Username: ?|Login: ?/i ) { acs_local_login( $ip, $t ); } #-- If initial prompt received was (Password:) push to the telnet +login sequence. elsif ( $match =~ /Password: ?/ ) { telnet_login( $ip, $t ); } } sub acs_local_login { my ( $ip, $t ) = (@_); #-- Print aaa username for initial prompt, then wait for #-- password prompt. If no prompt, log and move on. $t->print("$aaaUser"); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_2\n"); return; } #-- Print aaa password and wait for prompt. If "Username:" receive +d #-- move to second login attempt. If user-exec prompt achieved, p +roceed #-- to enable sequence. If priv-exec prompt received, continue. $t->print("$aaaPass"); my ( $prematch, $match ) = $t->waitfor( Match => '/Username: ?/', Match => '/\S+[>] ?$/', Match => $t->prompt ); #-- Attempt second login using local database username, then #-- wait for password prompt. If no prompt, log and move on. if ( $match =~ /Username: ?/ ) { $t->print("$localUser"); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_3\n"); return; } #-- Attempt second login using local database password #1. If +"Username:" #-- received move to third login attempt. If user-exec prompt + achieved, #-- proceed to enable sequence. If priv-exec prompt received, +continue. $t->print("$localPass1"); my ( $prematch, $match ) = $t->waitfor( Match => '/Username: ?/', Match => '/\S+[>] ?$/', Match => $t->prompt ); #-- Attempt third login using local database username, then #-- wait for password prompt. If no prompt, log and move on. if ( $match =~ /Username: ?/ ) { $t->print("$localUser"); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_4\n"); return; } #-- Attempt third login using local database password #2. +If user-exec prompt #-- achieved, proceed to enable sequence. If priv-exec pro +mpt received, continue. $t->print("$localPass2"); my ( $prematch, $match ) = $t->waitfor( Match => '/\S+[>] ?$/', Match => $t->prompt ); #-- Third login attempt returned user-exec prompt. Attempt + to gain priv-exec by #-- requesting enable. If password prompt is not received + log and move on. if ( $match =~ /\S+[>] ?$/ ) { $t->print('enable'); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_5\n"); return; } #-- Print local database password #2 to gain priv-exec +, then wait for prompt. $t->print("$localPass2"); my ( $prematch, $match ) = $t->waitfor( Match => $t->p +rompt ); } #-- If could not match a prompt after third login attempt, + log and move on. if ( !defined $match ) { logging("$ip: ERROR_6\n"); return; } } #-- Second login attempt returned user-exec prompt. Attempt to + gain priv-exec by #-- requesting enable. If password prompt is not received log + and move on. if ( $match =~ /\S+[>] ?$/ ) { $t->print('enable'); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_7\n"); return; } #-- Print local database password #1 to gain priv-exec, th +en wait for prompt. $t->print("$localPass1"); my ( $prematch, $match ) = $t->waitfor( Match => $t->promp +t ); } #-- If could not match a prompt after second login attempt, lo +g and move on. if ( !defined $match ) { logging("$ip: ERROR_8\n"); return; } } #-- Initial login attempt returned user-exec prompt. Attempt to ga +in priv-exec by #-- requesting enable. If password prompt is not received log and + move on. if ( $match =~ /\S+[>] ?$/ ) { $t->print('enable'); if ( !$t->waitfor('/Password: ?/i') ) { logging("$ip: ERROR_9\n"); return; } #-- Print aaa password to gain priv-exec, then wait for prompt +. $t->print("$aaaPass"); my ( $prematch, $match ) = $t->waitfor( Match => $t->prompt ); } #-- If could not match a prompt after initial login attempt, log a +nd move on. if ( !defined $match ) { logging("$ip: ERROR_10\n"); return; } #-- After acs_local login, proceed to information gathering sub. get_inventory( $ip, $t ); } sub telnet_login { my ( $ip, $t ) = (@_); #-- Print telnet line password #1 for initial password prompt. If +user-exec #-- prompt achieved, proceed to enable sequence. If "Password:" re +ceived move #-- to second telnet line login attempt. If priv-exec prompt recei +ved, continue. $t->print("$localPass1"); my ( $prematch, $match ) = $t->waitfor( Match => '/\S+[>] ?$/', Match => '/[Pp]assword: ?$/', Match => $t->prompt ); #-- Initial telnet line login attempt returned user-exec prompt. A +ttempt to gain priv-exec #-- by requesting enable. If password prompt is not received log +and move on. if ( $match =~ /\S+[>] ?$/ ) { $t->print('enable'); if ( !$t->waitfor('/[Pp]assword: ?/') ) { logging("$ip: ERROR_11\n"); return; } #-- Print telnet line password #1 to gain priv-exec, then wait + for prompt. $t->print("$localPass1"); my ( $prematch, $match ) = $t->waitfor( Match => $t->prompt ); } #-- Attempt second login using telnet line password #2. If user-ex +ec prompt #-- achieved, proceed to enable sequence. If priv-exec prompt rece +ived, continue. if ( $match =~ /[Pp]assword: ?$/ ) { $t->print("$localPass2"); my ( $prematch, $match ) = $t->waitfor( Match => '/\S+[>] ?$/', Match => $t->prompt ); #-- Second telnet line login attempt returned user-exec prompt +. Attempt to gain priv-exec #-- by requesting enable. If password prompt is not received +log and move on. if ( $match =~ /\S+[>] ?$/ ) { $t->print('enable'); if ( !$t->waitfor('/[Pp]assword: ?/') ) { logging("$ip: ERROR_12\n"); return; } #-- Print telnet line password #2 to gain priv-exec, then +wait for prompt. $t->print("$localPass2"); my ( $prematch, $match ) = $t->waitfor( Match => $t->promp +t ); } #-- If could not match a prompt after second telnet line login + attempt, log and move on. if ( !defined $match ) { logging("$ip: ERROR_13\n"); return; } } #-- If could not match a prompt after initial telnet line login at +tempt, log and move on. if ( !defined $match ) { logging("$ip: ERROR_14\n"); return; } #-- After telnet login, proceed to information gathering sub. get_inventory( $ip, $t ); } sub get_inventory { my ( $ip, $t ) = (@_); #-- Once logged in change terminal length, then store output of ho +stname and inventory. if ( !$t->cmd('terminal length 0') ) { logging("$ip: Error changing terminal length\n"); } #-- Format output for use in inventory file. my @showRun = $t->cmd('show run | inc hostname'); chomp( my @hostname = grep /hostname/, @showRun ); s/hostname // for @hostname; chomp( my @showInventory = $t->cmd('show inventory') ); @showInventory = grep /\S/, @showInventory; #-- Move first two lines of array to scalars, then print hostname, + ip #-- and each hardware item to inventory file on single line delimi +ted. while (@showInventory) { my $invLine1 = shift @showInventory; my $invLine2 = shift @showInventory; print $inv "$hostname[0], $ip, $invLine1, $invLine2\n"; } } sub logging { #-- Consolidates log printing into one liner. my @args = @_; print @args; print $log @args; }
|
|---|