#!/bin/bash

#
#  Copyright Red Hat Inc., 2002
#
#  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, 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; see the file COPYING.  If not, write to the
#  Free Software Foundation, Inc.,  675 Mass Ave, Cambridge, 
#  MA 02139, USA.
#

# Author: Brian Stevens <bstevens@redhat.com>

#
# Cluster alias start/stop/status utility
#

export PATH=/sbin:/bin:/usr/sbin:/usr/bin

SH_LIB=$(dirname $0)
ALIAS_NETMASK_STR="cluster%alias_netmask"
ALIAS_IP_STR="cluster%alias_ip"
ALIAS_BROADCAST_STR="cluster%alias_broadcast"

LIBRARIES="\
	$SH_LIB/svclib_globals \
	$SH_LIB/svclib_log \
	$SH_LIB/svclib_svcdb \
"

for library in $LIBRARIES
do
	if [ -f $library ]; then
	  . $library
	fi
done

#
# inSameSubnet IPaddr1 IPaddr2 mask
#
# Given two IP addresses and a subnet mask determine if these IP
# addresses are in the same subnet.  If they are, return $YES, if
# not return $NO.  In case of an error, return $FAIL.
#
#
inSameIPsubnet ()
{
	typeset -i n
	typeset -ia mask 
	typeset -ia ip1 ip2		# IP addresses given
	typeset -i quad1 quad2		# calculated quad words

	#
	# Remove '.' characters from dotted decimal notation and save
	# in arrays. i.e.
	#
	#	192.168.1.163 -> array[0] = 192
	#	                 array[1] = 168
	#	                 array[2] = 1
	#	                 array[3] = 163
	#
	let n=0
	for quad in $(echo $1 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  ip1[n]=$quad
	  let n=n+1
	done

	let n=0
	for quad in $(echo $2 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  ip2[n]=$quad
	  let n=n+1
	done

	let n=0
	for quad in $(echo $3 | awk -F. '{print $1 " " $2 " " $3 " " $4}')
	do
	  mask[n]=$quad
	  let n=n+1
	done

	#
	# For each quad word, logically AND the IP address with the subnet
	# mask to get the network/subnet quad word.  If the resulting
	# quad words for both IP addresses are the same they are in the 
	# same IP subnet.
	#
	for n in 0 1 2 3
	do
	  let $((quad1=${ip1[n]} & ${mask[n]}))
	  let $((quad2=${ip2[n]} & ${mask[n]}))

	  if [ $quad1 != $quad2 ]; then
	    return $NO	# in different subnets
	  fi
	done

	return $YES		# in the same subnet, all quad words matched
}

#
# findInterface IPaddr
#
# Given a target IP address find the interface in which this address is
# configured.  If found return $SUCCESS, if not return $NOT_FOUND.  The
# interface name is returned to stdout.
#
findInterface()
{
	typeset line
	typeset intf
	typeset addr
	typeset state

	typeset target=$1

	{
	while read intf line
	do
	  while read line
	  do
	    if [ "$line" = "" ]; then	# go to next interface
	      continue 2
	    fi

	    set - $line

	    addr=
	    while [ $# -gt 0 ]; do
	      case $1 in
	        addr:*)
	          addr=${1##addr:}
	          if [ -n "$addr" -a $addr = $target ]; then
	            echo $intf
	            return $SUCCESS
	          fi
	          ;;
	      esac
	      shift
	    done
	  done
	done
	} < <(ifconfig)

	return $NOT_FOUND
}

#
# findMacAddr IPaddress
#
# Given an interface find the MAC addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findMacAddr()
{
	typeset line
	typeset intf
	typeset addr
	typeset state

	typeset target=$1

	{
	while read intf line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      HWaddr)
	        echo $2	 	# return MAC addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done
	} < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findNetmask IPaddress
#
# Given an interface find the netmask addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findNetmask()
{
	typeset line
	typeset addr
	typeset target=$1

	# 
	# If the user defined a network mask in the config file, use it.
	#
	addr=`clugetconfig $ALIAS_NETMASK_STR`
	case $? in
	  0) echo $addr 	# found it
	     return $SUCCESS
	     ;;
	  *) addr= 		# not found
	     ;;
	esac

	#
	# If no network mask is defined.  Find it from the interface
	# that this ip address is assigned to.
	#
	while read line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      Mask:*)
	        echo ${1##*:}	 	# return netmask addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findBroadcast IPaddress
#
# Given an interface find the broadcast addresses associated with it.
# Return $SUCCESS when found, return $NOT_FOUND if an interface 
# was not found and return $FAIL on error.
#
findBroadcast()
{
	typeset line
	typeset intf
	typeset addr
	typeset state

	typeset target=$1

	# 
	# If the user defined a broadcast address in the config file, use it.
	#
	addr=`clugetconfig $ALIAS_BROADCAST_STR`
	case $? in
	  0) echo $addr 	# found it
	     return $SUCCESS
	     ;;
	  *) addr= 		# not found
	     ;;
	esac

	#
	# If no broadcast address is defined. Find it from the interface
	# that this ip address is assigned to.
	#
	{
	while read line
	do
	  set - $line

	  while [ $# -gt 0 ]; do
	    case $1 in
	      Bcast:*)
	        echo ${1##*:}	 	# return broadcast addr
	        return $SUCCESS 
	        ;;
 	    esac
	    shift
	  done
	done
	} < <(ifconfig $target)

	return $NOT_FOUND
}

#
# findInterfaceInSubnet IPaddr
#
# Given a target IP address find the interface which is configured
# in this subnet and find a free interface name.  When found return
# $SUCCESS and print the interface name to stdout.  If not found return
# $NOT_FOUND, on error return $FAIL.
#
findInterfaceInSubnet ()
{
	typeset line
	typeset intf
	typeset addr
	typeset mask

	typeset target=$1

	while read intf line
	do
	  while read line
	  do
	    if [ "$line" = "" ]; then	# go to next interface
	      continue 2
	    fi

	    set - $line

	    addr=
	    mask=
	    while [ $# -gt 0 ]; do
	      case $1 in
	        addr:*) addr=${1##addr:} ;;
	        Mask:*) mask=${1##Mask:} ;;
	      esac
	      shift
	    done

	    if [ -z "$addr" -o -z "$mask" ]; then
	      continue
	    fi
	  
	    inSameIPsubnet $target $addr $mask
	    if [ $? = $YES ]; then
	      echo $intf
	      return $SUCCESS
	    fi
	  done
	done < <(ifconfig)

	return $NOT_FOUND
}

#
# reserveNextFreeInterface IPaddress
#
# Given an IP address find an interface for it to be configured on.
# First we must find an interface in the same IP subnet as this IP
# address.  Then we look for a free instance of that interface.
# On return, store the name of the interface that can be used in
# argument $2 and return $SUCCESS.  On error or not found return $FAIL.
#
# Note: the caller cannot use the same variable name of a 'typeset'
# variable name in this script.  If he does, scoping will not allow
# the setting to be seen when this function returns.
#
reserveNextFreeInterface()
{
	typeset used_interfaces used_intf
	typeset ipAddr
	typeset tmp_intf
	typeset -i n

	ipAddr=$1
	eval $2='""'		# initialize passed in arg

	tmp_intf=$(findInterfaceInSubnet $ipAddr)
	if [ $? -ne $SUCCESS ]; then
	  logAndPrint $LOG_ERR \
	      "Error: Cannot reserve alias interface for address $ipAddr"
	  return $FAIL
	fi

	used_interfaces=$(ifconfig | \
	                  grep "^${tmp_intf}:[0-9]" | sed 's/[ 	].*//')
	let n=0
	while :
	do
	  for used_intf in $used_interfaces
	  do
	    if [ $used_intf = ${tmp_intf}:$n ]; then
	      let n=n+1
	      continue 2		# its used, try next number
	    fi
	  done
	  break				# found an unused alias interface
	done
  
	#
	# Reserve this interface ($tmp_intf:$n).  
	#
	# Note: The caller should apply the correct netmask and broadcast
	# address and any other options.  This is just a place holder.
	# Also the caller needs to 'ifconfig $tmp_intf down' if something 
	# goes wrong.
	#
	ifconfig ${tmp_intf}:$n $ipAddr broadcast 0.0.0.0 \
	           netmask 255.255.255.255 > /dev/null 2>&1
	eval $2='${tmp_intf}:$n'	# pass back interface in given arg
	return $SUCCESS			# found a free interface
}

#
#  startIP
#
startIP()
{
	typeset mac_addr
	typeset intf
	typeset real_intf
	typeset ret_val
	typeset -i try=0
	typeset -i max_tries=10	# max tries to stop address

	IPaddr=`clugetconfig $ALIAS_IP_STR`
	case $? in
	    0) : ;;		# found it
	    *) return $NOT_FOUND ;;
	esac

	logAndPrint $LOG_INFO "Starting IP address $IPaddr"

	intf=$(findInterface $IPaddr)
	if [ $? -ne $SUCCESS ]; then

	  reserveNextFreeInterface $IPaddr intf
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "Cannot find interface for $IPaddr"
	    return $FAIL
	  fi

	  real_intf=${intf%%:*}

	  netmask_addr=$(findNetmask $real_intf)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "\
Cannot find network mask for interface '$real_intf'"

	    # remove entry added by reserveNextFreeInterface()
	    ifconfig $intf down

	    return $FAIL
	  fi

	  broadcast_addr=$(findBroadcast $real_intf)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_ERR "\
Cannot find broadcast address for interface '$real_intf'"

	    # remove entry added by reserveNextFreeInterface()
	    ifconfig $intf down

	    return $FAIL
	  fi

	  ifconfig_cmd="ifconfig $intf $IPaddr broadcast $broadcast_addr netmask $netmask_addr"
	  logAndPrint $LOG_DEBUG $ifconfig_cmd

	  $ifconfig_cmd 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	    logAndPrint $LOG_ERR "\
Cannot ifconfig $IPaddr to $intf; err=$ret_val"
	    return $FAIL
	  fi
	else
	  real_intf=${intf%%:*}
	fi

	intf=$(findInterface $IPaddr)
	if [ $? -ne $SUCCESS ]; then
	  return $FAIL
	fi

	logAndPrint $LOG_DEBUG "Adding host based route for $IPaddr"

	route add -host $IPaddr dev $intf 2> /dev/null
	ret_val=$?
	if [ $ret_val -ne 0 -a $ret_val -ne 7 ]; then # 7 = already done
	  logAndPrint $LOG_ERR "\
Cannot route add $IPaddr to $intf; err=$ret_val"
	  return $FAIL
	fi

	mac_addr=$(findMacAddr $real_intf)
	if [ $? -ne $SUCCESS ]; then
	  logAndPrint $LOG_ERR "\
Cannot find harware address for interface '$real_intf'"
	  return $FAIL
	fi

	logAndPrint $LOG_DEBUG "Adding ARP entry for $IPaddr ($mac_addr)"

	arp -i $real_intf -s $IPaddr $mac_addr 2> /dev/null
	ret_val=$?
	if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	  logAndPrint $LOG_ERR "\
Cannot add ARP entry for $IPaddr ($mac_addr); err=$ret_val"
	  return $FAIL
	fi

	logAndPrint $LOG_INFO "Sending Gratuitous arp for $IPaddr ($mac_addr)"
	for i in 1 2 3
	do
	  logAndPrint $LOG_DEBUG $GRATUITOUSARP $IPaddr $mac_addr $IPaddr ffffffffffff $real_intf
	  $GRATUITOUSARP $IPaddr $mac_addr $IPaddr ffffffffffff $real_intf
	  ret_val=$?
	  if [ $ret_val -ne 0 ]; then
	    logAndPrint $LOG_ERR "\
Cannot send gratuitous ARP for $IPaddr ($mac_addr); err=$ret_val"
	    return $FAIL
	  fi
	done

	return $SUCCESS
}

