#!/bin/sh
#
# Dovecot
#
# Description:  Manages Dovecot as an OCF resource in an high-availability
#               setup.
#
# Author:       Jeroen Koekkoek
# License:      GNU General Public License (GPL)
# Copyright:    (C) 2010 Pagelink B.V.
#
#       This code was inspired by the Postfix resource script written by
#       Raoul Bhatia <r.bhatia@ipax.at>.
#
################################################################################

# Initialization:
doveconf="/usr/bin/doveconf"
dovecot="/usr/sbin/dovecot"

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

: ${OCF_RESKEY_conffile="/etc/dovecot/dovecot.conf"}
USAGE="Usage: $0 {start|stop|reload|status|monitor|validate-all|meta-data}"

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

usage()
{
  echo $USAGE >&2
}

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

<longdesc lang="en">
Resource script for Dovecot. It manages a Dovecot instance as an OCF resource.
</longdesc>
<shortdesc lang="en">Manages a Dovecot instance</shortdesc>

<parameters>

<parameter name="conffile" unique="1" required="0">
<longdesc>
Full pathname of the Dovecot configuration file.
For example, "/etc/dovecot/dovecot.conf"
</longdesc>
<shortdesc>Full pathname of configuration file</shortdesc>
<content type="string" default="/etc/dovecot/dovecot.conf" />
</parameter>

</parameters>

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

cmd_by_pid()
{
  local pid=$1

  if [ "$pid" = "" ]; then
    return 1 # failure
  fi

  local cmd=`ps -p $pid -o ucomm | tail -n 1`

  if [ "$cmd" = "" ]; then
    return 1 # failure
  fi

  echo "$cmd"

  return 0 # success
}

pids_by_ppid()
{
  local pids
  local proc
  local ppid=$1

  if [ "$ppid" = "" ]; then
    return 1 # failure
  fi

  for proc in `ps ax -o pid= -o ppid= | sed 's/^[[:space:]]*\([0-9]*\)[[:space:]]*\([0-9]*\)[[:space:]]*$/\1;\2/'`; do
    local pid=`echo $proc | cut -f 1 -d \;`
    local pid_ppid=`echo $proc | cut -f 2 -d \;`

    if [ "$ppid" = "$pid_ppid" ]; then
      pids="$pids $pid"
    fi
  done

  echo "$pids"

  return 0 # success
}

terminate()
{
  local dead
  local pid=$1
  local signal=$2
  local recheck=$3

  if [ "$recheck" = "" ]; then
    recheck=0
  fi

  kill -$signal $pid >/dev/null 2>&1; dead=$?

  if [ $dead -eq 0 ] && [ $recheck -gt 0 ]; then
    # Grant some time for shutdown and recheck n times.
    local i=0

    while [ $dead -eq 0 ] && [ $i -lt $recheck ]; do
      kill -0 $pid >/dev/null 2>&1; dead=$?
      let "i += 1"

      if [ $dead -eq 0 ]; then
        sleep 1
      fi
    done
  else
    dead=1
  fi

  if [ $dead -ne 0 ]; then
    return 0
  fi

  # The command name can provide valuable information to the administrator so
  # we try our best to be as complete as possible.
  local cmd=`cmd_by_pid $ppid`

  if [ $? -eq 0 ]; then
    ocf_log err "Dovecot child process $cmd with pid $pid failed to stop."
  else
    ocf_log err "Dovecot child process with pid $pid failed to stop."
  fi

  return 1
}

terminate_tree()
{
  local cmd
  local pid
  local ppid=$1
  local signal=$2
  local recheck=$3
  local result=0

  if [ "$ppid" = "" ]; then
    return 1 # failure
  fi

  if [ "$recheck" = "" ]; then
    recheck=0
  fi

  for pid in `pids_by_ppid $ppid`; do
    terminate_tree $pid

    if [ $? -eq 1 ]; then
      # terminate_tree failed to terminate $pid, but it might still be able to
      # terminate other child processes.
      result=1
    fi
  done

  # If we failed to terminate all child processes, the parent process will not
  # be killed, since that would leave processes floating around.

  if [ $result -eq 0 ]; then
    terminate $ppid $signal $recheck
    recheck=$?
  fi

  return $result
}

