#!/usr/bin/perl -w # cdp-n.pl # pod at tail use strict; use Net::Telnet::Cisco; use Term::ReadKey; use Tie::IxHash; ### Begin Config Parameters ### my %parm = ( tntimeout => 10, pwtimeout => 60, errmode => 'return', ); my %file = ( in => 'cdp-n.in', tmp0 => 'cdp-n.tmp0', tmp1 => 'cdp-n.tmp1', tmp2 => 'cdp-n.tmp2', tmp3 => 'cdp-n.tmp3', tmp4 => 'cdp-n.tmp4', uniqout => 'cdp-n.uniq', ); my @tmpfiles = qw( $file{tmp0} $file{tmp1} $file{tmp2} $file{tmp3} $file{tmp4} ); my @commands = ( 'term leng 0', # IOS 'set leng 0', # CatOS 'sho cdp n de', # distinguish 'details' from 'duplex' ); my @keeplines = ( # lines of text to be kept 'scrpne', # Corporate switches 'cff', # old Frozen switches at Campus 5 'pdl', # old Frozen switches at Campus 6 ); my @stripchars = ( # specific text to be stripped from what's left '\.\w.*', # domain name (if present) through end-of-line '^.*\(', # characters and paren before devicename '\).*$', # paren after devicename through end-of-line 'device-id:\s', # preceeds some switches, dunno why 'device id:\s', # preceeds some switches, dunno why '^.*>.*$', # command prompts '^\s+', # leading spaces ); my @striplines = ( # strip all lines containing this text '-MSFC', '-msfc', 'RSM', 'rsm', 'Password', 'password', 'unintialized value', ); ### End Config Parameters ### my ($pass, %seen); Usage() unless ((@ARGV) or (-s $file{in} && -T_)); open (TMP1, ">$file{tmp1}") or die "Error opening $file{tmp1} WO: $!"; close (TMP1) or die "Error closing $file{tmp1}: $1"; # $file{tmp1} must be present to prevent errors if first device unreachable print "\n", " Prompting for password\n", " (*not* echoed to screen nor written to disk)\n\n", ' Enter password:'; ReadMode('noecho'); eval { local $SIG{ALRM} = sub { die "ALARUM" }; alarm("$parm{pwtimeout}"); chomp($pass = ); alarm(0); }; if ($@ =~ /ALARUM/) { print "\n\n", " Sorry - you waited too long before entering a password.\n", " Try again, if you want.\n\n"; ReadMode(0); exit; } ReadMode(0); unless (@ARGV) { print " using $file{in} for input\n"; @ARGV = $file{in}; chomp (@ARGV = <>); } print "\n Starting CDP neighbor check, shouldn't take long...\n"; for my $cisco (@ARGV) { if (my $cs=Net::Telnet::Cisco->new( host => $cisco, timeout => $parm{tntimeout}, errmode => $parm{errmode}, input_log => $file{tmp0}, ) ) { $cs->login('',$pass); # vty login print ' ', $cs->last_prompt, ' '; # print to console for(@commands) { my @output = $cs->cmd($_); } print $cs->last_prompt, "\n"; # print to console $cs->close; # exit session open (TMP0, "<$file{tmp0}") or die "Error opening $file{tmp0} RO: $!"; open (TMP1, ">>$file{tmp1}") or die "Error opening $file{tmp1} for append: $!"; while () { print TMP1 $_; } close (TMP0) or die "Error closing $file{tmp0}: $!"; close (TMP1) or die "Error closing $file{tmp1}: $!"; } else { warn "Error connecting to $cisco\n"; } # if telnet connect fail } unlink ($file{tmp0}) or die "Error unlinking $file{tmp0}: $!"; print " Finished CDP neighbor check.\n"; print " Extracting lines of interest...\n"; open (TMP1, "<$file{tmp1}") or die "Error opening $file{tmp1} RO: $!"; open (TMP2, ">$file{tmp2}") or die "Error opening $file{tmp2} RW: $!"; while () { foreach my $keep(@keeplines) { if (/$keep/i) { print TMP2 $_; } } } close (TMP1) or die "Error closing $file{tmp1}: $!"; unlink ($file{tmp1}) or die "Error unlinking $file{tmp1}: $!"; close (TMP2) or die "Error closing $file{tmp2}: $!"; print " Stripping unwanted characters...\n"; open (TMP2, "<$file{tmp2}") or die "Error opening $file{tmp2} RO: $!"; open (TMP3, ">$file{tmp3}") or die "Error opening $file{tmp3} RW: $!"; while () { foreach my $strip(@stripchars) { s/$strip//gi; } tr/A-Z/a-z/; print TMP3 $_; } close (TMP2) or die "Error closing $file{tmp2}: $!"; unlink ($file{tmp2}) or die "Error unlinking $file{tmp2}: $!"; close (TMP3) or die "Error closing $file{tmp3}: $!"; print " Stripping unwanted lines...\n"; open (TMP3, "<$file{tmp3}") or die "Error opening $file{tmp3} RO: $!"; open (TMP4, ">$file{tmp4}") or die "Error opening $file{tmp4} RW: $!"; while () { foreach my $strip(@striplines) { s/^.*$strip.*//gi; } s/^\s+$//gi; print TMP4 $_; } close (TMP3) or die "Error closing $file{tmp3}: $!"; unlink ($file{tmp3}) or die "Error unlinking$file{tmp3}: $!"; close (TMP4) or die "Error closing $file{tmp4}: $!"; print " Stripping duplicate lines...\n"; open (TMP4, "<$file{tmp4}") or die "Error opening $file{tmp4} RO: $!"; open (UNIQ, ">$file{uniqout}") or die "Error opening $file{uniqout} RW: $!"; foreach my $item () { ++$seen{$item}; # parse $file{in} into a hash } # and remove duplicate keys print UNIQ sort keys %seen; # sort keys and save to $outfile close TMP4 or die "Cannot close $file{tmp4}: $!"; unlink ($file{tmp4}) or die "Error unlinking$file{tmp4}: $!"; close UNIQ or die "Cannot close $file{uniqout}: $!"; print " Finished parsing results.\n", " Output at $file{uniqout}\n\n\a"; ########################################################################### sub Usage { print < to continue, ctrl-C to quit.\n"; (); } ########################################################################### =head1 Name cdp-n.pl =head1 Summary Generate comprehensive list of Cisco LAN switches installed. =head1 Usage cdp-n.pl switch1 switch2 switch3 will query the 3 named (or numbered) switches for CDP neighbors cdp-n.pl with no arguments will read $file{in} for list of switches to query. $file{in} would be a text file that looks like this: switch1 switch2 switch3 ASCII text file One IP address or DNS name per line FQDN if targets in different DNS domain No leading/trailing spaces No blank lines First run this program against a core switch, then recurse down, using output as next input. Review and edit .in files between recursions, and move outfile for each iteration also. Remember to add each recursion back into final results, then run uniq.pl to sort and extract unique entries from combined outfile. =head1 Tested with: Perl 5.00503 on Debian 2.2r2 "Espy" against: Catalyst 2916/24, 5500, =head1 Updated 2001-04-25 14:15 Add password timeout Simplify foreach $scalar(@array) to 'for(@array)' and '$_' Hashimafy file scalars Unsubify non-redundant code Mixed-case subroutines Eliminate global vars with my(,) instead of use vars qw() Reformat to 75 chars/line max 2001-03-29 Eliminate unnecessary quotes Change double to single quotes for strings @keep case-insensitive 2000-09-20 Initial working code =head1 ToDos Use File::Slurp to append log (avoid race condition) Use File::Temp instead of multiple temp files =head1 Author Don [ybiC] Royer =head1 Credits Thanks to tilly, fastolfe, japhy, geektron and chromatic for suggestions and critique, and to tilly, mkmcconn and boo_radley for cat filename | sed -e 's/$/.subdomain.dom/' > newfilename to fully-qualify hostnames in resulting outfiles so they can be fed back in as infiles. =cut