#!/bin/sh
#
# Startup/shutdown script for services managed by xinetd.
#
#	Copyright (C) 2003 Charlie Brooks
#
# WARNING:	Tested ONLY on Red Hat 7.3 and Fedora Core 2/4 at this time.
#
# Author:	Charlie Brooks <ha@HBCS.Org>
# Description:	given parameters of a service name and start|stop|status,
#		will enable, disable or report on a specified xinetd service
# Config:	all services must have a descriptor file in /etc/xinetd.d
# Support:	linux-ha@lists.linux-ha.org
# License:	GNU General Public License (GPL)
#
#	  OCF parameters are as below:
#		OCF_RESKEY_service

#######################################################################
# Initialization:

: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat}
. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs

#######################################################################

meta_data() {
	cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="Xinetd">
<version>1.0</version>

<longdesc lang="en">
Resource script for Xinetd. It starts/stops services managed
by xinetd.

Note that the xinetd daemon itself must be running: we are not
going to start it or stop it ourselves.

Important: in case the services managed by the cluster are the
only ones enabled, you should specify the -stayalive option for
xinetd or it will exit on Heartbeat stop. Alternatively, you may
enable some internal service such as echo.
</longdesc>
<shortdesc lang="en">Manages an Xinetd service</shortdesc>

<parameters>
<parameter name="service" unique="0" required="1">
<longdesc lang="en">
The service name managed by xinetd. 
</longdesc>
<shortdesc lang="en">service name</shortdesc>
<content type="string" default="" />
</parameter>
</parameters>

<actions>
<action name="start" timeout="20s" />
<action name="stop" timeout="20s" />
<action name="restart" timeout="20s" />
<action name="status" depth="0" timeout="10" interval="10" />
<action name="monitor" depth="0" timeout="10" interval="10" />
<action name="validate-all" timeout="5" />
<action name="meta-data" timeout="5" />
</actions>
</resource-agent>
END
}

# don't rely on the pid file, but lookup xinetd in the list of
# processes

hup_inetd () {
    pid=`ps -e -o pid,command | grep '/[x]inetd' | awk '{print $1}'`
    if [ "$pid" ]; then
      if kill -HUP $pid; then
          :
      else
          ocf_log err "Could not SigHUP xinetd superdaemon!"
          ocf_log err "perhaps we are booting after a system crash"
          exit $OCF_ERR_GENERIC
      fi
    else
       ocf_log err "xinetd superdaemon process not found!"
       ocf_log err "perhaps we are currently booting the system."
       exit $OCF_ERR_GENERIC
    fi
}

xup_start () {
  if [ "running" = "`xup_status`" ]; then
    ocf_log info "Service $service already started"
    exit $OCF_SUCCESS
  fi

  ocf_log "info" "$0: enabling in $RCFILE"
  if $AWK '!/disable/' $RCFILE > $RCFILE.xup
    then
      if mv $RCFILE.xup $RCFILE
        then
          ocf_log "info" "$0: Starting"
          hup_inetd
          touch $PIDFILE
        else
          ocf_log "err" "Could not replace $RCFILE"
      fi
    else
      ocf_log "err" "Could not rewrite $RCFILE!"
  fi
}

xup_stop () {
  if [ "stopped" = "`xup_status`" ]; then
    ocf_log info "Service $service already stopped"
    exit $OCF_SUCCESS
  fi

  ocf_log "info" "$0: disabling in $RCFILE"
  if $AWK '!/disable/;/{/{printf "\tdisable\t\t\t= yes\n"}' $RCFILE >$RCFILE.xup
    then
      if mv $RCFILE.xup $RCFILE
        then
          ocf_log "info" "$0: Shutting down"
          hup_inetd 
          rm -f $PIDFILE
        else
          ocf_log "err" "Could not replace $RCFILE"
      fi
    else
      ocf_log "err" "Could not rewrite $RCFILE!"
  fi
}

xup_usage () {
        echo "Usage: $0 {start|stop|restart|status|monitor|validate-all|meta-data}"
	return 0
}

xup_status () {
  	if [ -f $PIDFILE ]; then
		echo running
		return $OCF_SUCCESS
	else
		echo stopped
		return $OCF_NOT_RUNNING
	fi
}

#
#	Check if the arg is a valid integer
#
CheckInteger() {
#	Examples of valid integer: "1080", "1", "0080", "0", "0000"
#	Examples of invalid integer: "1080bad", ""
  case "$1" in
    "") #empty string 
	false;;
    *[!0-9]*) #got invalid char
	false;;
    *) #no invalid char, and has at least one digit, so is a good integer
	true;;
  esac
}

#
#	Check if the arg is a valid umask
#
CheckUmask() {
#	Examples of valid umask: "100", "1", "000", "0"
#	Examples of invalid umask: "108", "bad", "1111" ""
  case "$1" in
    [0-7])
	true;;
    [0-7][0-7])
	true;;
    [0-7][0-7][0-7])
	true;;
    *)
	false;;
  esac
}

