#!/usr/bin/perl -w # cpass # pod at tail $|++; # STDOUT hot require 5; # run only on Perl v5 or newer use strict; # avoid d'oh! bugs use Net::SNMP; # query target(s) for code type/ver use Net::Telnet; # required by Net::Telnet::Cisco use Net::Telnet::Cisco; # simplify telnet to Cisco devices use Getopt::Long; # support commandline switches use Term::ReadKey; # disable screen echo during password entry use Tie::IxHash; # insertion-order hash of passwords+prompts ########################################################################### # Season to taste: # date-stamped filenames for uniquity. my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time); my $ymd = sprintf("%04d%02d%02d",$year+1900,$mon+1,$mday); my %file = ( errorlog => "cpass$ymd.err", sessionLog => "cpass$ymd.log", NtcLog => "cpass$ymd.ntc", # NtcLog is Net::Telnet::Cisco log current session # !! contains cleartext IOS passwords !! # !! don't leave it laying around !! ); my %parm = ( NtcErrmode => 'return', # Net::Telnet::Cisco not die on problem device userTimeout => 120, # secs to wait for keyboard input from user NtcTimeout => 30, # secs to wait for response from device # shorter for quicker prog run, # longer for small IOS routers slooow 'wr mem' ); my %snmp = ( defROcomm => 'public', # default Read-Only community string verOID => '.1.3.6.1.2.1.1.1.0', # sysDescr for Cisco code version ); my %id = ( Ios => 'Cisco Internetwork Operating System', CatOs => 'Cisco Catalyst Operating System', nineteen => 'Cisco Systems Catalyst 1900', ); my %counter = ( targTotal => 0, targIos => 0, targCatOs => 0, targUnknown => 0, telnetGood => 0, telnetFail => 0, loginGood => 0, loginFail => 0, enableGood => 0, enableFail => 0, IosGood => 0, IosFail => 0, CatOsGood => 0, CatOsFail => 0, ); my ( @IosGood, @CatOsGood, @IosFail, @CatOsFail, @telnetFail, @loginFail, @enableFail, @unknownFail, ); my ( $opt_help, $opt_nochange, $opt_ROcomm, $opt_target, $opt_infile, ); GetOptions( 'help!' => \$opt_help, 'nochange!' => \$opt_nochange, 'ROcomm!' => \$opt_ROcomm, 'target=s' => \$opt_target, 'infile=s' => \$opt_infile, ); ########################################################################### # Get stuf ready: # Net::Telnet::Cisco input_log file accessible to only this user: umask oct 177; # Unlink prior logs since session log write is append: for(keys %file) { if(-e $file{$_} and -f_){ unlink($file{$_}) or die "Error unlinking $file{$_}: $!"; } } open (LOG,"> $file{sessionLog}") or die "Error opening $file{sessionLog}: $!"; PrintLogConsole( "\n", " ** Started $0 **\n", " ",DateTime(),"\n\n", ); if(defined $opt_help){ Usage('You rang, sir?'); CloseLog(); exit; } ########################################################################### # get list of target device(s) my @targets; if(defined $opt_target) { @targets = $opt_target; PrintLogConsole(" Target given at command-line switch.\n"); } elsif (defined $opt_infile) { $file{in} = $opt_infile; unless (-r $file{in} and -T _) { Usage("Error reading input file \"$file{in}\": \"$!\""); CloseLog(); exit; } PrintLogConsole(" Target list at $file{in}\n"); open (INFILE, "< $file{in}") or die "Error opening $file{in}: $!"; @targets = ; close INFILE; } else { Usage('Error - target device or input file not specified!'); CloseLog(); exit; } PrintLogConsole(" Validating target list... "); for(@targets) { chomp; unless(/^(\w|-|\.)+$/) { Usage("Error - improperly formatted target name: $_"); CloseLog(); exit; } ++$counter{targTotal}; } PrintLogConsole("done!\n\n"); ########################################################################### # Set SNMP RO community string: either from user or default of "public". my( $ROcomm, $input ); if(defined $opt_ROcomm) { PrintLogConsole(" Enter RO community string: "); WaitForUserInput(); if($input =~ /^(\w|-|\.)+$/) { $ROcomm = $input; } else { PrintLogConsole( "Sorry, you have to enter *something* for SNMP RO community.\n", "Try again if you want.\n\n", ); exit; } PrintLogConsloe("\n"); } else { $ROcomm = $snmp{defROcomm}; } ########################################################################### # Get existing pw's from user: PrintLogConsole( " Prompting for existing passwords:\n", " (keystrokes *not* echoed to screen or written to disk)\n" ); tie my %prompts, "Tie::IxHash"; %prompts = ( 'Enter existing password:' => 'oldpass', 'Enter existing enable password:' => 'olden', ); tie my %passwds, "Tie::IxHash"; for(keys %prompts) { PrintLogConsole(" $_ "); ReadMode('noecho'); # don't echo password to screen WaitForUserInput(); $passwds{$prompts{$_}} = $input; ReadMode('restore'); # re-activate screen echo PrintLogConsole("\n"); } PrintLogConsole("\n"); my $oldpass = ($passwds{"oldpass"}); my $oldenablepass = ($passwds{"olden"}); ########################################################################### # Get replacement pw's from user: my( $newpass, $newenablepass, $confirmpass, $confirmenablepass, ); if(defined $opt_nochange) { PrintLogConsole( " ###########################################\n", " ### --nochange specified at commandline ###\n", " ### Passwords will *not* be changed ###\n", " ###########################################\n\n", ); $newpass = $oldpass; $newenablepass = $oldenablepass; } else { PrintLogConsole( " Prompting for new passwords\n", " (keystrokes *not* echoed to screen or written to disk)\n", ); tie my %newprompts, "Tie::IxHash"; %newprompts = ( 'Enter new password:' => 'newpass', ' Retype to confirm:' => 'confpass', 'Enter new enable password:' => 'newen', ' Retype to confirm: ' => 'confen', ); tie my %newpasswds, "Tie::IxHash"; for(keys %newprompts) { PrintLogConsole(" $_ "); ReadMode('noecho'); # don't echo password to screen WaitForUserInput(); $newpasswds{$newprompts{$_}} = $input; ReadMode('restore'); # re-activate screen echo PrintLogConsole("\n"); } # Confirm that pw's entered same both time: PrintLogConsole("\n"); $newpass = ($newpasswds{"newpass"}); $confirmpass = ($newpasswds{"confpass"}); $newenablepass = ($newpasswds{"newen"}); $confirmenablepass = ($newpasswds{"confen"}); unless ( ($newpass eq $confirmpass) and ($newenablepass eq $confirmenablepass) ) { Usage('Error - password confirmation(s) did not match!'); CloseLog(); exit; } } ########################################################################### # User review of target devices before proceeding: PrintLogConsole (" target devices:\n"); for(@targets) { PrintLogConsole (" $_\n"); } PrintLogConsole( "\n", " Ctrl+c to abort or to continue.", ); WaitForUserInput(); ########################################################################### # Get down to business: my( $devtype, $ntc, @cmd_output, ); for my $target(@targets) { ## Net::SNMP detection of device code type/ver my $ssess = Net::SNMP->session( -hostname => $target, -community => $ROcomm, ); my $devver = $ssess->get_request ($snmp{verOID}); my $error = $ssess->error; if($error) { ++$counter{targUnknown}; push(@unknownFail, $target); PrintLogConsole( "\nError detecting $target device type: ${error}\nSkip to next.\n\n", ); next; } if(($devver->{$snmp{verOID}}) =~ /$id{CatOs}/i){ ++$counter{targCatOs}; $devtype = 'CatOS'; } elsif (($devver->{$snmp{verOID}}) =~ /$id{Ios}/i) { $devtype = 'IOS'; ++$counter{targIos}; } elsif (($devver->{$snmp{verOID}}) =~ /$id{nineteen}/i) { ++$counter{targUnknown}; push(@unknownFail, $target); PrintLogConsole( "\nError: $target appears to be a Catalyst 1900.\nSkip to next. \n\n" ); next; } else { ++$counter{targUnknown}; push(@unknownFail, $target); PrintLogConsole( "\nError: $target is of unknown device type.\nSkip to next. \n\n" ); next; } $ssess->close; ## Telnet me baby: PrintLogConsole(" ",DateTime(),"\n"); if($ntc = Net::Telnet::Cisco->new ( host => $target, errmode => $parm{NtcErrmode}, timeout => $parm{NtcTimeout}, input_log => $file{NtcLog}, )) { ++$counter{telnetGood}; PrintLogConsole (" Connected to $target\n"); if($ntc -> login('', $oldpass)){ ++$counter{loginGood}; PrintLogConsole (' 'x 4, $ntc -> last_prompt, "\n"); if($ntc -> enable($oldenablepass)) { ++$counter{enableGood}; PrintLogConsole (' 'x 4, $ntc -> last_prompt, "\n"); SetPassIos($target) if $devtype eq 'IOS'; SetPassCat($target) if $devtype eq 'CatOS'; } else { ++$counter{enableFail}; push(@enableFail, $target); PrintLogConsole ( "\nError getting enable mode for $target.\nSkip to next.\n", ' 'x 4, $ntc -> last_prompt, "\n\n", ); } # dunno why disable twice, but it works *shrug* $ntc -> cmd('disa'); print(@cmd_output); $ntc -> disable; $ntc -> close; } else { ++$counter{loginFail}; push(@loginFail, $target); PrintLogConsole("\nError logging in to $target\n\n"); } } else { ++$counter{telnetFail}; push(@telnetFail, $target); PrintLogConsole ("\nError connecting to $target\n\n"); } } ########################################################################### # Is that your final answer? PrintLogConsole( " ",DateTime(), "\n\n", " Targets:\n", " total: $counter{targTotal}\n", " IOS: $counter{targIos}\n", " CatOS: $counter{targCatOs}\n", " unknown: $counter{targUnknown} (skipped)\n", "\n", " Telnet connections:\n", " success $counter{telnetGood}\n", " error $counter{telnetFail}\n", "\n", " Logins:\n", " success $counter{loginGood}\n", " error $counter{loginFail}\n", "\n", " Enable logins:\n", " success $counter{enableGood}\n", " error $counter{enableFail}\n", "\n", " IOS devices reset:\n", " success $counter{IosGood}\n", " error $counter{IosFail}\n", "\n", " CatOS passwords reset:\n", " success $counter{CatOsGood}\n", " error $counter{CatOsFail}\n", "\n", ); # List, not count, of devices sucessfully reset, one per line: my %successList = ( 'IOS devices sucessfuly reset' => \@IosGood, 'CatOS devices sucessfully reset' => \@CatOsGood, 'Unknown devices (no connection attempt)' => \@unknownFail, 'Devices err on telnet connection' => \@telnetFail, 'Devices err on login' => \@loginFail, 'Devices err on enable' => \@enableFail, 'IOS devices err during reset' => \@IosFail, 'CatOS devices err during reset' => \@CatOsFail, ); for my $head ( 'IOS devices sucessfuly reset', 'CatOS devices sucessfully reset', 'Unknown devices (no connection attempt)', 'Devices err on telnet connection', 'Devices err on login', 'Devices err on enable', 'IOS devices err during reset', 'CatOS devices err during reset', ) { PrintLogConsole("\n $head:\n"); PrintLogConsole(" $_\n") for Uniques( @{$successList{$head}} ); } PrintLogConsole("\n\n"); CloseLog(); # NtcLog contains cleartext IOS passwords. # !! Don't leave it laying around !! if(-e $file{NtcLog} and -f_){ unlink($file{NtcLog}) or die "Error unlinking $file{NtcLog}: $!"; } # Parse log for errors: print(" Parse $file{sessionLog} for errors\n"); open(GREPIN, "< $file{sessionLog}") or die "Error opening $file{sessionLog}: $!"; open(GREPOUT, "> $file{errorlog}") or die "Error opening $file{errorlog}: $!"; my @grepIn; while(){ push @grepIn, $_; } for(my @grepOut = grep( /(^Error|^Note)/, @grepIn)){ print GREPOUT "$_"; } close GREPIN or die "Error closing $file{sessionLog}: $!"; close GREPOUT or die "Error closing $file{errorlog}: $!"; # Wrapitup: print( "\n", " ** Completed $0 **\n", " ",DateTime(),"\n\n", " Session log: $file{sessionLog}\n", " Error log: $file{errorlog}\n", "\n", ); ########################################################################### # Subroutines start here ########################################################################### sub SetPassIos { my $target = $_[0]; # first Cisco config commands to be run: my @commands = ( 'term leng 0', 'conf t', "enable pass $newenablepass", ); # only reset pw's on VTYs already active/configured: my %sholine = ( ' 0 CTY' => 'line con 0', ' 2 VTY' => 'line vty 0 4', ' 7 VTY' => 'line vty 5 9', '11 VTY' => 'line vty 10 15', ); my @sholines = $ntc -> cmd('sho line'); for(@sholines) { if(/1 AUX/) { PrintLogConsole( "Note: line '0 AUX' detected - pw not (re)set in case of DDR\n" ); } for my $line(keys %sholine) { if(/$line/){ print(" detected $line - set pw+login for $sholine{$line}\n"); push(@commands, $sholine{$line}, "pass $newpass", 'login'); } } } # last Cisco commands to be run: push(@commands, 'end','write mem'); # run the Cisco commands: for my $command(@commands) { unless(my @output = $ntc -> cmd($command)){ ++$counter{IosFail}; push(@IosFail, $target, $command); return; } } ++$counter{IosGood}; push(@IosGood, $target); } ########################################################################### sub SetPassCat { my $target = $_[0]; my( $old, $new); my @commands = ( 'set password', 'set enablepass' ); for my $command(@commands) { # print() instead of cmd() as 'set password' not return std prompt if($ntc -> print("$command")) { PrintLogConsole (" $command\n"); } else { return ("Error sending '$command' command to $target.\n"); } # "$ntc -> getline" must be here to populate @getlines properly # I don't understand it, but is what made it work |^( my $getline = $ntc -> getline; # print "\$getline = $getline\n"; my @getlines = $ntc -> getlines; # print ("\$getlines = $getlines\n"); for(@getlines) { if(/% Invalid input/) { #for my $getlines(@getlines) { # if($getlines =~ /% Invalid input/) { push(@CatOsFail, $target); PrintLogConsole ("Error running '$command' command at $target.\n"); PrintLogConsole (' 'x 4, $ntc -> last_prompt, "\n"); return(); # skip to next target if command fails } } if($command =~ $commands[0]) { $old = "$oldpass"; $new = "$newpass"; } if($command =~ $commands[1]) { $old = "$oldenablepass"; $new = "$newenablepass"; } # Additional error checking still needed here. if($ntc -> waitfor('/Enter old password:/')) { $ntc -> print($old); print(@cmd_output); } else { ++$counter{CatOsFail}; push(@CatOsFail, $target); return PrintLogConsole ( "Error getting 'Enter old password' prompt.\n" ); } if($ntc ->waitfor('/Enter new password:/')) { $ntc -> print($new); print(@cmd_output); } else { ++$counter{CatOsFail}; push(@CatOsFail, $target); return PrintLogConsole ( "Error getting 'Enter new password' prompt.\n" ); } if($ntc ->waitfor('/Retype new password:/')) { $ntc -> print($new); print(@cmd_output); } else { ++$counter{CatOsFail}; push(@CatOsFail, $target); return PrintLogConsole ( "Error getting 'Retype new password' prompt.\n" ); } # Woohoo - it worked! if($ntc ->waitfor('/Password changed./')) { ++$counter{CatOsGood}; push(@CatOsGood, $target); } else { ++$counter{CatOsFail}; push(@CatOsFail, $target); return PrintLogConsole ( "Error getting 'Password changed' prompt.\n" ); } } } ########################################################################### # extract unique elements of array sub Uniques { my %saw = map { $_ => 1 } @_; sort keys %saw; } ########################################################################### sub DateTime { my($sec,$min,$hour,$mday,$mon,$year) = localtime(time); my $datetime = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec); } ########################################################################### sub WaitForUserInput { eval { local $SIG{ALRM} = sub { die "ALARUM" }; alarm("$parm{userTimeout}"); chomp($input = ); alarm(0); }; if($@ =~ /ALARUM/) { print "\n\n"; print "Sorry - You waited too long before entering something.\n"; print "Try again if you want.\n\n"; ReadMode('restore'); # re-activate screen echo exit; # "exit" instead of "die" so no error to console } } ########################################################################### # Print to console and logfile # param is text string to be printed sub PrintLogConsole { print @_; print(LOG @_) or die "Error printing to $file{sessionLog}: $!"; } ########################################################################### sub CloseLog { close LOG or die "Error closing $file{sessionLog}: $!"; } ########################################################################### # Really don't have to esplain this'un, eh? sub Usage { my $specific_err = $_[0]; print <