http://qs1969.pair.com?node_id=62186
Category: Networking Code
Author/Contact Info ybiC
Description: ## deprecated by (code) Poor Man's TACACS - automate CatOS and IOS password resets with Net::Telnet::Cisco and Net::SNMP ##

(code)) Cisco Pass Mass - CatOS (deprecated by node 123464) automates password resets on multiple Cisco CatOS LAN switches.   It's a complete re-write of (code)) Cisco Pass Mass - IOS (deprecated by node 123464), which does the same for Cisco IOS routers and LAN switches.

CatOS switches are a little bit tougher to script, since their password resets are interactive.   Fortunately, I learned of Net::Telnet's waitfor() and getlines() syntax in Network Programming with Perl.   They're pretty straightforward, and Net::Telnet's input_log and dump_log help *a*lot* with debugging.   If you use dump_log, be sure to unlink it when done to avoid leaving your passwords laying around.   Anyway, I suspect this approach is quicker+simpler to code than Expect.pm.  

Target switches are either given as command-line arguments, or in a text file - one device name or IP address per line.   Rudimentary sanity checks are done on the input file, but it won't pass -T yet.   It takes a few seconds for each device.   Progress is displayed on-screen and recorded to a logfile.

Comments and critique are both welcome and invited.

Thanks to:
Petruchio for mondo suggestions and help
chromatic for $command =~ $commands[0] suggestion on a different post
ar0n for timeout example at Re: timeout for ?
tilly for tips on functions
strredwolf, jcwren, boo_radley, danger, crazyinsomniac, OeufMayo, azatoth and deprecated for suggestions in CB
Oh yeah, and some guy named vroom.   {grin}
    cheers,
    ybiC

Most recent update: 2001-04-30
hashamafied passel o' scalar vars.
un-subified non-redundant code to reduce number of global vars.
mixed-case subroutine names, w/o ampersan's.
formatted for 75 chars/line (well, mostly)

 

#!/usr/bin/perl -w

# setpassCat.pl
# pod at tail

use strict;
use Net::Telnet::Cisco;      # simplify telnet to Cisco devices
use Term::ReadKey;           # disable screen echo during password ent
+ry
use Tie::IxHash;             # insertion-order hash of passwords+promp
+ts
use Time::localtime;         # timestamp of each target device
use vars qw(
    $target
    $input
    $newpass
    $newenablepass
    $ntc
    @cmd_output
    );

my %file = (
     in             => 'spC.in',
     sessionlog     => 'spC.log',
     sessionsummary => 'spC.summ',
     NTClog         => 'spC.input_log',
    );
my %parm = (
    pwtimeout   => 120,      # seconds to wait for password from keybo
+ard
    NTCtimeout  => 5,        # seconds to wait for response from devic
+e
    NTCerrmode  => 'return', # Net::Telnet::Cisco not die on problem d
+evice
    targetcount => 0,        # start with device count at zero
    );

######################################################################
+#####
umask oct 177;               # $input_log file accessible to only this
+ user
PrintBar();
print "  Change passwords for multiple Cisco CatOS LAN switches\n";
PrintBar();


######################################################################
+#####
if (-e $file{sessionlog} and -f _) {
    unlink ($file{sessionlog})
        or die "  Error unlinking $file{sessionlog}: $!"
    }
open (LOG, "> $file{sessionlog}")
    or die "  Error opening $file{sessionlog}: $!";


######################################################################
+#####
unless (@ARGV) {
    unless (-r $file{in} && -T_) {
        Usage();
        }
    @ARGV = "$file{in}"
        or die "  Error opening $file{in}: $!";
    chomp (@ARGV = <>);
    }


######################################################################
+#####
print "  Validating target list.\n";
foreach $target (@ARGV) {
    chomp $target;
    print ("    $target\n");
    unless ($target =~ (/^(\w|-|\.)+$/)) {
        print(
            "\n  Error reading $file{in}:\n",
            "  \"$target\" is an improperly formatted device name.\n",
            "  Only alphanumeric, underscore, dash and dot allowed.\n\
+n",
            "  Edit $file{in} to remove puctuation,",
            " blank lines and/or blank spaces.\n",
            );
        WrapItUp();
        exit;
        }
    }
print ("\n");


######################################################################
+#####
print "  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";
foreach my $prompt (keys %prompts) {
    print "  $prompt ";
    ReadMode('noecho');                     # don't echo password to s
+creen
    &WaitForPassword();
    $passwds{$prompts{$prompt}} = $input;
    ReadMode('restore');                          # re-activate screen
+ echo
    print "\n";
    }
