Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
#!/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 entr +y use Tie::IxHash; # insertion-order hash of passwords+prompt +s ###################################################################### +##### # 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 dev +ice 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 str +ing 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 = <INFILE>; 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 scr +een 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 scree +n 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 <enter> 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(<GREPIN>){ 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 = <STDIN>); 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 <<EOF; $specific_err Usage: cpass.pl --help --nochange --ROcomm --target hostname --infile filesp +ec Switches include ("=" is optional): --help *or* -h --nochange *or* -n --ROcomm *or* -R --target=myputer *or* -t 172.31.0.5 --infile /path/infile *or* -i=/path/infile "help" displays this very message screen. "nochange" resets passwords to original values). "ROcomm" option causes program to prompt you for an SNMP Read-Only community string. Default is 'public'. You *must* specify either target or infile. If "target" given, then infile switch is ignored. Target can be IP address, hostname, or FQDN. Valid characters include alphanumeric, underscore, dash and dot. "infile" is a text file of target devices. One device per line. No blank lines. Same character limitations as with 'target'. Currently installed versions of necessary Perl modules: Net::Telnet::Cisco $Net::Telnet::Cisco::VERSION Net::Telnet $Net::Telnet::VERSION Net::SNMP $Net::SNMP::VERSION Term::ReadKey $Term::ReadKey::VERSION Tie::IxHash $Tie::IxHash::VERSION Perl $] OS $^O Program $0 EOF ; } ###################################################################### +##### =head1 TITLE cpass.pl =head1 DESCRIPTION Automate password resets for multiple Cisco switches and/or routers. Runtime is a couple seconds for each device. CatOS = 2948g, 4000, 5000 and 6000 IOS = 2900xl, 2950, 3500, and routers ??? = 1900 =head1 USAGE Use the source, Luke. Or run cpass.pl with no switches. =head1 TESTED with: Debian 2.2r(2|3) "Espy" Perl 5.00503 Term::ReadKey 2.14 Tie::IxHash 1.21 Net::SNMP 3.65 Net::Telnet 3.02 Net::Telnet::Cisco 1.03 against: 2514 12.0(8) 2900xl 11.2(8.5)SA5, SA6 3500 12.0(5.1)XP 2948g 4.5(3), (4) retest: 4006 5.5(1) 5505 4.5(1) 5500 4.5(2) 6009 5.3(5a)CSX 6509 5.5(1) =head1 UPDATED 2001-11-05 21:15 Post to PerlMonks for review. Move "CloseLog();exit;" lines out of &Usage. Unlink logs at start with for(keys %file). Unlink errorlog at start. Untie %file, as insertion-order retrieval not needed. Detect Catalyst 1900 and skip. Datestamp as part of log file names. grep main log for ^Error to populate errorlog. Remove explicit $_ where already already implied. Disply VTYs detected *and* resulting VTYs pw-reset. Untie %sholine as is sorted with keys(%sholine). Require Perl v5 or higher. Judicious add of clarifying comments. Tied-hashimafy @sholines to eliminate redundent if loops. Eliminate global var $target. Copy "my $target = $_[0];" from &SetPassIos to &SetPassCat. Remove unecessary quotes. Reduce indents from 2 spaces to 3. Simplify multiple PrintLogConsole(); to PrintLogConsole(,,,); 2001-11-03 21:25 PrintLogConsole() *all* screen messages. HOL for final log/viewing success/fail. Extract uniques from @*(Good|Fail) for log+console at end. Push login/enable error devices to @(Ios|CatOs)Fail. Replace Pause() with WaitForUserInput() for timeout. Error detection+handling for non-privilaged login. IOS reset counter - no err during 'conf t' session and 'wr m' not +err. CatOs reset counter - no err and *both* access+enable pw's reset. 20sec min timeout for IOS router 'write mem'. 'write mem' instead of 'copy run st' for IOS routers. Check for number of IOS VTYs, only reset pw's on that many. Check for presence of IOS 'line aux 0', only attempt reset pw if pre +sent. (present for routers, not for switches) Sanity-check user-provided SNMP ROcommunity string. Timeout waiting for user input if --ROcomm. Prompt instead of switch for SNMP community if --ROcomm. CatOS success/fail count. Handle IOS switches/routers as well as CatOS switches. Check for CatOS/IOS with SNMP 'sho ver'. Getopt param --help. Initial psuedo-code for (CatOS|IOS) detection with Net::SNMP. Unlink Net::Telnet::Cisco input_log when run complete. s/&&/and/g --nochange to leave passwords as is. use Getopt::Long for input params. Simplify timestamp chunk. Change output indenting so headers closest to left margin. Un-subamify functions only called once. Replace tabs with ' '. $_ with simple loops instead of 'for my $foo(@foos)'. 'my' instead of 'use vars'. s/foreach/for/g STDOUT hot. Indent to match PerlStyle recommendations. Remove '&' from subroutine names. Rename to cpass. 2001-10-28 Begin rewrite of SetPassCat. =head1 TODO Testing: Retest against more (IOS|CatOS) (switch|router) versions. Functionality: Properly handle wrong login pw for CatOS devices. Count+display CatOS devices (not passwords) reset. IOS Getline and Waitfor for better error detection+handling. Display and log elapsed time, start-to-finish. Support multiple targets with --target. Support 1900 Catalysts with their stupid menu and only one password. Ping targets for reachability before SNMP device type query. Net::Ping or Net::Ping::Improved Improve (DNS name|IP address) check for infile sanity-checking. Net::IPv4Addr - ipv4_checkip Check for (and skip if) TACACS-enabled device. prompt =~ /sername/ Code quality: Test::(Simple|More) test frame: cpass.t/foo.t, bar.t, bat.t... Real versioning, not just Update datestamp. Post to (FreshMeat|SourceForge). Untaint inputs instead of just sanity check. Pod::Usage to synchronize Usage() with pod. ?? Figure out array or hash for installed modules::VERSIONS. ?? use User.pm to save (log|summary) files to home dir. =head1 BUGS -n doesn't work, while --nochange works fine. =head1 AUTHOR ybiC =head1 CREDITS Thanks to: Petruchio for code reviews, muchly++ suggestions & corrections. c for inspiration+code from Pancho for Net::SNMP to detect device ty +pe. to several other monks who offered good counsel on prior efforts lea +ding up to this project. vroom for http://www.perlmonks.org =cut

In reply to (code) Poor Man's TACACS - automate CatOS and IOS password resets with Net::Telnet::Cisco and Net::SNMP by ybiC

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



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (3)
As of 2024-03-29 01:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found