#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
    if 0; # not running under some shell


# $Id: snarf.PL,v 2.11 2002/03/14 16:40:29 gmj Exp $
#
# snarf - grab cisco configs
#
#
#
# Copyright (C) 2002  George M. Jones <gmj@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


=head1 NAME

router-snarf - retrieve router configuration files

=head1 SYNOPSIS

router-snarf [-user=user] [-userpw=pw] [-enablepw=pw] [-noclobber] [-dir=outputdir] router...

B<router-snarf>
[ B<-user=>I<username> ]
[ B<-userpw=>I<userpw> ]
[ B<-enablepw=>I<enablepw> ]
[ B<-noclobber> ]
[ B<-dir=>I<outputdir> ]
I<addr> [addr ...]

=head1 DESCRIPTION

B<router-snarf> logs in to one or more network devices and retrieves the
configurations.  

=head1 OPTIONS

=over 8

=item user

The C<-user> flag specifies the username to use when logging in.  The
default is the current username. If this is not specified, the
contents of the environment variable SNARFUSER are used, if defined.

=item userpw

The C<-userpw> flag specifies the user password to use when logging in.
If this is not specified, the contents of the environment variable
USERPW are used.  If USERPW is not defined, the user is prompted
for the password with echo turned off.

=item enablepw

The C<-enablepw> flag specifies the enable password to use when enabling.
If this is not specified, the contents of the environment variable
ENABLEPW are used.  If ENABLEPW is not defined, the user is prompted
for the password with echo turned off.

=item noclobber

The C<-noclobber> flag specifies that existing configurations are
not to be overwritten.

=item dir

The C<-dir> flag allows the specification of an alternate directory
in which to store the configs that are retrieved.  The default 
is the current directory.

=item B<-V, --version>

The C<--version> option displays the current program version.

=back

=head1 ARGUMENTS

The B<addr> argument(s) allow the user to specify the names or
addresses of the configurations that are to be retrieved.

=head1 RETURN VALUE

0  - success
>0 - some error occurred

=head1 EXAMPLES

=head1 FILES

=over 8

 $config    			- the config file that was pulled

=back

=head1 CAVEATS

This program uses Telnet to retrieve configurations.  Telnet sends
passwords and configs (which contain router passwords) in the clear
over the network.  These passwords and configs can be intercepted
by other users (or intruders) on the local network.  If you use this,
be sure you only traverse networks that you own and that you trust
every user and system on those networks.  A better solution would be
 to use an encrypted solution such as SSH or IPSEC.

=head1 BUGS

Yes.

=head1 SEE ALSO

=head1 AUTHOR

George M. Jones <gmj@users.sourceforge.net>

=head1 CREDIT WHERE CREDIT IS DUE

=cut


# History:
#	$Log: snarf.PL,v $
#	Revision 2.11  2002/03/14 16:40:29  gmj
#	* winsnarf mrege
#	
#	Revision 2.10.2.1  2002/03/12 21:17:34  joshwr1ght
#	Edited snarf for use under Windows.  Added --noenable prompt so users can
#	bypass going into enable mode (for those whose accounts already give
#	priv 15 access upon login).  Requested by Denis Figeys.
#	
#	Revision 2.10  2002/03/02 13:53:23  gmj
#	* Added caviat about cleartext passwords/configs over telnet
#	
#	Revision 2.9  2002/03/02 13:44:07  gmj
#	* Added ability to execute multiple commands separated by ";".
#	* do both "sh ver" and "sho run" by default.  Save output of both
#	  in one file.
#	
#	Revision 2.8  2002/03/01 13:19:08  gmj
#	Print version info correctly.
#	
#	Revision 2.7  2002/02/19 16:10:43  gmj
#	Made -V synonymous with --version
#	
#	Revision 2.6  2002/02/05 20:07:44  gmj
#	* In doc, tried to clear up confusion between "configs" and "rules file"
#	* Moved "TO DO"s to TODO.txt or WISHLIST.txt
#	
#	Revision 2.5  2002/02/04 23:11:56  gmj
#	Add RCS Id to --version output
#	
#	Revision 2.4  2002/02/04 17:06:22  jnssf
#	INSTALL.txt: missing directions on ensuring the PATH variable had the PREFIX/bin in it so that rat can find its supporting files
#	
#	Timestamp/skew issues with some files, yet no differences
#	
#	Revision 2.3  2002/02/01 13:15:41  gmj
#	Changed authors email address
#	
#	Revision 2.2  2002/01/29 16:46:23  jnssf
#	Modifications for automatically changing @INC and allowing the
#	NCAT.pm file to be placed in $(PREFIX)/lib for ease of us (non root
#	install)
#	
#	Revision 2.1  2002/01/24 21:52:08  gmj
#	merge back to mainline
#	
#	Revision 2.0.2.2  2002/01/24 21:34:25  gmj
#	* Updated copyright info
#	
#	Revision 2.0.2.1  2001/12/21 17:37:01  gmj
#	Updated to use CIS Terms of Service
#	
#	Revision 2.0  2001/12/21 15:23:36  gmj
#	Level set version to 2.0 prior to branch for Center for Internet Security.
#	
#	Revision 1.4  2001/12/14 11:31:22  gmj
#	* Added --verbose, --cmd and --timeout flags.
#	* --verbose causes output to be sent to STDOUT as well as file.
#	* Added error checking to command execution (i.e. to catch timeouts)
#	
#	Revision 1.3  2001/12/10 15:49:25  gmj
#	Added SNARFUERS environment variable.
#	
#	Revision 1.2  2001/11/26 14:58:04  gmj
#	Fix minor problems with option parsing.
#	
#	Revision 1.1.1.1  2001/11/14 21:40:18  gmj
#	Initial CVS checkin.
#	
#
#	Revision 2.4  2001/11/07 07:27:29  jns
#	minor changes in spacing
#	moved Usage to the end
#
#	Revision 2.3  2001/11/07 02:35:52  jns
#	Missing _ in F_CONFIG
#
#	Revision 2.2  2001/11/07 02:25:38  jns
#	added the IFS, PATH, and SHELL variables
#
#	Revision 2.1  2001/11/07 02:05:22  jns
#	changed to use strict
#	spelling errors corrected
#	switched from a FOO filehandle name to F_CONFIG
#
#	Revision 2.0  2001/11/07 01:43:30  jns
#	branch for jns work
#
#	Revision 1.6  2001/11/03 00:37:50  gjones
#	- Converted to using GetOption.
#	- Cleaned out old cruft.
#


