#!/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 WARNING" #                                                 please review
LOG_INFO="ha_log.sh message" #                                                    please review
MY_COOKIES="cookies.txt"
MY_TEMPFILE="temp.htm"
DEVICE_SETTINGS="system.htm"
PORT_STATUS="iocontrol.htm"
UNDEFINED_HOSTNAME="*not-defined*"

#
# check MY_ROOT_PATH for IP Power 9258 and create it if necessary
#
MY_ROOT_PATH="/var/run/heartbeat/rsctmp/ippower9258" #                             please review
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"
    ret=`cat ${MY_PATH}/${MY_TEMPFILE} | grep "Invalid User name or Password"`
	if [ "X${ret}" != "X" ]
	then
		$LOG_WARNING "Login to device ${deviceip} failed."
		$LOG_WARNING "Received Challenge = <<<$challenge>>>."
		$LOG_WARNING "Sent postdata = <<<$postdata>>>."
	fi
}

get_data_from_device() {
	# If successful all device info is available in MY_PATH
	rm -f "${MY_PATH}/${PORT_STATUS}"
	rm -f "${MY_PATH}/${DEVICE_SETTINGS}"
	send_web_command "${MY_PATH}/${PORT_STATUS}" "http://${deviceip}/${PORT_STATUS}"
    ret=`cat ${MY_PATH}/${PORT_STATUS} | grep "Cookie Time Out"`
	if [ "X${ret}" != "X" ]
	then
		$LOG_WARNING "Got no port data from ${deviceip}. Reveived: Cookie Time Out"
	fi
	send_web_command "${MY_PATH}/${DEVICE_SETTINGS}" "http://${deviceip}/${DEVICE_SETTINGS}"
	if [ "X${ret}" != "X" ]
	then
		$LOG_WARNING "Got no device info from ${deviceip}. Reveived: Cookie Time Out"
	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=`expr $IPPort0`
	port="invalid"
	for h in $hostlist ; do
		if [ $h = ${1} ]
		then
			port="$IPPort_name"$i
		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
	ret=`cat ${MY_PATH}/${PORT_STATUS} | grep '<!--'`
	if [ "X${ret}" = "X" ]
	then
		portlist="pw1Name pw2Name pw3Name pw4Name pw5Name pw6Name pw7Name pw8Name"
	else # html-code for the ports 5 to 8 is a html-comment
		portlist="pw1Name pw2Name pw3Name pw4Name"
	fi
	hostlist=""
	for h in $portlist ; do
		ret=`cat ${MY_PATH}/${PORT_STATUS} | grep $h | cut -f 4 -d '"'`
		if [ "X${ret}" = "X" ]
		then
			ret=$UNDEFINED_HOSTNAME
		fi
		hostlist="${hostlist} ${ret}"
	done
	$LOG_INFO "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" ]
	then
		$LOG_ERROR "Cant get hostlist for $deviceip."
		exit 1
	fi
}

print_hosts() {
	for h in $hostlist ; do
		if [ $h = $UNDEFINED_HOSTNAME ]
		then
			echo ""
		else
			echo $h
		fi
	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
}

get_device_name() {
	ret=`cat $MY_PATH/$DEVICE_SETTINGS | grep sender | cut -d '"' -f 4`;
	echo $ret
}

# 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.
	#
	if [ "X${devicename}" = "X" ] #                                                         please review
	then
		tmp_path=`echo $deviceip | md5sum | cut -b -32` # 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;
		ret=`get_device_name`;
		if [ "X${ret}" = "X" ]
		then
			$LOG_INFO "devicename is undefined."
		else
			devicename=$ret
			$LOG_INFO "Got new devicename $devicename from $deviceip"
		fi
		# (re-)link new devicename to tmp_path; devicename might exist with wrong data      please review
		if [ "X${devicename}" != "X${tmp_path}" ]
		then
			cur_path=`pwd`
			cd ${MY_ROOT_PATH}
			test -e $devicename || { rm -f $devicename;  }
			test -d $devicename || { rm -fr $devicename;  }
			ln -s  ${tmp_path} ${devicename}
			test -e ${tmp_path}/${tmp_path} || { rm -f ${tmp_path}/${tmp_path}; }
			cd $cur_path
			MY_PATH="${MY_ROOT_PATH}/${devicename}"
		fi
	else
		MY_PATH="${MY_ROOT_PATH}/${devicename}"
		test -d $MY_PATH || { mkdir $MY_PATH;  }
		get_cookie_from_device; 
		get_data_from_device;
	fi
	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 password devicename hostlist username
	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 -e "Power switch IP Power 9258 with 4 or 8 power outlets. \nWARNING: 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="1" required="1">
<content type="string" />
<shortdesc lang="en">
Password
</shortdesc>
<longdesc lang="en">
The password to log in with.
</longdesc>
</parameter>

<parameter name="devicename" unique="1" required="0">
<content type="string" />
<shortdesc lang="en">
Unique name of the device
</shortdesc>
<longdesc lang="en">
Define a unique name for the device.
If you leave this field empty, we try to get the name from the device.
Worst case we will use deviceip to generate a devicename.
</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="1" required="0">
<content type="string" />
<shortdesc lang="en">
Account Name
</shortdesc>
<longdesc lang="en">
The user to log in with. Default username is admin.
</longdesc>
</parameter>

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