print "\n";
my $oldpass       = ($passwds{"oldpass"});
my $oldenablepass = ($passwds{"olden"});


######################################################################
+#####
print "  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";
foreach my $newprompt(keys %newprompts) {
    print "  $newprompt ";
    ReadMode('noecho');                  # don't echo password to scre
+en
    &WaitForPassword();
    $newpasswds{$newprompts{$newprompt}} = $input;
    ReadMode(0);                               # re-activate screen ec
+ho
    print "\n";
    }
    print "\n";
    $newpass              = ($newpasswds{"newpass"});
    my $confirmpass       = ($newpasswds{"confpass"});
    $newenablepass        = ($newpasswds{"newen"});
    my $confirmenablepass = ($newpasswds{"confen"});
    unless (
      ("$newpass" eq "$confirmpass")
      and
      ("$newenablepass" eq "$confirmenablepass")) {
        PasswordsMismatch();
        }


######################################################################
+#####
PrintLogConsole ("  Passwords will be updated on these devices:\n");
for(@ARGV) {
    PrintLogConsole ("    $_\n");
    }
PrintLogConsole ("\n");
Pause ();
PrintBar();


######################################################################
+#####
foreach $target (@ARGV) {
    ShowPcTime();
    ++$parm{targetcount};
    if ($ntc = Net::Telnet::Cisco->new (
        host      => $target,
        errmode   => $parm{NTCerrmode},
        timeout   => $parm{NTCtimeout},
        input_log => $file{NTClog},
        )) {
            $ntc -> login('',$oldpass);
            PrintLastPrompt(4);
            if ($ntc -> enable($oldenablepass)) {
            PrintLastPrompt(2);
            ChangePass();
            } else {
            PrintLogConsole ("Error accessing $target\n");
            PrintLastPrompt(2);
            }
        $ntc -> cmd('disa');
        print (@cmd_output);
        PrintLastPrompt(2);
        $ntc -> disable;
        PrintLastPrompt(4);
        $ntc -> close;
        } else {
        PrintLogConsole ("Error connecting to $target\n");
        }
    }
ShowPcTime();
WrapItUp();
ParseLog2Summary();

######################################################################
+#####
# print most recent telnet session prompt to log and console
# param is number of spaces to precede device prompt
sub PrintLastPrompt {
    PrintLogConsole (' 'x $_[0], $ntc -> last_prompt, "\n");
    }
