#!/usr/bin/perl -w # switchver.pl # pod at tail use strict; use Net::Telnet::Cisco; # simplify telnet to Cisco devices use Term::ReadKey; # disable screen echo during password entry use Getopt::Long; # support command-line switches use vars qw( $opt_target $opt_infile $opt_outfile @targets ); my %ntcparm = ( errmode => 'return', # bad command won't kill script ntctimeout => 20, # seconds to wait for device response pwtimeout => 120, # seconds to wait for password entry from user ); my %file = ( def_out => 'switchver.csv', tmp0 => 'switchver.tmp0', tmp1 => 'switchver.tmp1', tmp2 => 'switchver.tmp2', ); my @tmpfiles = ( $file{tmp0}, $file{tmp1}, $file{tmp2} ); my @commands = ( # run these from Net::Telnet::Cisco session 'term leng 0', # IOS 'set leng 0', # CatOS 'sho ver', # What This Program Is All About ); my @keep = ( # whole lines of text to be *kept* '>sho ver', # IOS device name # 'Version', # IOS # 'System serial number', # IOS # 'uptime', # IOS '> sho ver', # CatOS device name 'WS-C', # CatOS ); my @strip = ( # specific text to be *stripped* from what's left '\r', # CatOS bogus end-of-line (should be first regexp) '>sho ver', # IOS device name '> sho ver', # 2948g device name 'System Bootstrap.*$', # 2948g 'Hardware Version.*$', # 2948g 'System serial number: ', # IOS 'Model number.*$', # 3548 'cisco ', # IOS to leave model as leading string in line ' \(PowerPC.*$', # IOS processor, memory, etc following model number 'Software.*$', # CatOS sw info following model number '^\d.*', # CatOS chassis blade hw info '^\s+', # leading blanks ',', # first step to csv-ifying output ); ################################################################################## # preliminaries and opening message umask oct 177; # temp files viewable only by user running this program print "\nStarting $0\n"; GetOptions( 'target=s' => \$opt_target, 'infile=s' => \$opt_infile, 'outfile=s' => \$opt_outfile, ); ################################################################################## # get list of target device(s) if (defined $opt_target) { @targets = $opt_target; print " Target given at command-line switch.\n"; } elsif (defined $opt_infile) { $file{in} = $opt_infile; unless (-r $file{in} && -T _) { &USAGE("Error reading input file \"$file{in}\": \"$!\""); } print " 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!'); } ################################################################################## # get name of output file if (defined $opt_outfile) { $file{out} = $opt_outfile; print " Output file given at command-line switch.\n"; } else { $file{out} = $file{def_out}; print " Using default output file.\n"; } ############################################################################## # sanity-check target(s) list print " Validating target list...\n"; foreach my $nonsane(@targets) { chomp $nonsane; print (" $nonsane\n"); unless ($nonsane =~ (/^(\w|-|\.)+$/)) { print "\n Ack - \"$nonsane\" is an improperly formatted device name.\n"; print " Only alphanumeric, underscore, dash and dot allowed.\n\n"; print " Edit $file{in} or re-enter targets to remove puctuation, blank lines and/or blank spaces.\n\n"; exit; # "exit" instead of "die" so no error to console } } print (" Specified target name(s) valid.\n"); ################################################################################## # prompt for access password (enable pass not needed for 'show ver' command print " Enter access password (*not* echoed to screen): "; ReadMode('noecho'); # don't echo password to screen eval { local $SIG{ALRM} = sub { die "ALARUM" }; alarm("$ntcparm{pwtimeout}"); chomp($ntcparm{pwinput} = ); alarm(0); }; if ($@ =~ /ALARUM/) { print "\n\n"; print "Sorry - You waited too long before entering a password.\n"; print "Try again, if you want.\n\n"; ReadMode(0); # re-activate screen echo exit; # "exit" instead of "die" so no error to console } $ntcparm{password} = $ntcparm{pwinput}; ReadMode(0); # re-activate screen echo print "\n"; ############################################################################## # delete existing out/tmp files print ' Unlinking prior out and temp files...'; &UNLINK($file{out}, 'quiet'); foreach my $tmpfile (@tmpfiles) { &UNLINK($tmpfile, 'quiet'); } print " *poof*\n"; ################################################################################## # connect to each target device, then run command to get device info print (" Target devices queried:\n"); foreach my $target(@targets) { my $ntc; chomp $target; if ($ntc=Net::Telnet::Cisco->new( host => $target, timeout => $ntcparm{ntctimeout}, errmode => $ntcparm{errmode}, input_log => $file{tmp0}, )) { $ntc->login('',$ntcparm{password}); print " ", $ntc->last_prompt, " "; foreach my $command (@commands) { my @output = $ntc->cmd($command); } print " ", $ntc->last_prompt, "\n"; $ntc->close; # if multiple Cisco sessions, append each to same tmpfile open (TMP0, "<$file{tmp0}") or die "Can't open $file{tmp0} RO: \"$!\""; open (TMP1, ">>$file{tmp1}") or die "Can't open $file{tmp1} for append: \"$!\""; while () { print TMP1 $_; } close (TMP0) or die "Can't close $file{tmp0}: \"$!\""; close (TMP1) or die "Can't close $file{tmp1}: \"$!\""; } else { warn "Can't connect to $target\n";} #: " . $ntc->errmsg; } # if telnet connection failed my @output = $ntc->cmd(); } &UNLINK ($file{tmp0}, 'quiet'); # don't leave pw's laying around ################################################################################## # munge temp file data # extract lines we *do* want to see open (TMP1, "<$file{tmp1}") or die "Can't open $file{tmp1} RO: \"$!\""; open (TMP2, ">>$file{tmp2}") or die "Can't open $file{tmp2} WO: \"$!\""; while () { foreach my $keep(@keep) { print (TMP2 $_) if /$keep/; } } close (TMP1) or die "Can't close $file{tmp1}: \"$!\""; close (TMP2) or die "Can't close $file{tmp2}: \"$!\""; #------------------------------------------------------------------------# # delete specific text don't care about open (TMP2, "<$file{tmp2}") or die "Can't open $file{tmp2} RO: \"$!\""; open (OUT, ">>$file{out}") or die "Can't open $file{out} WO: \"$!\""; while () { foreach my $strip(@strip) { s/$strip//g; } s/\n/,\n/g; # second step toward csvifying s/\s+,/,/g; # CatOS device-type chomp unless(/WS-/); # final step to csvify print (OUT $_); } close (TMP2) or die "Can't close $file{tmp2}: \"$!\""; close (OUT) or die "Can't close $file{out}: \"$!\""; ################################################################################## # turn out the lights, it's time to go print " Finished switch versions check.\n"; print ' Unlinking temp files...'; foreach my $tmpfile(@tmpfiles) { &UNLINK($tmpfile, 'quiet'); } print " *poof*\n"; print "Results at $file{out}\n\n"; ############################################################################## # Subroutines start here ############################################################################## # cleanup temp files when done with them sub UNLINK { my $file = $_[0]; my $echo = $_[1]; if (-e $file && -w _) { print " Unlinking $file..." if ($echo eq 'verbose'); unlink $file or die "Error unlinking $file: \"$!\""; print " *poof*\n" if ($echo eq 'verbose'); } } ######################################################################### sub PAUSE { print " Ctrl+c to abort or to continue."; (); } ################################################################################## # Really don't have to explain this'un, eh? sub USAGE { my $specific_err = $_[0]; print < <(-i|--infile) file> <(-o|--outfile) file> If target given, then infile switch is ignored. Target can be IP address or hostname or FQDN. Switches include: (note that "=" is optional) --target=sparky *or* -t 172.31.0.5 --outfile another_file *or* -o=another_file --infile=some_file *or* -i some_file You *must* specify either targets or infile. If no outfile given, then hardcoded $file{def_out} is used. EOF ; exit; } ################################################################################## =head1 Name switchver.pl =head1 Summary Automate collection of Cisco LAN switch and router information: device name, hardware type, IOS/CatOS ver, serial number Comments or suggestions are quite welcomed. =head1 Usage Usage: getopt.pl <(-t|--target) host> <(-i|--infile) file> <(-o|--outfile) file> If target given, then infile switch is ignored. Target can be IP address or hostname or FQDN. Switches include: (note that "=" is optional) --target=sparky *or* -t 172.31.0.5 --outfile another_file *or* -o=another_file --infile=some_file *or* -i some_file You *must* specify either targets or infile. If no outfile given, then hardcoded $file{def_out} is used. =head1 Tested with: Perl 5.00503 on Debian 2.2 "Espy" against: 2916, 2924, 3548, 2948g, 4000 5000, 6000 =head1 Updated 2001-04-13 17:30 Remove unecessary Getopt::Long switch abreviations Outfile in csv format Add '\r' to @strip regexp for CatOS data >8^( 2001-04-11 Add command-line switches for target, infile, outfile using Getopt::Long. Submit to www.perlmonks.org. 2001-04-10 Hashamafy file and ntc variables. Sanity-check target names. Prompt for access password instead of hard-coded. 2001-04-03 Eliminate unecessary global variables. Consistant indenting. Replace double quotes with single for strings. Eliminate quotes for numbers. Replace multiple "my $tmpfile" with @tmpfile. Unlink temp files instead of overwrite. Add &USAGE(). 2000-10-05 Initial working code. =head1 ToDos Use Net::Snmp sysDescr instead of Net::Telnet::Cisco. simplify data structure simplify parsing/munging simplify program improve security Add regex so hostname with IPaddr-format not stripped by '^\d' in @strip Fix ntc so not die on unreachable target Print console to logfile so record of failed attempts Hashamafy Getopt::Long scalar variables Add support for multiple targets and -t switch. Use File::Temp instead of $file{tmp(0..2)}. =head1 Author ybiC =head1 Credits thanks to Petruchio, jeroenes, geektron, damian1301, crazyinsomniac, ar0n and yakko for Getopt::Long suggestions. =cut