#!/bin/sh
#
# External STONITH module using IP Power 9258 or compatible devices.
#
# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de)
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like. Any license provided herein, whether implied or
# otherwise, applies only to this software file. Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#

#
# Basic commands & parameters independent from individual device 

DEVICE="IP Power 9258"
IPPowerOn="1"
IPPowerOff="0"
IPGetPower="Set.cmd?CMD=GetPower"
IPSetPower="Set.cmd?CMD=SetPower"
IPPort_name="P"
IPPort0=60
HTTP_COMMAND="wget -q -O - --"
LOG_ERROR="ha_log.sh err"
LOG_WARNING="ha_log.sh warn"
LOG_INFO="ha_log.sh info"
LOG_DEBUG="ha_log.sh debug"
MY_COOKIES="cookies.txt"
MY_TEMPFILE="temp.htm"
PORT_STATUS="iocontrol.htm"
UNDEFINED_HOSTNAME="*not-defined*"

#
# check MY_ROOT_PATH for IP Power 9258 and create it if necessary
#
# in final version:
# MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258"
MY_ROOT_PATH="/var/run/heartbeat/rsctmp/ippower9258"
test -d $MY_ROOT_PATH || { mkdir $MY_ROOT_PATH; }

#
# script functions
#

get_challenge() {
	#
	# device sends a challenge for md5 encryption of username, password and challenge
	send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6
}

get_cookie_from_device(){
	# the form on the login page has these fields:
	# Username, Password, Challenge, Response, ScreenWidth
	#
    challenge=`get_challenge`
	response=`echo -n "$username$password$challenge" | md5sum | cut -b -32`
	postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024"
	send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi"
	if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE
	then
		$LOG_ERROR "Login to device $deviceip failed."
		$LOG_ERROR "Received Challenge = <<<$challenge>>>."
		$LOG_ERROR "Sent postdata = <<<$postdata>>>."
		exit 1
	fi
}

get_data_from_device() {
	# If successful all device info is available in MY_PATH
	rm -f "$MY_PATH/$PORT_STATUS"
	send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS"
	if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS
	then
		$LOG_ERROR "received no port data from $deviceip (Cookie Time Out)"
		exit 1
	fi
}

send_http_request() {
	# ececution of http commands supported by the device
	$HTTP_COMMAND "http://$username:$password@$deviceip/$1"
}

send_web_command(){
	# ececution of web commands through the web-interface
	WEB_COMMAND="wget -q --keep-session-cookies"
	WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES"
	WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES"
	$WEB_COMMAND -O $1 -- $2
}

resolve_name_to_port() {
#
	i=$IPPort0
	port="invalid"
	for h in $hostlist ; do
		if [ $h = $1 ]
		then
			port=$IPPort_name$i
			break
		fi
		i=`expr $i + 1`
	done
	echo $port
}