######################################################################
+#####
# Summarize results
sub ParseLog2Summary {
    my $loglines       = 0;
    my $pwschanged     = 0;
    my $errors         = 0;
    open (LOG,     "< $file{sessionlog}")
        or die "  Error opening $file{sessionlog}: $!";
    open (SUMMARY, "> $file{sessionsummary}")
        or die "  Error opening $file{sessionsummary}: $!";
    for (<LOG>) {
        ++$loglines;
        ++$errors     if /^Error /;
        ++$pwschanged if /^\s+Password changed/;
        }
    printf "  $^O  %d:%d:%d  %d-%d-%d\n",
        localtime -> hour(),
        localtime -> min(),
        localtime -> sec(),
        localtime -> mon()+1,
        localtime -> mday(),
        localtime -> year()+1900,
        ;
    printf SUMMARY "  $^O  %d:%d:%d  %d-%d-%d\n",
        localtime -> hour(),
        localtime -> min(),
        localtime -> sec(),
        localtime -> mon()+1,
        localtime -> mday(),
        localtime -> year()+1900,
        ;
    PrintSummConsole ("  Parsed $loglines line logfile\n\n");
    PrintSummConsole ("  $parm{targetcount} device(s)\n");
    PrintSummConsole ("  $pwschanged passwords changed\n");
    PrintSummConsole ("  $errors error(s)\n\n");
    PrintSummConsole ("  Review $file{sessionlog} for details.\n");
    PrintSummConsole ("  Search for \"Error\" if any errors indicated.
+\n");
    PrintBar();

    close LOG     or die "  Error closing $file{sessionlog}: $!";
    close SUMMARY or die "  Error closing $file{sessionsummary}: $!";
    }
######################################################################
+#####
# print() instead of cmd() because 'set password' does not return std 
+prompt
sub ChangePass {
    my $old; my $new;
    my @commands = ( 'set password', 'set enablepass' );
    foreach my $command (@commands) {
        if ($ntc -> print("$command")) {
            PrintLogConsole ("  $command\n");
            }
        else { return ("Error sending '$command' command to $target.\n
+"); }
        # "$ntc -> getline" must be here to populate @getlines properl
+y
        # I don't understand it, but is what made it work |^(
        my $getline  = $ntc -> getline;     # print "\$getline = $getl
+ine\n";
        my @getlines = $ntc -> getlines;    # print ("\$getlines = $ge
+tlines\n");
        foreach my $getlines (@getlines) {
            if ($getlines =~ /% Invalid input/) {
                PrintLogConsole ("Error running '$command' command at 
+$target.\n");
                PrintLastPrompt(2);
                return();                   # skip to next target if c
+ommand 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:/')) {
            PrintLastPrompt(2);
            $ntc -> print($old);
            print (@cmd_output);
            }
        else {
        return PrintLogConsole ("Error getting 'Enter old password' pr
+ompt.\n");
            }
        if ($ntc ->waitfor('/Enter new password:/')) {
            PrintLastPrompt(2);
            $ntc -> print($new);
            print (@cmd_output);
            }
        else {
            return PrintLogConsole ("Error getting 'Enter new password
+' prompt.\n");
            }
        if ($ntc ->waitfor('/Retype new password:/')) {
            PrintLastPrompt(2);
            $ntc -> print($new);
            print (@cmd_output);
            }
        else {
            return PrintLogConsole ("Error getting 'Retype new passwor
+d' prompt.\n");
            }
        if ($ntc ->waitfor('/Password changed./')) {
            PrintLastPrompt(2);
            }
        else {
            return PrintLogConsole ("Error getting 'Password changed' 
+prompt.\n");
            }
        }
    }


######################################################################
+#####
# Subroutines start here
######################################################################
+#####
sub WaitForPassword {
    eval {
        local $SIG{ALRM} = sub { die "ALARUM" };
        alarm("$parm{pwtimeout}");
        chomp($input = <STDIN>);
        alarm(0);
        };
    if ($@ =~ /ALARUM/) {
        print "\n\n";
        print "  Sorry - You waited too long before entering a passwor
+d.\n";
        print "  Try again, if you want.\n\n";
        ReadMode(0);                           # re-activate screen ec
+ho
        exit;           # "exit" instead of "die" so no error to conso
+le
        }
    }
######################################################################
+#####
sub PasswordsMismatch {
    print(
        "  D'oh!  Password confirmation(s) didn't match!\n",
        "  Run this program again if you want, ",
        "and try not to fatfinger it next time!\n\n"
        );
    exit;                # "exit" instead of "die" so no error to cons
+ole
    }
######################################################################
+#####
sub WrapItUp {
    close LOG
        or die "  Error closing $file{sessionlog}: $!";
    PrintBar();
    }
######################################################################
+#####
sub Pause {
    print "  Ctrl+c to abort  or  <enter> to continue.";
    (<STDIN>);
    }
######################################################################
+#####
sub ShowPcTime {
    printf "      $^O  %d:%d:%d  %d-%d-%d\n",
        localtime -> hour(),
        localtime -> min(),
        localtime -> sec(),
        localtime -> mon()+1,
        localtime -> mday(),
        localtime -> year()+1900,
        ;
    printf LOG "      $^O  %d:%d:%d  %d-%d-%d\n",
        localtime -> hour(),
        localtime -> min(),
        localtime -> sec(),
        localtime -> mon()+1,
        localtime -> mday(),
        localtime -> year()+1900,
        ;
    }
######################################################################
+#####
# print to console *and* logfile
# param is text string to be printed
sub PrintLogConsole { 
    print @_;
    print(LOG @_)
        or die "  Error printing to $file{sessionlog}: $!";
    }
######################################################################
+#####
# print to console *and* logfile
# param is text string to be printed
sub PrintSummConsole {
    print @_;
    print(SUMMARY @_)
        or die "  Error printing to $file{sessionsummary}: $!";
    }
######################################################################
+#####
sub PrintBar {                                 # Visual divider for ou
+tput
    print "\n  ", '=' x 54, "\n\n";
    }
######################################################################
+#####
sub Usage {
    print <<EOF
    
  Oops - you forgot to specify *what* CatOS switches to reset password
+s on!

  Usage : setpassCat.pl device1 device2...<enter>
     or : setpassCat.pl<enter>
          where $file{in} = textfile list of devices address/name,
                          one per line.

  "perldoc setpassCat.pl" for a bit more info.

EOF
  ;
#my @modules = (
#  "Term::ReadKey",
#  "Tie::IxHash",
#  "Time::localtime",
#  "Net::Telnet",
#  "Net::Telnet::Cisco",
#  );
#foreach my $module (@modules) {
#  print ("$module $module::VERSION\n");
#  }
print( 
    "    Net::Telnet::Cisco $Net::Telnet::Cisco::VERSION\n",
    "    Net::Telnet        $Net::Telnet::VERSION\n",
    "    Term::ReadKey      $Term::ReadKey::VERSION\n",
    "    Tie::IxHash        $Tie::IxHash::VERSION\n",
    "    Time::localtime    $Time::localtime::VERSION\n",
    "    Perl               $]\n",
    "    OS                 $^O\n",
    "    Program            $0\n\n",
    );
  exit;
  }
######################################################################
+#####

=head1 Title

 setpassCat.pl

=head1 Description

 Automate password resets for multiple Cisco CatOS LAN switches

 Runtime is a couple seconds for each CatOS device

 CatOS = 2948g plus 4000, 5000 and 6000 family chassis
 IOS   = 2916, 2924, 3548, and routers
 ???   = 1900

=head1 Usage

  Usage : setpassCat.pl device1 device2...<enter>
     or : setpassCat.pl<enter>
          where $file{in} = textfile list of devices address/name,
                          one per line.

=head1 Tested

 with:
   pass - Debian             2.2r2 "Espy"
          Perl               5.00503
          Term::ReadKey      2.14
          Tie::IxHash        1.21
          Time::localtime    1.01
          Net::Telnet        3.02
          Net::Telnet::Cisco 1.03
          
   fail - Windows            2000pro
          SiePerl            5.00503
            install fails for
              Term::ReadKey 
              Net::Telnet::Cisco

 against: CatOS 4.5(4) on Cisco Catalyst 2948g
                5.5(1)                   4006
                4.5(2)                   5500
                5.3(5a)CSX               6009
                5.5(1)                   6509

=head1 Updated

 2001-04-30  16:15
   Hashamafy file and ntc param scalar variables.
   Un-subify to eliminate unecessary global variables.
   Mixed-case subroutine names, without ampersan.
   Format for 75 chars/line (well, mostly).
 2001-04-10  15:00
   Insignificant tweaks
 2001-03-29
   Changed doublequote to singlequote around "=" in PrintBar()
 2001-03-28
   Removed unecessary quotes around "$_ [0]" in PrintLastPrompt()
   Commented function parameters
 2001-03-27
   Commented reason for extra "$ntc -> getline" in ChangePass()
   Removed surrounding quotes from variable definitions
   Eliminated unecessary "my $continue" from Pause()
   Reduced copy+paste redundancy by adding PrintLastPrompt()
   Removed surrounding single quotes from numeric variables
   Fixed inconsistant indentation
   Tweaked console messages
 2001-03-12
   Added ParseLog2Summary to count (Password changed|Error)s
     and PrintSummConsole for output of above
   Simplified output syntax with 'print "=" x 54'
 2001-03-09
   Simplified ChangePass() with $command =~ $commands[0]
   Closed $file{sessionlog} in WrapItUp()
 2001-03-08
   Added check for 'Invalid input detected'
   Moved password timeout to function
   Improved error handling if device unreachable
   Tweaked console messages
   Replaced die"\n" with exit
 2001-03-05
   Added working timeout for passwords entry
 2001-03-04
   Initial post to Perl Monks
 2001-02-26
   Start rewrite of CiscoPassMass using waitfor()

=head1 Comments

 The regex to sanity-check infile is very limited.
 But it should suffice for most $file{in} problems.

=head1 Todos

 Figure out array for installed modules::VERSIONS.
 Improve (DNS name|IP address) check for infile sanity-checking.
   Net::IPv4Addr - ipv4_checkip
 Untaint infile instead of just sanity check.
 User.pm to save (log|summary) files to home dir.

=head1 Author

 ybiC

=head1 Credits

 Thanks to:
 Petruchio for mondo suggestions and help
 chromatic for $command =~ $commands[0] suggestion on a different post
 ar0n for timeout example at [id://30092]
 tilly for tips on functions
 strredwolf, jcwren, boo_radley, danger, crazyinsomniac, OeufMayo,
   azatoth and deprecated for suggestions in CB
 Oh yeah, and some guy named vroom {grin}

=cut