Execute commands on a router/switch using Telnet (Perl)
From NMSWiki
This script can be used to execute commands on a router or switch (I've only tested cisco). It supports AAA network authentication and can also fall back to trying local authentication - I've found this useful during TACACS key changes where there might be a small window of time where the device's TACACS key is out of synch with the AAA server and you need to get into this device.
It also determines the device type, so you can execute IOS or CatOS specific commands, and will try multiple different community strings (useful for SNMP community string changing?)
This is not the full version of the program I originally wrote, which allows you to put the commands you want to execute on a device in a file that is read in at startup, but if you really want that functionality it should not be too hard to add yourself.
There are a lot of dependencies for this script, which I won't list out here, but you should be able to examine the code and easily deduce them yourself. This script also requires two config files, which are shown below.
You can run this script two ways:
cmang.pl -s <device> -c <command to be executed>
cmang.pl -f <file containing list of devices> -c <command to be executed>
#!/usr/local/bin/perl -w
############################################################################
# TITLE:
# cmang.pl - Cisco Mangler
# By Michael J. Freeman (mfreeman@spammeplease.com)
#
# $Id: cmang.pl 858 2006-09-12 20:58:24Z mfreeman $
############################################################################
# PURPOSE:
# Used to execute commands on network devices and collect/store output
############################################################################
# PRELOAD:
#use lib '/etc/local/lib';
############################################################################
# INCLUDE:
use strict;
use warnings;
use Time::localtime;
use Net::Telnet::Cisco;
use IO::File;
use SNMP_Session;
use BER;
use SNMP_util;
use Getopt::Std;
use Config::Tiny;
use Log::Log4perl;
############################################################################
# VARIABLES:
# This is the command to be executed on the device
#my $ts_cmd = "show version";
## DO NOT EDIT ANYTHING IN THIS FILE ##
# VERSION
my $VERSION = "0.1";
use vars qw/ %opt /;
$|++; # Flush Output
# Suppress SNMP warnings
$SNMP_Session::suppress_warnings = 1;
# DEBUG
#my $DEBUG = 10;
my $DEBUG = 0;
# log directory (input log save location for Net::Telnet::Cisco)
my $log_dir = '/var/log/telnet/';
# Parse the config file using Config::Tiny
my $cf_file = "/usr/local/etc/cmang.conf";
my $Config = Config::Tiny->read("$cf_file");
# TACACS
my $tac_login = $Config->{TACACS}->{login};
my $tac_password = $Config->{TACACS}->{password};
# Local Authentication (Fallback if TACACS is unavailable)
my $loc_login = $Config->{LOCALAUTH}->{login};
my $loc_password = $Config->{LOCALAUTH}->{password};
my $loc_enable = $Config->{LOCALAUTH}->{enable};
# Cisco
my $ios_timeout = $Config->{CISCO}->{IOS_timeout};
my $catos_timeout = $Config->{CISCO}->{CatOS_timeout};
my $router_timeout = $Config->{CISCO}->{Router_timeout};
my $login_timeout = $Config->{CISCO}->{login_timeout};
# SNMP community strings
my (@snmp_comm);
push @snmp_comm, $Config->{SNMP}->{community1};
push @snmp_comm, $Config->{SNMP}->{community2};
push @snmp_comm, $Config->{SNMP}->{community3};
push @snmp_comm, $Config->{SNMP}->{community4};
my %months = (
1, 'JAN', 2, 'FEB', 3, 'MAR', 4, 'APR', 5, 'MAY', 6, 'JUN',
7, 'JUL', 8, 'AUG', 9, 'SEP', 10, 'OCT', 11, 'NOV', 12, 'DEC'
);
my $tm = localtime;
my ( $DAY, $MONTH, $YEAR ) = ( $tm->mday, $tm->mon, $tm->year );
my $mon = $MONTH + 1;
my $year = $YEAR + 1900;
my $m = $months{$mon};
my $date = "$DAY-$m-$year";
# Log4perl
my $log4perl_conf = $Config->{LOGGING}->{conf_file};
Log::Log4perl->init("$log4perl_conf");
my $logger = Log::Log4perl->get_logger();
# Used to support multiple authentication methods (TACACS, local)
my $try_local = 0;
my $try_local_enable = 0;
############################################################################
# INIT:
sub init {
usage() unless $ARGV[0];
# Log4perl init
Log::Log4perl->init("$log4perl_conf");
my $opt_string =
'hs:f:c:'; # help, single device, file (device_list), command
getopts( "$opt_string", \%opt ) or &usage;
usage() if $opt{h};
usage() unless $opt{c};
}
############################################################################
# USAGE:
# Help Message
sub usage {
print STDERR << "EOF";
Usage: $0 [-hvd] [-s <device>] [-f <file>] -c <command>
-h\t\t: prints this message
-v\t\t: print debugging messages to STDOUT
-s <device> : execute test scripts for <device>
-f <file> : get hosts from file and grab data
-c <command> : execute <command> on "device"
[-v]\t\t: verbose output
Example: $0 -s 172.17.253.4 -c "show config"
Example: $0 -f spna.hosts -c "show int"
EOF
exit;
}
############################################################################
# INIT:
# Do something..
init();
############################################################################
# MAIN:
$logger->info("CMANG starting..");
my $ts_cmd = $opt{c};
my (@host_list);
push @host_list, $opt{s} if ( $opt{s} );
if ( $opt{f} ) {
open( HOSTS_FILE, "$opt{f}" ) or $logger->logdie("host_list: Couldn't open file: $!");
@host_list = <HOSTS_FILE>;
close(HOSTS_FILE);
}
# Let's keep track of how many devices we successfully contact and execute our job on
my $host_cnt = 1;
my $host_list_tot = $#host_list + 1;
my $host_success = 0;
$logger->info("Processing $host_list_tot hosts");
NEXTDEV: # Our &errdev error handler will call back into this block if it hits an error
foreach my $host (@host_list) {
$host =~ s/\s+$//; # Get rid of nasty white space
&log_it('debug',$host,"[$host_cnt/$host_list_tot]");
$host_cnt++
unless ( $#host_list == 0 ); # If our array only has one host in it, no need to increment
# our $host_cnt counter#
# Determine device type (ios, router, catos)
my ( $sysDescr, $p_comm_string );
foreach my $comm_string (@snmp_comm) { # Try multiple SNMP Community strings
$sysDescr = ( snmpget( "$comm_string\@$host", 'sysDescr' ) )[0]
or $logger->info("$host: Couldn't get sysDescr.. trying another community string");
if ($sysDescr) {
$p_comm_string = $comm_string;
last;
}
}
unless ($sysDescr) {
&log_it('error',$host,"Couldn't get sysDescr. Skipping...");
next NEXTDEV;
}
my ($device_type,$timeout);
if ( $sysDescr =~ s/\s(WS-[^\s]+)// ) {
$device_type = "catos";
$timeout = $catos_timeout;
} elsif ( $sysDescr =~ /C35|C29|s72033|c6sup2|cat4000-I9S-M|IOS.*?Catalyst/ ) {
$device_type = "ios";
$timeout = $ios_timeout;
} elsif ( $sysDescr =~ s/\s\((C[^)]+)// ) {
$device_type = "router";
$timeout = $router_timeout;
} elsif ( $sysDescr =~ m/DSL/ ) {
$device_type = "router";
$timeout = $router_timeout;
} else {
&log_it('info',$host,"Unknown device type: $sysDescr");
next;
}
# Get the hostname, and derive the PSI code
my $sysName = ( snmpget( "$p_comm_string\@$host", 'sysName' ) )[0]
or $logger->info("$host: Couldn't get sysName.. trying another community string");
unless ($sysName) {
&log_it('error',$host,"Couldn't get system.sysName.0 via SNMP. Skipping..");
next NEXTDEV;
}
my $psi = "$1" if ( $sysName =~ /^(\w+)-/ );
$logger->info("$host: sysName reported as $sysName - Type: $device_type");
$logger->debug("$host: Beginning test script output collection..");
print "Gathering config information for $host\n" if $opt{v};
# Establish telnet connection to device
my $session = Net::Telnet::Cisco->new(
Host => "$host",
Input_Log => "$log_dir/$host-input.log",
);
# Send a 'wakeup' to slow CatOS devices. See RT ticket 799
if ($device_type eq "catos") {
$session->send_wakeup('timeout');
$session->send_wakeup('connect');
}
# Setup our error handler
$session->errmode( sub { &login_err( $session->errmsg, $host ) } );
if ( !$session ) {
&log_it('error',$host,"Couldn't connect to device with Telnet. CMANG FAILED for $host!");
next;
}
# Authenticate to the device using TACACS credentials
if ( ! $try_local ) {
$logger->debug("$host: Trying TACACS.");
if ( !$session->login( Name => $tac_login, Password => $tac_password , Timeout => $login_timeout) ) {
$logger->info("$host: Couldn't get in using TACACS.. trying local");
#$try_local = 1;
#$try_local_enable = 1;
}
$try_local = 0;
$try_local_enable = 0;
} else {
$session->errmode('return');
if ( !$session->login( Name => $loc_login, Password => $loc_password, Timeout => '1' ) ) {
print "$host: Problem with login : " . $session->errmsg . "\n" if $opt{v};
$logger->error( "$host: Authentication error : ", $session->errmsg );
$session->close;
&log_it('error',$host,"Authentication error. CMANG collection failed for $host. Session closed.");
$try_local = 0;
next;
}
}
# Reset our errmode so we just return when we hit an error with the
# Net::Telnet::Cisco module
$session->errmode('return');
# Jump into enable mode.. we should already be there if our tacacs
# login is lvl 15, this is just a safeguard
if ( ! $try_local_enable ) {
if ( !$session->enable($tac_password) ) {
$logger->info("Couldn't go into enable mode using TACACS.. trying local");
$try_local_enable = 1;
}
} elsif ( !$session->enable("$loc_enable") ) {
print "$host: Problem getting into enable mode (local auth) : " . $session->errmsg . "\n"
if $opt{v};
$logger->error("$host: Couldn't get into enable mode");
$session->close;
&log_it('error',$host,"Couldn't get into enable mode. Session closed for $host.");
$try_local_enable = 0;
$try_local = 0;
next;
}
# reset so we go back to normal authentication on next device
$try_local = 0;
$logger->debug("$host: Found $device_type device");
print "$host: Found $device_type device\n" if $opt{v};
$logger->info("$host: Executing test scripts");
print "$host: Executing $ts_cmd..\n" if $opt{v} && ( $DEBUG >= 10 );
my @output = $session->cmd(
String => "$ts_cmd",
Timeout => "30"
);
my $ts_cmd_out = join( "", @output );
$logger->debug("$host: Executed -> $ts_cmd");
print "$ts_cmd_out\n" if $opt{v} && ( $DEBUG >= 10 );
print $logger->info("$host: Done");
# close our session and go on to the next device
$session->close;
$host_success++;
}
#$host_cnt-- if ($host_cnt > $host_list_tot);
$logger->info("Total amount of hosts worked on [$host_success/$host_list_tot]");
############################################################################
# LOGIN_ERR:
sub login_err {
my ( $msg, $host ) = @_;
no warnings;
if ( $msg =~ /pattern match timed-out/ ) {
&log_it('error',$host,"Fix command prompt: $msg");
} elsif ( $msg =~ /access denied/ ) {
&log_it('debug',$host,"Access-Denied, trying local credentials");
# we're gonna switch back to our NEXTDEV block, but we want
# to make sure this time we go straight to trying the local authentication
$host_cnt--;
$try_local = 1;
$try_local_enable = 1;
# since we're calling back into the NEXTDEV marker, we have to tell it to
# retry the same host again, so we put it back into the @host_list array
# at the very beginning. This works with or without an empty list.
unshift @host_list, $host;
} else {
&log_it('debug',$host,"Unhandled error - $msg");
}
next NEXTDEV;
}
############################################################################
# LOG_IT:
sub log_it {
my($level,$host,$message)=@_;
if ($level eq 'error') {
$logger->error("$host: $message");
} elsif ($level eq 'info') {
$logger->info("$host: $message");
} elsif ($level eq 'debug') {
$logger->debug("$host: $message");
} elsif ($level eq 'logdie') {
$logger->logdie("$host: $message");
}
print "$host: $message\n" if $opt{v};
#print LOG "$host: $message\n" if $opt{f};
}
CMANG config file (/usr/local/etc/cmang.conf):
[NOC] email=mfreeman@spammeplease.com [TACACS] login=mfreeman password=foobar [LOCALAUTH] login=mfreeman password=moome enable=foome [SNMP] community1=public community2=private community3=FooB4r community4=sn4p3k1ll3ddumbl3d0re [CISCO] IOS_timeout=25 CatOS_timeout=60 Router_timeout=25 login_timeout=60 [LOGGING] conf_file=/usr/local/etc/cmang-log.conf
Log4perl config file (/usr/local/etc/cmang-log.conf):
############################################################ # A simple root logger with a Log::Log4perl::Appender::File # file appender in Perl. # $Id: cmang-log.conf 667 2006-04-12 19:59:34Z mfreeman $ ############################################################ log4perl.rootLogger=INFO, LOGFILE log4perl.appender.LOGFILE=Log::Log4perl::Appender::File log4perl.appender.LOGFILE.filename=/var/log/cmang.log log4perl.appender.LOGFILE.mode=append log4perl.appender.LOGFILE.layout=PatternLayout log4perl.appender.LOGFILE.layout.ConversionPattern=[%r] %d %F %L %c - %m%n

