#!/usr/bin/perl

###############################################################################
###############################################################################
##
##  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.
##  Copyright (C) 2004-2006 Red Hat, Inc.  All rights reserved.
##  Copyright (C) 2007 Graham Wood.
##
##  Edited from fence_apc to handle the HS500.  The HS500 is a little simple
##  and therefore this code is too.  This works against Firmware I1, with Web
##  firmware I4.  If yours doesn't work, then ideally carry out a login & 
##  reset, logging all the traffic with tcpdump.  (obviously with a changed
##  password).  If that's not possible then please email the content of:
##    http://<ups>/cfg.cgi		(cfg1.cgi on 240V versions)
##    http://<ups>/status.cgi
##    http://<ups>/about.cgi
##  to fence_apc@spam.dragonhold.org - and I will try and update it to cover
##  your version.  In particular my UPS outputs binary zero bytes in nasty 
##  places that break firefox. 
##  
##  This copyrighted material is made available to anyone wishing to use,
##  modify, copy, or redistribute it subject to the terms and conditions
##  of the GNU General Public License v.2.
##
###############################################################################
###############################################################################

use Getopt::Std;
use LWP::Simple;

# Get the program name from $0 and strip directory names
$_=$0;
s/.*\///;
my $pname = $_;

# Change these if the text returned by your equipment is different.
# Test by running script with options -t -v and checking /tmp/apclog

my $debuglog = '/tmp/apclog';# Location of debugging log when in verbose mode
$opt_o = 'Reboot';           # Default fence action.  

my $logged_in = 0;

sub usage 
{
	print "Usage:\n";
	print "\n";
	print "$pname [options]\n";
	print "\n";
	print "Options:\n";
	print "  -a <ip>          IP address or hostname of MasterSwitch\n";
	print "  -h               usage\n";
	print "  -l <name>        Login name\n";
	print "  -n <num>         Outlet number to change: <outlet> \n";
	print "  -o <string>      Action: Reboot (default), Off or On\n";
	print "  -p <string>      Login password\n";
	print "  -V               version\n";
	print "  -v               Log to file /tmp/apclog\n";
	
	exit 0;
}

sub fail
{
	($msg)=@_;
	print $msg."\n" unless defined $opt_q;

	exit 1;
}

sub fail_usage
{
	($msg)=@_;
	print STDERR $msg."\n" if $msg;
	print STDERR "Please use '-h' for usage.\n";
	exit 1;
}

sub version
{
	print "$pname Release 0.1 Alpha - 2007/05/27\n";
	exit 0;
}

