#!/usr/bin/perl

#(C) Fredrik Widell (fredrik@widell.net) 2003
#use as you wish, modify as you wish
#this script will probably destroy your whole
#network in a second, dont blame me. send me
#a email if you want to. Run the script without
#arguments for a howto. I use this to configure
#and collect data in the SUNET-network core.
#
#the telnet-code works quite smooth, the ssh-code
#needs some patching.

# The file format of the ciscopasswords-in-a-file is
#
# router user password ena_password
#
# one line per router
 
use strict;
use Getopt::Long;
use Net::Telnet ();
use Net::SSH::Perl;


#default values
my $passwords='/some/path/to/ciscopasswords-in-a-file';
my $enable=0;
my $port='23';
my $commandisfile=0;
my $init='ter len 0';
my @commands;
my @routeroutput;
my $magicword='ROUTER';

my %passwords;

my ($command, $opts, $id, $pass, $enable, $router, $ssh, $nouser);
my @routers;


#get the commandline input
sub get_commandline_options {
    # Get commandline options
    $opts = {};
    GetOptions($opts, ("n","t=s","s","e=s","u=s","f=s","o=s","p=s","c=s","r","help","h"));
    
    # Consolidate options with long and short names
    if ($opts->{'help'}) {
        $opts->{'h'} = $opts->{'help'}
    }
    
    # If there are no arguments, set h to 1 (help)
    if ($#ARGV < 0) {
        $opts->{'h'} = 1;
    }
    
    return $opts->{'h'};
}

#if no input display help
sub display_help {
    print qq(
Usage: $0 [-h] [-r] [-n] [-f /path/to/passwordfile] [-u userid] [-p "password"] [-e "enablepassword"] [-o /path/to/outputfile] [-s] [-t <portnumber>] -c <commands>|</path/to/commandfile> <routers>&|</path/to/listofrouters>

-h	Print this help

-r 	enable, will exec the commands in enable-mode

-f	alternative passwordfile, default is $passwords
	the format for this file is:
	routername1 userid password enablepassword
	routername2 userid password enablepassword

-u	if you don't want to use a passwordfile you can specify user here.

-p	and a password for this user. Only valid if you do not use a passwordfile.

-e	and the enablepassword. Only valid if you do not use a passwordfile.

-o	save the output to file(s), if filename is ROUTER, the script will save
	the output to unique files in current directory based on the routername. 
	Otherwise the output will be appended to the name specified. If the file
	exist it will contain old and new data.

-c 	commands, either e.g "sh ver" or ./file which contains a list of commands,
	if ./file contains the word ROUTER this text will be replaced with the current
	name of the router logged in to. 

-s	use SSH instead of telnet. In ciscos this can only be used to send single
	commands since they only use SSHv1. No configure etc.

-n	do NOT use 'user, password' but just a simple 'password' to login to the
	router, this option accepts a passwordfile in the format:
	router1 password enablepassword
	router2 password enablepassword
	and this does not work with ssh, just with telnet.

-t	use this port instead of the default 23 for telnet. (good for eg zebras)

Examples:

$0 -c "sh log" routername

$0 -r -c "sh run" -o ROUTER router1 router2 ./file-with-routernames

$0 -c ./file-with-commands ./file-with-routernames

$0 -u myloginid -p "mypassword" "sh ip int br" router1

$0 -f ./mypasswordfile -o all-output.txt -e -c "sh run | inc nterface|escr" ./file-with-routernames

);
}


	

	


#converting options 
if (get_commandline_options) {
    display_help;
    exit(0);
}
if ($opts->{'u'}) {
	$id=$opts->{'u'};
}
if ($opts->{'p'}) {
	$pass=$opts->{'p'};
}
if ($opts->{'e'}) {
	$enable=$opts->{'e'};
}
if ($opts->{'s'}) {
	$ssh=1;
}
if ($opts->{'r'} && !$enable) {
	$enable=1;
}
if ($opts->{'n'}) {
	$nouser=1;
}
if ($opts->{'t'}) {
	$port=$opts->{'t'};
}

#alternate passwordfile
if ($opts->{'f'}) {
	$passwords=$opts->{'f'};
}

#checking if commands is in the prompt or in a file
if ($opts->{'c'}) {
	$opts->{'c'} =~ /(\/)/;
	if ($1) {
		$commandisfile=1;
		$command=$opts->{'c'};
	} else {
		$command=$opts->{'c'};
	}

#we must have atleast one command
} else {
	display_help;
	exit(0);
}

#making a list of all routers
my @typerouters=@ARGV;
my $type;
foreach $type (@typerouters) {
	while ($type) {
		$type =~ /(.*\/.*\s?)(.*)/;
		if ($1) {
			open ROUTERFILE, "<$type" or die "can'nt open $type $!";
			my $router;
			while(defined($router=<ROUTERFILE>)) {
				chomp($router);
				@routers = (@routers, $router);
			}
			close ROUTERFILE;
		} else {
			@routers = (@routers, $type);
		}
		$type=$2;
	}
}