use lib '/usr/lib';

use Net::Telnet::Cisco;
use File::stat;
use Getopt::Long;
use IO::Handle;
use NCAT;
use strict;
use Config;

my ($name,
    $opt_debug,         $opt_dir,
    $opt_enablepw,      $opt_noclobber,
    $opt_user,          $opt_userpw,
    $opt_help,          $opt_version,
    $opt_cmd,		$opt_verbose,
    $opt_timeout,	$opt_noenable,
    $outputs_grabbed,   $enablepw,
    $grabs_attempted,   $home,
    $ok,                $os,
    $router,
    $user,              $userpw,
    $cmd,		$windows
    ) = ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '');

my (@output,@cmds) =
    ();

#test for Windows
if ($Config{'osname'} =~ /MSWin/) {
    $windows=1;
}

# saftey

unless ($windows) {
    $ENV{'PATH'}   = '/bin:/usr/bin:/usr/sbin';
    $ENV{'SHELL'}  = '/bin/sh';
    $ENV{'IFS'}    = '' if (defined $ENV{'IFS'});
}

#default values
my $progname      = $0;
$progname         =~ s,.*/,,;    # only basename left in progname
$progname         =~ s/\.\w*$//; # strip extension if any


$opt_debug        = '';
$opt_noclobber    = 0;
$opt_dir          = './';

#parse command line

Getopt::Long::Configure ("bundling_override");
GetOptions(
	   # Common
	   "debug|d=s",    	\$opt_debug,
	   "help|h", 		\$opt_help,
	   "verbose|v", 	\$opt_verbose,
	   "version|V",	 	\$opt_version,

	   # Snarf specific
	   "user|u=s",		\$opt_user,
	   "userpw|w=s",   	\$opt_userpw,
	   "enablepw|e=s", 	\$opt_enablepw,
	   "timeout|t=i", 	\$opt_timeout,
	   "cmd|c=s", 		\$opt_cmd,
	   "noclobber|b",  	\$opt_noclobber,
	   "dir|i=s",      	\$opt_dir,
           "noenable|n",	\$opt_noenable,
	  )
  or &Usage('');

&Version if $opt_version;
&Usage("") if ($opt_help || @ARGV == 0);

# defaults

$opt_cmd = $opt_cmd eq "" ? "show ver;show run" : $opt_cmd; #default commands
$opt_timeout = $opt_timeout eq "" ? 30 : $opt_timeout; #default timeout

$os              = 'IOS';
$outputs_grabbed = 0;
$grabs_attempted = 0;


# expand out ~ (why dosn't Perl do this ?)

if ($windows) {
    $home = "."
} else {
    $home = $ENV{"HOME"} || $ENV{"LOGDIR"} || (getpwuid($<))[7];
}

$opt_dir =~ s/^~(.*)/$home$1/;


#
# Get usernames and passwords
#
if ($windows) {
    # Get login information
    if ($opt_user) {
        $user = $opt_user;
    } elsif (defined $ENV{'USERNAME'}) {
        $user = $ENV{'USERNAME'};
    } elsif (defined $ENV{'SNARFUSER'}) {
        $user = $ENV{'SNARFUSER'};
    } else {
        print STDERR "Hit Enter if no username is needed.\n";
        print STDERR "Username: ";
        $user = <STDIN>;
        chomp $user;
    }
} else {
    # Unix users
    if ($opt_user ne "") {
        $user = $opt_user;
    } elsif (defined $ENV{"SNARFUSER"}) {
        $user = $ENV{"SNARFUSER"};
    } else {
        print STDERR "Hit Enter if no username is needed.\n";
        print STDERR "Username: ";
        $user = <STDIN>;
        chomp $user;
    }
}

