#!/usr/bin/perl -w 
#
# (C) 2010 - David Holland (david.w.holland@gmail.com)

use strict; 

use IO::File; 
use HTTP::Request::Common qw(GET POST); 
use LWP::UserAgent; 

use vars qw( $source $data );
use vars qw( $current_public $current_private $current_snmp );
use vars qw( $new_public $new_private ); 
use vars qw( $checksum_head $checksum_body );
use vars qw( $username $password );
use vars qw( @byte_array $i );

sub prompt($$); 
sub get_data_file($); 
sub get_data_http($$$); 
sub put_data_file($$); 
sub put_data_http($$$$);

if( !defined($ARGV[0]) || $ARGV[0] eq "" ) { 
	$source = prompt("Data Source: ", ""); 
	if( $source eq "" ) { 
		printf("Usage? Must specify a data source, file, or hostname.\n"); 
		exit(0); 
	}
} else { 
	$source = $ARGV[0]; 
} 

if( -f $source ) { 
	printf("Reading Data from File: %s\n", $source); 
	$data = get_data_file($source); 
} else { 
	printf("Obtaining data from Switch: %s\n", $source); 
	$username = prompt("Administrator Username: [ admin ]: ", "admin" ); 
	$password = prompt("Administrator Password: [ admin ]: ", "admin" ); 
	$data = get_data_http($source, $username, $password); 
}

$current_snmp = ord( substr($data, 0x1F0, 1) ); 
$current_public = unpack( "Z16", substr( $data, 0x1F7, 16 )); 
$current_private = unpack( "Z16", substr( $data, 0x20C, 16 )); 
$checksum_body = unpack("n", substr($data, 0x6, 2)); 
$checksum_head = ord( substr($data, 0xD, 1) ); 

printf("Current Body Checksum: %04X\n", $checksum_body); 
printf("Current Header Check Byte: %02X\n", $checksum_head); 

printf("SNMP is currently: "); 
if( $current_snmp == 0 ) { 
	printf("DISABLED - WILL ENABLE\n"); 
} else { 
	printf("ENABLED\n"); 
} 

printf("Both community strings are limited to 15 characters.\n"); 
$new_public = substr( prompt("New read-only community string: [ $current_public ]: ", $current_public ), 0, 15); 
$new_private = substr( prompt("New read-write community string: [ $current_private ]: ", $current_private ), 0, 15); 

# Change things. 
substr( $data, 0x1F0, 1) = chr( 1 ); 
substr( $data, 0x1F7, 16) = pack( "Z16", $new_public ); 
substr( $data, 0x20C, 16) = pack( "Z16", $new_private ); 

# Zero old checksums.
substr( $data, 0x6, 1) = chr( 0 ); 
substr( $data, 0x7, 1) = chr( 0 ); 
substr( $data, 0xD, 1) = chr( 0 ); 

# Body Checksum 
$checksum_body = 0; 
@byte_array = unpack("C*", substr( $data, 16, length($data)-16 ) );
foreach $i (@byte_array) { 
	$checksum_body = $checksum_body + $i; 
}
# Last 4 hex digits
$checksum_body = hex( substr(sprintf("%08X", $checksum_body), -4, 4) ); 

# Insert new checksums, for header checksumming
substr( $data, 0x6, 2 ) = pack("n", $checksum_body ); 

# Header Checksum
$checksum_head = 0; 
@byte_array = unpack("C*", substr( $data, 0, 16 ) ); 
foreach $i (@byte_array) { 
	$checksum_head = $checksum_head + $i; 
} 
$checksum_head = 0x100 - hex( substr(sprintf("%08X", $checksum_head), -2, 2) ); 
substr( $data, 0xD, 1 ) = chr( $checksum_head ); 

printf("New Body Checksum: %04X\n", $checksum_body); 
printf("New Header Check Byte: %02X\n", $checksum_head); 