#make a hash of the passwords, with the routers as keys
if (!$opts->{'p'}) {
	open PASSWORDS, "<$passwords" or die "can't open $passwords $!";

	my $trash;
	while (defined($trash=<PASSWORDS>)) {
		if ($nouser) {
			my($router, $pass, $enable)=split /\s/, $trash;
			$passwords{$router}=$pass . " " . $enable;
		} else {
			my($router, $id, $pass, $enable)=split /\s/, $trash;
			$passwords{$router}=$id . " " . $pass . " " . $enable;
		}
	}

	close PASSWORDS;
} else {
	foreach $router (@routers) {
		if ($nouser) {
			$passwords{$router}=$pass . " " . $enable;
		} else {
			$passwords{$router}=$id . " " . $pass . " " . $enable;
		}
	}
}
	

#print keys %passwords;
#foreach my $k (sort keys %passwords) {
#    print "$k => $passwords{$k}\n";
#}




#lets get to business, exec the commands in all routers.
my $router;
my $rename;

#checking if I should save output to file or stdout, and if the file
#is one big file or one per router
if ($opts->{'o'}) {
	if ($opts->{'o'} eq 'ROUTER') {
		$rename=1;
	} else {
		$rename=0;
	}
} else {
	open FIL, ">/dev/stdout" or die "can't open /dev/stdout $!";
}
		

foreach $router (@routers) {
	if (!$passwords{$router}) {
		print "$router missing in $passwords, skipping\n";
		next;
	}
	if ($opts->{'o'} && $rename) {
		#save output from each router to unique files
		open FIL, ">$router" or die "cant open file $router $!";
	} 
	if ($opts->{'o'} && !$rename){
		#save all output from all routers to single file
		open FIL, ">>$opts->{'o'}" or die "can't open file $opts->{'o'} $!";
	}
	print FIL "\n! ---- will exec command(s) in $router ";
	print FIL "in enabled mode " if (($enable||$opts->{'e'}));
	print FIL " via telnet port $port" if (!$opts->{'s'});
	print FIL " via SSH " if ($opts->{'s'});
	print FIL "---- !\n";

	if ($ssh) {
		ssh($router, $command);
	} else {
		telnet($router, $command);
	}
	close FIL if ($opts->{'o'});
}

sub telnet {
	my ($router, $cmd)=@_;
	my $prompt='/\w.*[>#]\s?$/';
	my @out=undef;
	my @tmp;
	my $trash;
	my ($id, $pass, $enablepass);
	my $t = new Net::Telnet (Timeout        => 40,
				Prompt          => $prompt,
				Port            => $port);

	#print "\nsub using telnet port $port\n";

	if ($nouser) {
		($pass, $enablepass)=split /\s/, $passwords{$router};
	} else {
		($id, $pass, $enablepass)=split /\s/, $passwords{$router};
	}
	eval {
		$t->open($router);
		if ($nouser) {
			$t->print($pass);
			$t->waitfor($prompt);
		} else {
			$t->login($id, $pass);
		}

		if ($enable||$opts->{'e'}) {
			$t->print('enable');
			$t->waitfor(/Password: /);
			$t->cmd("$enablepass");
		}

		$t->cmd($init);

		#checking if command is line or file
		if ($commandisfile) {
			open COMMAND, "<$cmd" or die "can't open $cmd $!";
			while (defined($cmd=<COMMAND>)) {
				chomp($cmd);
				print FIL "$router $cmd\n";
				@out = $t->cmd("$cmd");
				print FIL @out;
			}
			close COMMAND;
		} else {
			print FIL "$router $cmd\n";
			#@out = $t->cmd("$cmd");
			@out = $t->print("$cmd");
			@out = (@out, $t->waitfor($prompt));
		}
		$t->close;
		print FIL @out;
	};
	if ($@) {
		print FIL "error logging in to $router,$@,\n";
	}
	#return @out;
}

sub ssh {
	my ($router, $cmd)=@_;
	my $prompt='/.*[>#]$/';
	my @out=undef;
	my $trash;
	#my %params;
	#$params{protocol} = 2;

	print "\nsub using ssh\n";

	my ($id, $pass, $enablepass)=split /\s/, $passwords{$router};

	#my $ssh = Net::SSH::Perl->new($router, %params);
	my $ssh = Net::SSH::Perl->new($router);
	$ssh->login($id, $pass);
	#$ssh->shell;

	if ($enable||$opts->{'E'}) {
		$ssh->cmd('enable');
		#$ssh->waitfor(/Password: /);
		$ssh->cmd("$enablepass");
	}

	@out=(@out, $ssh->cmd($init));

	#checking if command is line or file
	if ($commandisfile) {
		open COMMAND, "<$cmd" or die "can't open $cmd $!";
		while (defined($cmd=<COMMAND>)) {
			chomp($cmd);
			print FIL "$router $cmd\n";
			@out = $ssh->cmd("$cmd");
			print FIL @out;
		}
		close COMMAND;
	} else {
		print FIL "$router $cmd\n";
		@out = $ssh->cmd("$cmd");
		print FIL @out;
	}
	#return @out;
}
