sourcecode
ybiC
<code>#!/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 = <INFILE>;
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} = <STDIN>);
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 (<TMP0>) { 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 (<TMP1>) {
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 (<TMP2>) {
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 <enter> to continue.";
(<STDIN>);
}
##################################################################################
# Really don't have to explain this'un, eh?
sub USAGE {
my $specific_err = $_[0];
print <<EOF;
$specific_err
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.
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
</code>
Automate collection of Cisco router and LAN switch information for inventory purposes:
<br> device name, hardware type, IOS/CatOS ver, serial number
<p>
From a Perlish standpoint, this has been an excercise in using hashes instead of buckets o' scalar variables or array with obtuse <code>$file[0]</code>, and more discriminate use of subroutines so not need passel o' global variables.
<p>
<b>Updated: </b>
<br>2001-04-13 <tt>perldoc switchver.pl</tt> for details.
<p>
<b>Todo: </b>
<br>Use Net::Snmp sysDescr instead of Net::Telnet::Cisco.
<br> simplify data structure
<br> simplify parsing
<br> simplify program
<br> improve security
Networking Code
ybiC