set_port() {
	#
	# port status is always set. Even if requested status is current status.
	# host status is not considered.
	host=$1
	requested_status=$2 # 0 or 1
	port=`resolve_name_to_port $host`;
	if [ "$port" = "invalid" ]
	then
		$LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip."
		exit 1
	fi
	ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11`
	if [ "$ret" != "$requested_status" ]
	then
		$LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port."
		exit 1
	fi
}

build_hostlist() {
	# 
	# hostnames are available from http://$deviceip/iocontrol.htm"
	# check for number of ports
	#
	#DM# This is a very weak way to calculate the number of available ports.
	# perhaps to make use of w3m -dump:
	# w3m -dump iocontrol.htm  | grep 'Power[1-8]'
	# │Power1│[adam            ]│(*) On ( ) Off│[0    ] Sec (*) On ( ) Off│
	# │Power2│[test            ]│( ) On (*) Off│[0    ] Sec ( ) On (*) Off│
	# │Power3│[                ]│(*) On ( ) Off│[0    ] Sec (*) On ( ) Off│
	# │Power4│[eva             ]│(*) On ( ) Off│[0    ] Sec (*) On ( ) Off│
	hostlist=$(
	w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' |
	sed 's/[^[]*\[//;s/\].*//;s/ *//' |
	while read h; do
		[ -z "$h" ] &&
			echo $UNDEFINED_HOSTNAME ||
			echo $h
	done
	)
	$LOG_DEBUG "Got new hostlist ($hostlist) from $deviceip"
}

prepare_hostlist() {
	#
	# The hostlist is a parameter. If that parameter is defined we do use that list
	# if hostlist is not defiend we try to get a list from the device
	if [ "X$hostlist" = "X" ]
	then
		build_hostlist
	else
		hostlist=`echo $hostlist | tr ',' ' '`
	fi
	if [ "X$hostlist" = "X" ] # This cannot happen. Worstcase we have a concatenation of invalid hostnames 
	then
		$LOG_ERROR "cannot get hostlist for $deviceip"
		exit 1
	fi
}

print_hosts() {
	for h in $hostlist ; do
		[ $h != $UNDEFINED_HOSTNAME ] &&
			echo $h
	done
}

get_http_status() {
	pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]"
	ret=`send_http_request "$IPGetPower" | grep $pattern`
	if [ "X$ret" = "X" ]
	then
		$LOG_ERROR "$DEVICE at $deviceip returns invalid or no string."
		exit 1
	fi
}

# define commands and parameters for the individual device and get device information
case $1 in
gethosts|on|off|reset|status) #stonith always calls status first
#status) #stonith always calls status first and then gethosts|on|off|reset
	#
	# We need environment from stonithd and device information from individual device
	#
	# standard device username is admin. IP Power 9258 does not allow user management.
	# parameter username is optional
	#
	if [ "X$username" = "X" ]
	then
		username="admin"
	fi

	#
	# In case the same device-type is used several times define unique MY_PATH. 
	# In this case the unique device name must be available from http://$deviceip/system.htm
	# in order to get that information we need to login first.
	#
	tmp_path="$deviceip" # ensure a simple unique pathname
	MY_PATH="$MY_ROOT_PATH/$tmp_path"
	test -d $MY_PATH || { mkdir $MY_PATH;  }
	get_cookie_from_device
	get_data_from_device
	prepare_hostlist
	;;
*)
	# Stonithd is asking for meta-data
	;;
esac

# the necessary actions for stonithd
case $1 in
gethosts)
	# copied from external/ssh
	print_hosts
	exit 0
	;;
on)
	host=$2
	set_port $host $IPPowerOn
	exit 0
	;;
off)
	host=$2
	set_port $host $IPPowerOff
	exit 0
	;;
reset)
	host=$2
	set_port $host $IPPowerOff
	sleep 5
	set_port $host $IPPowerOn
	exit 0
	;;
status)
	# werify http command interface
	get_http_status
	exit 0
	;;
getconfignames)
	# return all the config names
	for ipparam in deviceip username password hostlist
	do
		echo $ipparam
	done;
	exit 0
	;;
getinfo-devid)
	echo "IP Power 9258"
	exit 0
	;;
getinfo-devname)
	echo "IP Power 9258 power switch"
	exit 0
	;;
getinfo-devdescr)
	echo "Power switch IP Power 9258 with 4 or 8 power outlets."
	echo "WARNING: It is different from IP Power 9258 HP"
	exit 0
	;;
getinfo-devurl)
	echo "http://www.aviosys.com/manual.htm"
	exit 0
	;;
getinfo-xml)
	cat << IPPOWERXML
<parameters>
<parameter name="deviceip" unique="1" required="1">
<content type="string" />
<shortdesc lang="en">
IP address or hostname of the device.
</shortdesc>
<longdesc lang="en">
The IP Address or the hostname of the device.
</longdesc>
</parameter>

<parameter name="password" unique="0" required="1">
<content type="string" />
<shortdesc lang="en">
Password
</shortdesc>
<longdesc lang="en">
The password to log in with.
</longdesc>
</parameter>

<parameter name="hostlist" unique="1" required="0">
<content type="string" />
<shortdesc lang="en">
Hostlist
</shortdesc>
<longdesc lang="en">
The list of hosts that the device controls.
The list must be in the same order as the power outlets. 
Unused power outlets must also have a name.
If you leave this list empty, we try to get the hostnames from the device.
</longdesc>
</parameter>

<parameter name="username" unique="0" required="0">
<content type="string" default="admin"/>
<shortdesc lang="en">
Account Name
</shortdesc>
<longdesc lang="en">
The user to log in with.
</longdesc>
</parameter>

</parameters>
IPPOWERXML
	exit 0
	;;
*)
	$LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip."
	exit 1;
	;;
esac