# Check (part of) the attributes based on xinetd.conf(5) and xinetd source code
#	confparse.c
#	parsers.c
#	attr.h
check_attributes () {
  # Empty these attributes before validating them 
  unset socket_type wait server user protocol port group instances \
	type flags disable nice umask 
  VAR=""
  for SECTION in $*; do
    case $SECTION in
      *=*) # Check to see if we need to export the previous name=value
           # pair.
           if [ -n "$VAR" ]; then
             export "$VAR"
           fi
           # Save the "new" name=value pair in VAR
           VAR=$SECTION
           ;;
      *)   # This section does not have an equal sign, therefore it is part of
           # the *previous* name=value pair, thus we will append it to the
           # previous name=value assignment.
           VAR="$VAR $SECTION"
           ;;
    esac
    # A final cleanup step.
    # Do we need to export VAR ?
    if [ -n "$VAR" ]; then
      export "$VAR"
    fi  
  done
  if [ $? -ne 0 ]; then
	return $OCF_ERR_GENERIC
  fi

  case $disable in
	"")	disable=no # Default to no.
		;;
	yes|no)	;;
	*)	ocf_log err "Invalid value for disable [$disable] in $RCFILE, yes|no please"
		exit $OCF_ERR_CONFIGURED
		;;
  esac

  case $socket_type in
	"") 	socket_type=dgram
		# Default value for socket_type is dgram.
		;;
	stream|dgram|raw|seqpacket)
		;;
	*)	ocf_log err "Invalid socket type $socket_type in $RCFILE"
		exit $OCF_ERR_CONFIGURED
		;;
  esac

  case $wait in # Wait has no default value.
	yes|no)	;;
	*)	ocf_log err "Invalid waid [$wait] in $RCFILE, yes|no please"
		exit $OCF_ERR_CONFIGURED
	    	;;
  esac

  case $type in
  # Default value for type is RPC or INTERNAL, determined at compile time.
  # It may be a list in $RCFILE, however we only capture the first one.
	""|RPC|INTERNAL|UNLISTED|SPECIAL|TCPMUX|TCPMUXPLUS)
		;;
	*)	ocf_log err "Invalid service type [$type] in $RCFILE"
		exit $OCF_ERR_CONFIGURED
		;;
  esac

  if [ ! -z "$type" -a "INTERNAL" != "$type" ]; then
  # Type is explicitly set to EXTERNAL, hence server and user are necessary.
	case $server in
	    "")	ocf_log err "Please specify server in $RCFILE"
		exit $OCF_ERR_CONFIGURED
		;;
	    /*)	if [ -x $server ]; then
			: OK
	 	else
			ocf_log err "Server $server is not executable"
			exit $OCF_ERR_CONFIGURED
		fi
		;;
	    *)	ocf_log err "Server $server is not of obsolute path"
		exit $OCF_ERR_CONFIGURED
		;;
	esac 

	case $user in
	    "")	ocf_log err "Please specify user in $RCFILE"
		exit $OCF_ERR_CONFIGURED
		;;
	    *)	getent passwd $user >/dev/null 2>&1
		if [ $? -ne 0 ]; then
			ocf_log err "User $user does not exist!"
			exit $OCF_ERR_CONFIGURED
		fi
		;;
	esac

	# Protocol is necessary when type is MUX
	if [ $type = "TCPMUX" -o $type ="TCPMUXPLUS" ]; then
	    case $protocol in
		"")	ocf_log err "Please specify protocol in $RCFILE"
			exit $OCF_ERR_CONFIGURED
			;;
		*)	get protocols $protocol >/dev/null 2>&1
			if [ $? -ne 0 ]; then
			    ocf_log err "Invalid protocol [$protocol] in $RCFILE"
			    exit $OCF_ERR_CONFIGURED
			fi
			;;
  	    esac
	fi
  fi

  case $group in
	"")	;; # OK to be empty, the group for $user is used
	 *)	getent group $group >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		ocf_log err "Group $group does not exist!"
		exit $OCF_ERR_CONFIGURED
	fi
	;;
  esac

  case $flags in
  # Default value for flags is REUSE.
  # It may be a list in $RCFILE, however we only capture the first one.
	"")	flags=RESUME
		;;
	REUSE|INTERCEPT|NORETRY|IDONLY|NAMEINARGS|NODELAY|KEEPALIVE|NOLIBWRAP|SENSOR|IPv4|IPv6)	;;
	*)	ocf_log err "Invalid flags [$flags] in $RCFILE"
		exit $OCF_ERR_CONFIGURED
		;;
  esac

  case $instances in
	""|[Uu][Nn][Ll][Ii][Mm][Ii][Ti][Ee][Dd])	;;
	*)	if CheckInteger $instances; then
		    : OK
		else
		    ocf_log err "Invalid instances [$instances] in $RCFILE, non-negative integer expected"
		    exit $OCF_ERR_CONFIGURED
		fi
		;;
  esac

  case $nice in
	"")	;;
	*)	local foo=`echo $nice | sed s/^-//`
		if CheckInteger $foo; then
		    : OK
		else
		    ocf_log err "Invalid nice [$nice] in $RCFILE, integer expected"
		    exit $OCF_ERR_CONFIGURED
		fi
		;;
  esac

  if [ ! -z $umask ]; then
	if CheckUmask $umask; then
	    : OK
	else
	    ocf_log err "Invalid umask [$umask] in $RCFILE"
	    exit $OCF_ERR_CONFIGURED
	fi
  fi
}

xup_validate_all () {
  check_binary $AWK

# Parse the $RCFILE for necessary attributes, assume $RCFILE does not
# contain "include" or "includedir" directives.
  space="[ \t]*"
  leading_space="^$space"
  trailing_space="$space$"
  comment="$space\#"

  # Strip comments, delete blank lines.
  stripped=`sed -e "/^$comment/d" -e "/=$trailing_space/d" -e "/$leading_space$/d" $RCFILE`
 
  stripped=`echo $stripped`
  # At this stage, stripped="service <service-name> { attribute-list }".
  case $stripped in
	*#*)
	ocf_log err "Descriptor file $RCFILE contains extra \"#\", which is forbidden"
	exit $OCF_ERR_CONFIGURED
	;;

	service*)
	case $stripped in
	    *[!\	\ +-]=*)
		ocf_log err "Attribute needs a space before operator ="
		exit $OCF_ERR_CONFIGURED
		;;
	    *[!\	\ ][+-]=*)
		ocf_log err "Attribute needs a space before operator [+-]="
		exit $OCF_ERR_CONFIGURED
		;;
	    *)	;;
	esac

	# Strip leading "service" keyword, remove spaces surrounding "=".
	stripped=`echo $stripped | sed -e "s/^service//" -e "s/-=/ /g" \
			-e "s/+=/=/g" -e "s/$space=$space/=/g"`
	# At this stage, stripped=" <service-name> { attribute-list }",
	# note that the leading space before <service-name> is significant.
	case $stripped in
	    " "*)
	    stripped=`echo $stripped | sed -e "s/$leading_space[^ \t]\+$space//"`
	    # At this stage, stripped="{ attribute-list }"
	    case $stripped in
		{*})
		stripped=`echo $stripped | sed -e "s/^{//" -e "s/}$//"`
		# At this stage, stripped=<attribute-list>
#  		    echo $stripped
		check_attributes "$stripped"
		;;

		*)
		ocf_log err "Attrbute list should be contained in {}"
		exit $OCF_ERR_CONFIGURED
		;;
	    esac
	    ;;

	    *)
	    ocf_log err "Service name should follow the service key word"
	    exit $OCF_ERR_CONFIGURED
	    ;;
	esac
	;;

	*)
	ocf_log err "Service key word is necessary in $RCFILE"
	exit $OCF_ERR_CONFIGURED
	;;
  esac
}
if
  ( [ $# -ne 1 ] )
then
  xup_usage
  exit $OCF_ERR_ARGS
fi

# These operations do not require OCF instance parameters to be set
case "$1" in
  meta-data)
	meta_data
	exit $OCF_SUCCESS
	;;
  usage)
	xup_usage
	exit $OCF_SUCCESS
	;;
  *)	;;
esac

if 
  [ -z "$OCF_RESKEY_service" ]
then
  ocf_log err "Please set OCF_RESKEY_service to the service managed by Xinetd"
  if [ "$1" = "start" ]; then 
      exit $OCF_ERR_ARGS
  else
      exit $OCF_NOT_RUNNING
  fi
fi

service=$OCF_RESKEY_service
RCFILE=/etc/xinetd.d/$service
PIDFILE=$HA_VARRUN/xup$service

# Make sure the OCF_RESKEY_service is a valid xinetd service name
if [ ! -f $RCFILE ]; then
    ocf_log err "Service descriptor $RCFILE not found!"
    if [ "$1" = "start" ]; then 
	exit $OCF_ERR_ARGS
    else
	exit $OCF_NOT_RUNNING
    fi
fi

# See how we were called.
case "$1" in
  start)
	xup_start
	;;
  stop)
	xup_stop
	;;
  restart)
	$0 stop
	$0 start
	;;
  status)
	xup_status
	;;
  monitor)
	xup_status > /dev/null
	;;
  validate-all)
	xup_validate_all
	;;
  *)
	xup_usage
	exit $OCF_ERR_UNIMPLEMENTED
esac

exit $?