dovecot_option()
{
  local option=$1
  local value

  if [ "$option" = "" ]; then
    return $OCF_ERR_GENERIC
  fi

  value=`$doveconf -c $OCF_RESKEY_conffile -h $option`

  if [ $? -ne 0 ]; then
    return $OCF_ERR_CONFIGURED
  fi

  echo "$value"
  return $OCF_SUCCESS
}

dovecot_pidfile()
{
  local base_dir=`dovecot_option base_dir`

  if [ $? -eq $OCF_SUCCESS ]; then
    echo "$base_dir/master.pid"
  fi

  return $?
}

dovecot_pid()
{
  local pidfile=`dovecot_pidfile`

  if [ $? -eq $OCF_SUCCESS ] && [ -f $pidfile ]; then
    local pid=`head -n 1 $pidfile`

    if [ -n $pid ]; then
      echo "$pid"
      return $OCF_SUCCESS
    else
      return $OCF_ERR_GENERIC
    fi
  fi

  return $OCF_NOT_RUNNING
}

dovecot_status()
{
  local code
  local pid=`dovecot_pid`

  if [ $? -eq $OCF_SUCCESS ]; then
    kill -0 $pid >/dev/null 2>&1; code=$?

    if [ $code -eq 0 ]; then
      return $OCF_SUCCESS
    else
      return $OCF_NOT_RUNNING
    fi
  fi

  return $?
}

dovecot_start()
{
  dovecot_status

  if [ $? -eq $OCF_SUCCESS ]; then
    ocf_log info "Dovecot already running."
    return $OCF_SUCCESS
  elif [ $? -eq $OCF_ERR_CONFIGURED ]; then
    ocf_log err "Dovecot returned configuration error."
    return $OCF_ERR_CONFIGURED
  fi

  $dovecot -c $OCF_RESKEY_conffile > /dev/null 2>&1

  if [ $? -ne 0 ]; then
    ocf_log err "Dovecot returned error."
    return $OCF_ERR_GENERIC
  fi

  return $OCF_SUCCESS
}

