#!/usr/bin/perl -w
#
#  NETMAN - An Open Source Network Management System
#  Copyright (C) 2003 Peter Hicks
#
#  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.
#
#  $Source: /home/cvsroot/netman/cisco-config/ios-copy.pl,v $
#
#  Request the configuration from a Cisco IOS device is copied to a
#  TFTP server.
#
#  $Id: ios-copy.pl,v 1.5 2003/11/18 15:08:28 pwh Exp $
#

use strict;
use Netman;
use Getopt::Long;

Netman::print_version();


# Options parsing

my $verbose = 0;
my $debug = 0;
my $hostname;
my $help;

GetOptions('verbose' => \$verbose, 'debug' => \$debug, 'hostname=s' => \$hostname, 'help' => \$help);

if($help) {

print <<ENDHELP;
This script will copy a Cisco IOS device's configuration to a TFTP server
and import the configuration in to the configuration database.

Commands:

 -d, --debug                    Show debugging information for troubleshooting
 -v, --verbose                  Show detailed progress information
     --help                     Display this help screen
     --hostname [host]          Copy configuration for the specified host only

ENDHELP

	exit;

};


# Read in the configuration files

my %config = Netman::read_config();


# Build an SQL query to find the devices from which to copy configurations

my $configcopy_query = "
			SELECT  devices.id AS id,
				devices.hostname AS hostname,
				devices.ip_address AS ip_address,
				devices.checks AS checks

			FROM	devices

			LEFT JOIN sites
			ON	devices.site_id = sites.id

			LEFT JOIN management_systems
			ON	sites.poll_from = management_systems.id

			WHERE   management_systems.hostname = '".$config{'node_config'}{'hostname'}."'
			AND	checks LIKE '%config-copy%'
			AND	devices.enabled = '1'
			";

# If a hostname has been specified on the command line, then append this to
# the SQL query.

if($hostname) {

	$configcopy_query = $configcopy_query . "AND devices.hostname LIKE '%".$hostname."%'";

};


# Sort the data

$configcopy_query = $configcopy_query . "ORDER BY hostname";


my $dbh = Netman::db_connect();

my $db_query = $dbh->prepare($configcopy_query);
my $db_result = $db_query->execute;