#
#  stopIP serviceID
#
# Given a service ID number unconfigure all of its IP addresses.
#
stopIP()
{
	typeset IPaddr
	typeset intf
	typeset ret_val

	  IPaddr=`clugetconfig $ALIAS_IP_STR`
	  case $? in
	    0) : ;;		# found it
	    *) return $NOT_FOUND ;;
	  esac

	  intf=$(findInterface $IPaddr)
	  if [ $? -ne $SUCCESS ]; then
	    logAndPrint $LOG_DEBUG "IP address $IPaddr already removed"
	  else
	    logAndPrint $LOG_INFO "Stopping IP address $IPaddr"

	    logAndPrint $LOG_DEBUG ifconfig $intf down

	    ifconfig $intf down 2> /dev/null
	    ret_val=$?
	    if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	      logAndPrint $LOG_ERR "Cannot ifconfig $intf down"
	      return $FAIL
	    fi
	  fi

	  intf=$(findInterface $IPaddr)
	  if [ $? -ne $NOT_FOUND ]; then
	    logAndPrint $LOG_ERR "Cannot un-configure IP address $IPaddr"
	    return $FAIL
	  fi

	  logAndPrint $LOG_DEBUG "Removing host based route for $IPaddr"

	  route delete -host $IPaddr 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 7 ]; then	# 7 means already done
	    logAndPrint $LOG_ERR "Cannot route delete $IPaddr; err=$ret_val"
	    return $FAIL
	  fi

	  logAndPrint $LOG_DEBUG "Removing ARP entry for $IPaddr"

	  arp -d $IPaddr 2> /dev/null
	  ret_val=$?
	  if [ $ret_val -ne 0 -a $ret_val -ne 255 ]; then # 255 = already done
	    logAndPrint $LOG_ERR "\
Cannot delete ARP entry for $IPaddr; err=$ret_val"
	    return $FAIL
	  fi

	return $SUCCESS
}

statusIP()
{
    return $SUCCESS
}

if [ $# -ne 1 ]; then
    logAndPrint $LOG_ERR "Usage: $0 [start, stop, status]"
    exit 1
fi

case "$1" in
'start')

    logAndPrint $LOG_NOTICE "Starting cluster alias"
    startIP
    if [ $? -ne $SUCCESS -a $ret_val -ne $NOT_FOUND ]; then
	logAndPrint $LOG_ERR "Cannot start cluster alias"
	exit 1
    fi
    exit 0
    ;;

'stop')

    logAndPrint $LOG_NOTICE "Stopping cluster alias"
    stopIP
    if [ $? -ne $SUCCESS -a $ret_val -ne $NOT_FOUND ]; then
	logAndPrint $LOG_ERR "Cannot stop cluster alias"
	exit 1
    fi
    exit 0
    ;;

'status')

    logAndPrint $LOG_DEBUG "Checking cluster alias"

    statusIP
    if [ $? -ne $SUCCESS ]; then
	logAndPrint $LOG_ERR "Check status failed on cluster alias"
	exit 1
    fi
    exit 0
    ;;
esac