dovecot_stop()
{
  dovecot_status

  if [ $? -eq $OCF_NOT_RUNNING ]; then
    ocf_log info "Dovecot already stopped."
    return $OCF_SUCCESS
  elif [ $? -eq $OCF_ERR_CONFIGURED ]; then
    ocf_log info "Dovecot returned configuration error."
    return $OCF_ERR_CONFIGURED
  fi

  local pid
  local ppid=`dovecot_pid`
  local result

  local kill_master=0
  local remove_pidfile=0

  if [ $? -ne $OCF_SUCCESS ]; then
    return $?
  fi

  terminate $ppid 15 5; result=$?

  if [ $result -ne 0 ]; then
    # Figure out if we need to shutdown all active clients as well. This
    # behaviour is controlled by the shutdown_clients option in the Dovecot
    # configuration file.

    local shutdown_clients=`dovecot_option shutdown_clients`

    if [ $? -ne $OCF_SUCCES ]; then
      return $?
    fi

    echo "$shutdown_clients" | grep -e "[Yy][Ee][Ss]" >/dev/null 2>&1

    if [ $? -eq 0 ]; then
      shutdown_clients="yes"
    else
      shutdown_clients="no"
    fi

    local cmd
    local anvil_pid=-1
    local config_pid=-1
    local log_pid=-1

    if [ "$shutdown_clients" = "yes" ]; then
      result=0

      for pid in `pids_by_ppid $ppid`; do
        cmd=`cmd_by_pid $pid`

        if [ "x$cmd" = "xanvil" ]; then
          anvil_pid=$pid
        elif [ "x$cmd" = "xconfig" ]; then
          config_pid=$pid
        elif [ "x$cmd" = "xlog" ]; then
          log_pid=$pid
        else
          terminate_tree $pid 9 5
          kill_master=1

          $? && result=1
        fi
      done

      # 1st. kill config process
      if [ $result -eq 0 ] && [ $anvil_pid -ne -1 ]; then
        terminate $anvil_pid 9 3; result=$?
        kill_master=1
      fi
      # 2nd. kill anvil process
      if [ $result -eq 0 ] && [ $log_pid -ne -1 ]; then
        terminate $log_pid 9 3; result=$?
        kill_master=1
      fi

    else
      result=0

      for pid in `pids_by_ppid $ppid`; do
        cmd=`cmd_by_pid $pid`

        if [ "x$cmd" = "xconfig" ]; then
          config_pid=$pid
        fi
      done
    fi

    # 3rd. kill log process
    if [ $result -eq 0 ] && [ $config_pid -ne -1 ]; then
      terminate $config_pid 9 3; result=$?
      kill_master=1
    fi

    if [ $kill_master -eq 1 ] && [ $result -eq 0 ]; then
      terminate $ppid 9 5; result=$?
      remove_pidfile=1
    fi
  else
    remove_pidfile=1
  fi

  if [ $remove_pidfile -eq 1 ] && [ $result -eq 0 ]; then
    local pidfile=`dovecot_pidfile`

    if [ $? -eq $OCF_SUCCESS ] && [ -f $pidfile ]; then
      rm -f "$pidfile" >/dev/null 2>&1
    fi

    return $OCF_SUCCESS
  fi

  return $OCF_ERR_GENERIC
}

dovecot_reload()
{
  local result

  dovecot_status; result=$?

  if [ $result -eq $OCF_SUCCESS ]; then
    local pid=`dovecot_pid`; result=$?

    if [ $result -eq $OCF_SUCCESS ]; then
      kill -1 $pid >/dev/null 2>&1; result=$?

      if [ $result -eq 0 ]; then
        return $OCF_SUCCESS
      else
        return $OCF_ERR_GENERIC
      fi
    fi
  fi

  return $?
}

dovecot_monitor()
{
  dovecot_status

  return $?
}

dovecot_validate_all()
{
  local i

  for i in $dovecot $doveconf
  do
    if [ ! -f $i ]
    then
      ocf_log err "Dovecot binary $i does not exist"
      exit $OCF_ERR_INSTALLED
    elif [ ! -x $i ]
    then
      ocf_log err "Dovecot binary $i is not executable"
      exit $OCF_ERR_INSTALLED
    fi
  done

  if [ ! -f $OCF_RESKEY_conffile ]
  then
    ocf_log err "Dovecot configuration file $OCF_RESKEY_conffile does not exist"
    exit $OCF_ERR_CONFIGURED
  fi
}

#
# Main
#

if [ $# -ne 1 ]; then
  usage
  exit $OCF_ERR_ARGS
fi

case $1 in
  meta-data)
    meta_data
    exit $OCF_SUCCESS
    ;;
  usage|help)
    usage
    exit $OCF_SUCCESS
    ;;
esac

dovecot_validate_all
ret=$?

case $1 in
  monitor)
    dovecot_monitor
    exit $?
    ;;
  start)
    dovecot_start
    exit $?
    ;;
  stop)
    dovecot_stop
    exit $?
    ;;
  reload)
    dovecot_reload
    exit $OCF_ERR_GENERIC
    ;;
  status)
    dovecot_status; result=$?

    if [ $result -eq $OCF_SUCCESS ]; then
      ocf_log info "Dovecot is running."
    elif [ $result -eq $OCF_NOT_RUNNING ]; then
      ocf_log info "Dovecot is stopped."
    fi

    exit $result
    ;;
  validate-all)
    exit $OCF_SUCCESS
    ;;
  *)
    usage
    exit $OCF_ERR_UNIMPLEMENTED
    ;;
esac