if($db_query->rows == 0) {

	print "No targets found.\n\n";
	print "  * Set 'config-copy' as a check for every host for you wish to maintain a copy\n";
	print "    of the configuration.\n\n";
	print "  * Make sure that the devices are configured to be at a location polled from this\n";
	print "    management system\n\n";
	print "  * If a hostname was specified in the command line, is this hostname set to be\n";
	print "    polled from this system?\n";
	print "\n\n";

	exit 0;

} else {

	while(my $row = $db_query->fetchrow_hashref) {

		if($verbose) {

			print "  * Copying from ".$row->{'hostname'}." (".$row->{'ip_address'}.")...\n";

		} else {

			print "  * Copying from ".$row->{'hostname'}."...\n";

		};

		my $device_info = Netman::device_info($row->{'hostname'});

		my $result = get_configuration($device_info->{'hostname'}, $device_info->{'ip_address'}, $device_info->{'passwords'}{'snmpwrite'});

		if($result eq 0) {

			if($verbose) {

				print "  * Copying configuration to SQL database... ";

			};

			my $tftp_filename = $config{'miscellaneous'}{'system_tftproot'}."/configs/".$device_info->{'hostname'};


			# Loop until the file size stops growing

			my $old_tftp_size = 0;
			my $new_tftp_size = -1;

			do {

				sleep 1;

				$old_tftp_size = $new_tftp_size;
				$new_tftp_size = -s $tftp_filename;

				print "      - File is $new_tftp_size bytes\n" if $debug;

			} until($new_tftp_size > $old_tftp_size);

			open(CONFIG, "<".$tftp_filename) || warn "Could not open ".$tftp_filename;

			my $config_text = "";

			my $lines = 0;

			while(<CONFIG>) {

				chomp;

				$config_text = $config_text . $_ . "\n";

				$lines++;

			};

			print "      - Read $lines lines from configuration\n" if $debug;

			my $db_query = $dbh->prepare("

							INSERT INTO	cisco_configs
									(device_id, config_text, updated)

							VALUES('".$device_info->{'id'}."', ".$dbh->quote($config_text).",
								UNIX_TIMESTAMP(NOW())
								)

							");

			$db_query->execute;

			close(CONFIG);

			if($verbose) {

				print "OK\n";

			};

		} elsif($result eq -1) {

			print "  * TFTP copy failed\n";

		};

	};

};


sub get_configuration {

	# Read in variables

	my %config = Netman::read_config();

	my $hostname = shift;
	my $ip_address = shift;
	my $write_community = shift;


	# Connect to the NETMAN datbase

	my $db_handle = Netman::db_connect();



	# Connect to the device using the SNMP read-write community string
	# as defined in the NETMAN database

	if($debug) {

		print "  * Connecting to $hostname ($write_community\@$ip_address)... ";

	} elsif($verbose) {

		print "  * Connecting to $hostname... ";

	};

	my $snmp_handle = new SNMP::Session(DestHost => $ip_address, Community => $write_community);


	# Retreive sysUptime.0 to check that the connection was successful

	my @sysUpTime = [ ['sysUpTime', 0] ];

	my $sysUpTimeValue = $snmp_handle->get(@sysUpTime);

	if(!defined($sysUpTimeValue)) {

		if($debug) {

			print "failed - incorrect community string?\n"; 

		} else {

			print "failed.\n";

		};

		return -1;

	} else {

		print "OK\n" if $verbose;

	};


	# Tell the user what we are doing

	if($verbose) {

		print "  * Copying configuration from device...\n";

	};


	# Create an empty file in the TFTP root directory. The TFTP daemon will
	# not allow us to create files by default - it will only allow us to
	# overwrite files.

	print "      - Creating skeleton TFTP file\n" if $debug;

	system("touch ".$config{'miscellaneous'}{'system_tftproot'}."/configs/".$hostname."; chmod 777 ".$config{'miscellaneous'}{'system_tftproot'}."/configs/".$hostname."");


	

};


sub netman_configcopy_writenet {

	# Attempt to use SNMP writeNet, which is depreciated but still works
	# for some versions of IOS.  This method should be tried last as it
	# isn't possible to tell whether a device supports this method until
	# we try it.  Kinda like pulling the trigger of a gun to see if its
	# loaded.


	# Pull in the SNMP handle

	my $snmp_handle = shift || die "No SNMP handle was passed to netman_configcopy_writenet()\n";


	print "      - Attempting to use SNMP writeNet (legacy)...\n" if $debug;

	my @writeNet = [	['writeNet', '192.168.1.1', 'configs/'.$hostname, 'OCTETSTR']	];

	my $writeNet = $snmp_handle->set(@writeNet);

	my $writeNetErrorNum = $snmp_handle->{ErrorNum};
	my $writeNetErrorString = $snmp_handle->{ErrorStr};

	if($writeNetErrorNum eq 0) {

		if($verbose) {

			print "  * Copy successful\n";

		};

		return 0;

	} else {

		print "      - Failed.\n" if $debug;

	};



};

sub netman_configcopy_catos {

	# Try to copy the device's configuration using the CISCO-STACK-MIB.
	# This method isn't as sey as the CONFIG-COPY-MIB, but we aren't
	# dealing with IOS here!


	# Pull in the SNMP handle

	my $snmp_handle = shift || die "No SNMP handle was passed to netman_configcopy_writenet()\n";


	print "      - Attempting to use SNMP CatOS method...\n" if $debug;

	my @tftpCatOS = [	['tftpHost', '0', '192.168.1.1', 'OCTETSTR'],
				['tftpFile', '0', 'configs/'.$hostname, 'OCTETSTR'],
				['tftpModule', '0', '1', 'INTEGER'],
				['tftpAction', '0', '3', 'INTEGER']
			];

	my $tftpCatOS = $snmp_handle->set(@tftpCatOS);

	my @tftpResult = [	['tftpResult', '0']	];


	print "      - Waiting for completion" if $debug;

	$| = 1;

	sleep 1;


	# Loop until tftpResult is not inProgress(1)

	while($snmp_handle->get(@tftpResult) eq 1) {

		print ".";

		sleep 1;

	};

	print "\n";

	$| = 0;

	my $finaltftpResult = $snmp_handle->get(@tftpResult);

	if($finaltftpResult eq 2) {

		print "      - Success!\n" if $debug;

		return 0;

	} elsif($finaltftpResult eq 3) {

		print "      - Error 3: No response from TFTP server\n" if $debug;

		return -1;

	} elsif($finaltftpResult eq 4) {

		print "      - Error 4: Too many retries\n" if $debug;

		return -1;

	} elsif($finaltftpResult eq 16) {

		print "      - Error 16: Access Violation\n" if $debug;

		return -1;

	};

};

sub netman_configcopy_configcopymib {

	# Try to copy the device's configurating using the CONFIG-COPY-MIB.
	# This method provides the best feedback on completion and errors,
	# and full documentation is available at:
	#
	# http://www.cisco.com/warp/customer/477/SNMP/copy_configs_snmp.shtml


	# Pull in the SNMP handle

	my $snmp_handle = shift || die "No SNMP handle was passed to netman_configcopy_configcopymib()\n";


	print "      - Attempting to use SNMP ccConfigCopy MIB...\n" if $debug;

	my $rowIndex;
	my @rowIndexCheck;
	my $rowIndexCheckValue;
	my $rowIndexCheckError = 0;
	my $rowIndexCheckErrorString = "";
	my $rowIndexCheckErrorCount = 0;



	# Choose a random number for the row index, and check whether this
	# index is already being used (will probably never happen, but best
	# to be safe).  If the row index is being used, retry five times and
	# then assume there's some other problem.

	while($rowIndexCheckError ne 2 && $rowIndexCheckErrorCount lt 5) {

		print "      - Finding a valid row index...\n" if $debug;

		$rowIndex = int(rand(2**24));

		print "      - Trying index $rowIndex...\n" if $debug;

		@rowIndexCheck = [	['ccCopyEntryRowStatus', $rowIndex]	];

		$rowIndexCheckValue = $snmp_handle->get(@rowIndexCheck);

		my $rowIndexCheckError = $snmp_handle->{ErrorNum};
		my $rowIndexCheckErrorString = $snmp_handle->{ErrorStr};

		if($rowIndexCheckError ne 2 && $rowIndexCheckErrorCount lt 5) {

			print STDERR "      - Error '".$rowIndexCheckErrorString."' returned\n" if $debug;

			$rowIndexCheckErrorCount++;

		};

	};


	# Bail out if we've hit five or more errors from above 

	if($rowIndexCheckErrorCount ge 5) {

		print STDERR "  * Too many errors - skipping!\n";

		return -1;

	};


	print "      - Using row index $rowIndex\n" if $debug;


	# Prepare an SNMP VarList of OIDs to set, then set them

	print "      - Preparing SNMP sets\n" if $debug;

	my @copyVarList = [	['ccCopyProtocol', $rowIndex, '1', 'INTEGER'], 
				['ccCopySourceFileType', $rowIndex, '4', 'INTEGER'],
				['ccCopyDestFileType', $rowIndex, '1', 'INTEGER'],
				['ccCopyServerAddress', $rowIndex, '192.168.1.1', 'IPADDR'],
				['ccCopyFileName', $rowIndex, 'configs/'.$hostname, 'OCTETSTR'],
				['ccCopyEntryRowStatus', $rowIndex, '4', 'INTEGER']
			];

	$snmp_handle->set(@copyVarList);


	# Check to see if the SNMP set was successful

	if($snmp_handle->{ErrorStr} ne "") {

		print "      - SNMP set failed - ".$snmp_handle->{ErrorStr}."\n" if $debug;

		return -1;

	};


	# Keep tabs on the ccCopyState, printing a '.' for every 100ms that
	# the row is in the running(2) state

	my @copyState = [	['ccCopyState', $rowIndex]	];
	my @copyFailCause = [	['ccCopyFailCause', $rowIndex]	];

	print "      - Waiting for completion" if $debug;

	$| = 1;

	sleep 1;


	# Loop until ccCopyState is not running(2)

	while($snmp_handle->get(@copyState) eq 2) {

		print "." if $verbose;

		sleep 1;

	};

	print "\n" if $verbose;

	$| = 0;


	# Find out the final copy status and the fail cause

	my $copyFinalStatus = $snmp_handle->get(@copyState);
	my $copyFailCause = $snmp_handle->get(@copyFailCause);


	# Check the final copy status

	if($copyFinalStatus eq 3) {

		return 0;

	} elsif($copyFinalStatus eq 4) {

		if($copyFailCause eq "1") { $copyFailCause = "Unknown error"; };
		if($copyFailCause eq "2") { $copyFailCause = "TFTP server returned an error"; };
		if($copyFailCause eq "3") { $copyFailCause = "TFTP server timeout"; };
		if($copyFailCause eq "4") { $copyFailCause = "Low memory on device"; };
		if($copyFailCause eq "5") { $copyFailCause = "Configuration not found"; };
		if($copyFailCause eq "6") { $copyFailCause = "Unsupported protocol"; };
		if($copyFailCause eq "7") { $copyFailCause = "Configuration application failed"; };

		print "      - Configuration copy was unsuccessful - $copyFailCause\n" if $debug;

		return -1;

	} else {

		print "      - Final status unknown (error code $copyFinalStatus)\n" if $debug;

		return -1;

	};

};
