Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

(code)) Cisco Pass Mass - CatOS (deprecated by node 123464)

by ybiC (Prior)
on Mar 05, 2001 at 09:37 UTC ( #62186=sourcecode: print w/ replies, xml ) Need Help??

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

Comment on (code)) Cisco Pass Mass - CatOS (deprecated by node 123464)
Download Code

Back to Code Catacombs

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: sourcecode [id://62186]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (5)
As of 2015-07-05 16:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (67 votes), past polls