if( -f $source ) { 
	put_data_file( $data, $source ); 
} else { 
	put_data_http( $data, $source, $username, $password ); 
} 

printf("End - SNMP Should be enabled.\n"); 
exit(0); 

sub prompt($$) { 
	my ($prompt, $default) = @_; 
	my $ans;

	printf("%s", $prompt); 
	$ans = <STDIN>; chomp($ans); 

	if( !defined($ans) || $ans eq "" ) { 
		$ans = $default; 
	} 
	return($ans); 
}

sub get_data_file($) { 
	my ($source) = @_; 
	my $handle; 
	my $data; 
	my $size; 

	$handle = new IO::File( $source ) || die("Open: $!\n"); 
	$size = (stat( $handle ))[7]; 
	$handle->read( $data, $size ); 
	$handle->close(); 
	return($data); 
}

sub put_data_file($$) { 
	my ($data, $filename) = @_; 
	my $handle; 

	$handle = new IO::File(">$filename-snmpenabled.cfg") || die("Unable to write: $!\n"); 
	$handle->write($data); 
	$handle->close(); 

	return; 
}

sub http_login($$$) { 
	my ($hostname, $username, $password ) = @_; 

	my $ua; 
	my @title; 
	my $baseurl;
	my $req; 
	my $res; 

	$baseurl = sprintf("http://%s", $source); 
	$ua = LWP::UserAgent->new; 

	$req = POST "$baseurl/pass", 
		[ username => "$username", password => "$password", submit => "Log In" ]; 

	$res = $ua->request($req); 
	if( !$res->is_success ) { 
		printf("LOGIN POST ERROR: %s\n", $res->status_line); exit(1) 
	}

	# Cheat to figure out if login succeeded (via title), without importing an HTML parser
	@title = split(/title/, $res->content); 
	if( $title[1] =~ /Login/ ) { 
		printf("ERROR: Login Failed.\n"); exit(1); 
	} 
	return($ua); 
}

sub get_data_http($$$) { 
	my ($source, $username, $password) = @_; 

	my $baseurl; 
	my $ua; 
	my $req; 
	my $res; 

	my $data; 

	$baseurl = sprintf("http://%s", $source); 
	$ua = http_login($source, $username, $password); 

	$req = POST "$baseurl/cfgdownload", 
		[ submit => "Proceed" ]; 
	$res = $ua->request($req); 
	if( !$res->is_success ) { 
		printf("CFGDOWNLOAD POST ERROR: %s\n", $res->status_line); exit(1); 
	}

	$data = $res->content; 

	http_logout( $source ); 
	return($data)
}

sub put_data_http($$$$) { 
	my( $data, $hostname, $username, $password) = @_;

	my $baseurl; 
	my $ua; 
	my $req; 
	my $res; 
	my @output; 

	$baseurl = sprintf("http://%s", $hostname); 
	$ua = http_login($hostname, $username, $password); 

	$req = POST "$baseurl/cfgupload", 
		Content_Type => "form-data", 
		Content		=> [ 
			userfile => [ undef, "switch.cfg", Content_Type => "application/octet-stream", Content => $data ]
		]; 
	$res = $ua->request($req); 
	if( !$res->is_success ) { 
		printf("CFGUPLOAD ERROR: %s\n", $res->status_line); exit(1);
	} 

	@output = split(/center/, $res->content); 
	if( $output[1] =~ /Error/ ) { 
		printf("ERROR: Configuration Upload Failed.\n"); exit(1); 
	}
	return; 
}


sub http_logout($) { 
	my( $host ) = @_; 

	my $ua; 
	my $url; 
	my $req; 
	my $res; 

	$ua = LWP::UserAgent->new; 
	$url = sprintf("http://%s/logout?submit=Apply", $host); 
	$req = GET $url; 
	$res = $ua->request($req); 
	if( !$res->is_success ) { 
		printf("HTTP LOGOUT FAILURE: %s\n", $res->status_line); exit(1); 
	} 
	return; 
} 