sub password_encode
{
	my $password = shift;
	my @encoded;

	foreach my $letter (split(//, $password))
	{
		push @encoded , sprintf("%X", ord($letter));
	}

	return join("-", @encoded);
}

sub login
{
	# Try twice, since if the APC thinks we're logged in already,
	# then it'll log us out.
	foreach my $attempt ( 0, 1)
	{
		print DEBUG "Attempting to login:\n";
		print DEBUG "	Host:     $opt_a\n	Username: $opt_l\n	Password: $opt_p\n	Encoded : " . password_encode($opt_p) . "\n";
		print DEBUG "	http://" . $opt_a . "/2?n=" . $opt_l . "&T=" . password_encode($opt_p) . "\n";

		my $login = get("http://" . $opt_a . "/2?n=" . $opt_l . "&T=" . password_encode($opt_p));

		if (defined($login) && $login =~ /Current Status/)
		{
			# We got a response, and it matched the status page...
			print DEBUG "Login successful.\n";
			return;
		}
	}

	# If we got here, then both attempts failed.
	fail "Failed: Login Failure\n";
}

sub action
{
	# Ports in the HS500 are labelled o, p, and q.
	$change = chr(111+$opt_n);

	# First, find out what the current settings are.
	print DEBUG "Getting current status.\n";
	my $current = get("http://" . $opt_a . "/cfg1.cgi");

	if (!defined($current))
	{
		fail "Could not get current status.\n";
	}

	# Find the current state of each "thing", since we need to send
	# them all back in the update url.
	$current =~ /Checked\000 name=s value=([0-2])/;
	fail "Cannot get current Sensitivity" if !defined $1;
	my $s_status= $1;
	print DEBUG "	Current sensitivity: $s_status\n";

	$current =~ /Checked\000 name=a value=([0-2])/;
	fail "Cannot get current Audible Alarm Setting" if !defined $1;
	my $a_status= $1;
	print DEBUG "	Current audible alarm: $a_status\n";

	$current =~ /above.*Selected\000 value=([0-9]+).*below/;
	fail "Cannot get current Maximum Voltage" if !defined $1;
	my $u_status=$1;
	print DEBUG "	Current Maximum: $u_status\n";

	$current =~ /below.*Selected\000 value=([0-9]+)/;
	fail "Cannot get current Minimum Voltage" if !defined $1;
	my $l_status=$1;
	print DEBUG "	Current Minimum: $l_status\n";

	$current=~ /radio Checked\000 name=o value=([0-9]+)/;
	fail "Cannot get current Port 0 Status" if !defined $1;
	my $o_status=$1;
	print DEBUG "	State of port 0: $o_status\n";

	$current=~ /radio Checked\000 name=p value=([0-9]+)/;
	fail "Cannot get current Port 1 Status" if !defined $1;
	my $p_status=$1;
	print DEBUG "	State of port 1: $p_status\n";

	$current=~ /radio Checked\000 name=q value=([0-9]+)/;
	fail "Cannot get current Port 2 Status" if !defined $1;
	my $q_status=$1;
	print DEBUG "	State of port 2: $q_status\n";
	
	if ($opt_n == 0)
	{
		print DEBUG "	Changing o_status from $o_status to $opt_o\n";
		$o_status=$opt_o;
	}
	elsif ($opt_n == 1)
	{
		print DEBUG "	Changing p_status from $p_status to $opt_o\n";
		$p_status=$opt_o;
	}
	elsif ($opt_n == 2)
	{
		print DEBUG "	Changing q_status from $q_status to $opt_o\n";
		$q_status=$opt_o;
	}
	else 
	{
		fail "Invalid port number $opt_n specified"
	}

	my $url = "http://" . $opt_a . "/3?";
	$url .= "s=" . $s_status;
	$url .= "&a=" . $a_status;
	$url .= "&u=" . $u_status;
	$url .= "&l=" . $l_status;
	$url .= "&o=" . $o_status;
	$url .= "&p=" . $p_status;
	$url .= "&q=" . $q_status;
	$url .= "&S2=Apply";
	print DEBUG "	Updating using: " . $url . "\n";
	my $result = get($url);

	if ($result =~ /0123456789/ )
	{
		# This is a match for the logon screen.
		fail "Update failed.";
	}
}

sub logout
{
	get("http://" . $opt_a . "/logon.cgi");
}

### MAIN #######################################################

my @params = @ARGV;
if ($#ARGV<1)
{
	unlink "/tmp/apclog";
	while (<>)
	{
		$_ =~ s/\s//g;
		my ($param, $value) = split "=";
		next if ($param eq "");
		next if ($param eq "agent");
		   if ($param eq "option") { $opt_o = $value; }
		elsif ($param eq "port")   { $opt_n = $value; }
		elsif ($param eq "ipaddr") { $opt_a = $value; }
		elsif ($param eq "login")  { $opt_l = $value; }
		elsif ($param eq "passwd") { $opt_p = $value; }
		else { fail "Invalid parameters passed via STDIN.\n";}
	}
}
else
{
	getopts("a:hl:n:o:p:qTvV") || fail_usage ;
}
	
usage if defined $opt_h;
version if defined $opt_V;

fail_usage "Unkown parameter." if (@ARGV > 0);

fail_usage "No '-a' flag specified." unless defined $opt_a;
fail_usage "No '-n' flag specified." unless defined $opt_n;
fail_usage "No '-l' flag specified." unless defined $opt_l;
fail_usage "No '-p' flag specified." unless defined $opt_p;
if ($opt_o eq "Off")
{
	$opt_o = 1;
}
elsif ($opt_o eq "On")
{
	$opt_o = 0;
}
elsif ($opt_o eq "Reboot")
{
	$opt_o = 2;
}
else
{
	fail_usage "Unrecognised action '$opt_o' for '-o' flag"
}

#if (defined($opt_v))
#{
	unlink "/tmp/apclog";
	open DEBUG, ">", "/tmp/apclog";
	print DEBUG "Command Line: $_ " . join(" ", @params) . "\n";
	# Since that will not exist if debug is not enabled, we can be lazy
	# and just output to it - it'll just disappear if debug is not set.
#}

&login;

&action;

&logout;

exit 0;


