http://qs1969.pair.com?node_id=1146993

This is a script for reading config info from a bunch of routers (for archiving, autiting, whatever)

The configuration for the script itself is read from a INI-style config file (example below). Each router is represented by a section with its name or IP as the section header. The "info" attribute is mandatory (originally intended as title information for a to-be-generated report). Then there has to be a section "dump" (which means if you have a router with a hostname of "dump", you have to refer to it by its IP address).

Each entry in the "dump" section results in a command sent to the router(s), and the output of this command is written to a file.

The output is placed in the directory named by "dir" in the top INI section. Each router gets its own subdirectory, and if set "debug", there will be a subdirectory "debug" - so you should not use "debug" as hostname, either :-)

The "enable" password is needed, because on Cisco devices, the terminal size command is privileged.

Initially, I used Net::Telnet::Cisco, and later adapted it for SSH. After trying with SSH modules directly, I settled with Control::CLI. It took me a while noticing that they use different ways of to specify prompts, before getting it to work.

Of course it is bad to have passwords stored as plaintext, but at least they are kept separate from the script itself. It runs under Windows, but should work under other systems, too.

Additionally, I uploaded it to a github gist, just in case someone wants to tinker.

use strict; use warnings; use Data::Dumper; use Config::Tiny; use Path::Tiny 'path'; binmode STDOUT, ':encoding(cp437)'; use Control::CLI; my $ini = Config::Tiny->read( 'ciscodump.ini' ); my $dumpdir = path($ini->{_}{dir}); my $debugdir = $dumpdir->child('debug'); my $debug = $ini->{_}{debug}; $Data::Dumper::Useqq = 1; sub dbg { print @_ if $debug; @_; } dbg Dumper $ini; my @k = grep { $_ !~ /^(_|dump)$/ } keys %$ini; print Dumper \@k if $debug; my %dumpcommands = %{$ini->{dump}}; my $prompt = '(?m:^\W?(?!Device#)[\w\/\d.:-]+[>#])'; my %cs_opts; $cs_opts{Prompt} = $prompt; $cs_opts{Errmode} = 'return'; $cs_opts{Output_record_separator} = "\r"; for my $host (@k) { my $user = $ini->{$host}{user} // $ini->{_}{user}; my $pass = $ini->{$host}{pass} // $ini->{_}{pass}; my $enable = $ini->{$host}{enable} // $ini->{_}{enable}; my $info = $ini->{$host}{info} // $ini->{_}{info}; my $method = $ini->{$host}{use} // 'SSH'; print "$host\t- $info\n"; $debug = $ini->{_}{debug} // $ini->{$host}{debug}; $debugdir->mkpath if $debug; $cs_opts{Dump_Log} = $debugdir->child($host . '.log')->stringify i +f $debug; $cs_opts{Timeout} = $ini->{$host}{timeout} // 10; my $hostdir = $dumpdir->child($host); $hostdir->mkpath; my $cc = Control::CLI->new( %cs_opts, Use => $method ); my %login_opts = ( Username => $user, Password => $pass, ); if ($method =~ /^TELNET$/i) { if ($cc->connect(Host => $host)) { dbg "TELNET connection established\n"; if ($cc->login(%login_opts)) { dbg "logged in\n"; } else { warn 'login failed: ' . $cc->errmsg(); } } else { warn 'connection could not be established: ' . $cc->er +rmsg(); next; } } else { # SSH if ($cc->connect(Host => $host, %login_opts)) { dbg "SSH connection established\n"; } else { warn 'connection could not be established: ' . $cc->er +rmsg(); next; } } dbg Dumper $cc->read(Blocking => 1, Timeout => 5); dbg "enable>\n"; dbg Dumper $cc->cmd(command => 'enable', prompt => 'Password:' +); # dbg "waiting>\n"; # my @wf = $cc->waitfor(Match => 'Password:'); # dbg Dumper \@wf; dbg "sending>\n"; dbg Dumper $cc->cmd($enable); dbg "after>\n"; my $out = $cc->cmd('term len 0'); dbg Dumper $out; dbg Dumper $cc->cmd('term page 0') if grep { /invalid/i } $out +; for my $prefix (keys %dumpcommands) { my $cmd = $dumpcommands{$prefix}; dbg "command: '$cmd'\n"; my $out = $cc->cmd($cmd); dbg Dumper \$out; $hostdir->child($prefix . '.txt')->spew_raw($out); } $cc->disconnect; };
And here is the INI file example
user=admin pass=confidential enable=top_secret dir=C:\Users\Administrator\Desktop\ciscodump [dump] inventory=show inventory version=show version running-config=show running-config startup-config=show startup-config [192.168.0.1] info=location with different parameters user=otheruser pass=public timeout=30 [192.168.11.1] info=somewhere else, but "central" parameters [192.168.21.100] info=ancient router use=TELNET [192.168.254.254] debug=yes info=lets see...

Replies are listed 'Best First'.
Re: ciscodump - dump config info from routers into files
by VinsWorldcom (Prior) on Nov 05, 2015 at 16:23 UTC

      Even if you don't consider it "your work", please do consider uploading it onto CPAN so that other people can easily install it! It might help somebody who looks for something like it and save them some work :-)

        I wasn't sure what the etiquette would be on issue like this. I sent emails to the module authors, but got no response.

        I'll clean it up and get it on CPAN with the appropriate credit, references, etc...

Re: ciscodump - dump config info from routers into files
by stevieb (Canon) on Nov 06, 2015 at 15:30 UTC

    One way to avoid having the password documented in the config file, is to provide the user of the script the option of setting it in their shell's ENV instead. This would be temporary and only visible within the current shell running the script. If they have different passwords for each device, it would be cumbersome (it could be done though). Here's an (untested) example:

    my $pass; if (defined $ENV{PASS}){ $pass = $ENV{PASS}; } elsif (defined $ini->{$host}{pass}){ $pass = $ini->{$host}{pass}; } elsif (defined $ini->{_}{pass}) { $pass = $ini->{_}{pass} } else { die "no password could be found"; }

      Yes, that would have been an option, as in my case, there was only 1 system with a different password.

      But as we are using KeePass already, the next step may probably incorporating File::KeePass. Now, the password for the KeePass database… ;-)