# try command line options first

if ($opt_userpw ne "") {
    $userpw = $opt_userpw;
} elsif (defined $ENV{'USERPW'}) {
    $userpw = $ENV{'USERPW'};
} elsif ($windows) {
    print STDERR "WARNING: Password will be echo'd to screen.\n";
    print STDERR "Password: ";
    $userpw = <STDIN>;
    chomp $userpw;
} else {
    system "stty -echo";
    print STDERR "User Password: ";
    chomp($userpw = <STDIN>);
    print STDERR "\n";
    system "stty echo";
}

# now do the same for the enablepw
unless ($opt_noenable) {
    if ($opt_enablepw ne "") {
        $enablepw = $opt_enablepw;
    } elsif (defined $ENV{'ENABLEPW'}) {
        $enablepw = $ENV{'ENABLEPW'};
    } elsif ($windows) {
        print STDERR "WARNING: Password will be echo'd to screen.\n";
        print STDERR "Enable password: ";
        $enablepw = <STDIN>;
        chomp $enablepw;
    } else {
        system "stty -echo";
        print STDERR "Enable Password: ";
        chomp($enablepw = <STDIN>);
        print STDERR "\n";
        system "stty echo";
    }
}

$enablepw = ($enablepw =~ /^\s*$/) ? $userpw : $enablepw;


#
# Now grab each output
#

foreach $router (@ARGV) {
    if (-s "$opt_dir/$router" and $opt_noclobber) {
	printf STDERR "$progname: $opt_dir/$router already exists. Skipping.";
	next;
    }
    
    print STDERR "router: $router\n" if ($opt_debug =~ /router-snarf/);
    my $cs = Net::Telnet::Cisco->new( Host => $router,
				      Errmode => 'return',		
				      Input_log => 'test.log'
				    );

    unless (defined($cs)) {
	printf STDERR "failed to create handle for $router (down ?)\n";
	next;
    }

    $ok = $cs->login( Name => $user, Password => $userpw,
		      Timeout => $opt_timeout);
 
    unless ($ok) {
	print STDERR "failed to log in to: $router (bad password ?)\n";
	next;
    }

    if ($os =~ /IOS/) {
	# Turn off paging
	my @cmd_output = $cs->cmd('terminal length 0' );

	# Enable

        unless ($opt_noenable) {
            unless( $cs->enable($enablepw) ) {
                warn "Can't enable on $router: " . $cs->errmsg;
                next;
            }
        }

	# Do the commands

	unlink("$opt_dir/$router");

	@cmds = split(/;/,$opt_cmd);

	for $cmd (@cmds) {

	    $grabs_attempted++;

	    print "$router: $cmd\n" if ($opt_verbose);

	    @output = $cs->cmd(String => "$cmd",
			       Timeout => "$opt_timeout");
	    if (@output == 0) {
		warn "Error executing /$cmd/ on $router: " . $cs->errmsg;
		next;
	    } else {
		
		print @output if ($opt_verbose);
		
		# Save output to a file with the router name
		open(F_CONFIG,">>$opt_dir/$router") ||
		    die "Can't open $opt_dir/$router for writing: $!";
		print F_CONFIG @output;
		close(F_CONFIG);
		
		$outputs_grabbed++;
		@output = ();
		
		
	    } # got output
	}
    } # os is IOS

}

if ($grabs_attempted == $outputs_grabbed) {
    exit 0;
} else {
    exit $outputs_grabbed;
}

sub Version {



    print STDERR "This is $progname version ", NCAT::Version, "\n";
    print STDERR '$Id: snarf.PL,v 2.11 2002/03/14 16:40:29 gmj Exp $';
    print STDERR "\n\n";
    print STDERR "Copyright 2002, George M. Jones\n";
    print STDERR "\n";
    print STDERR "This program is free software; you can redistribute it and/or\n";
    print STDERR "modify it under the same terms as Perl itself\n";

    exit(0);
}

sub Usage {
  my($msg) = @_;

  select(STDERR);
  autoflush STDERR 1;

  print STDERR "$progname: $msg\n" unless ($msg eq "");
  print "Usage:\n";
  print "  $progname [options] address [address...]\n";
  print "    -u, --user USERNAME\n";  
  print "    -w, --userpw USER_PW\n";
  print "    -e, --enablepw ENABLE_PW\n";
  print "    -n, --noenable\n";
  print "    -b, --noclobber\n";
  print "    -d, --debug DEBUG_OPTIONS\n";  
  print "    -V, --version\n";  

  exit(1);
}
