#!/bin/sh
#
# Resource script for Postfix
#
# Description:  Manages Postfix as an OCF resource in
#               an high-availability setup.
#
# Author:       Raoul Bhatia <r.bhatia@ipax.at> : Original Author
# License:      GNU General Public License (GPL)
# Note:         If you want to run multiple Postfix instances, please see
#               http://amd.co.at/adminwiki/Postfix#Adding_a_Second_Postfix_Instance_on_one_Server
#               http://www.postfix.org/postconf.5.html
#
#
#       usage: $0 {start|stop|reload|monitor|validate-all|meta-data}
#
#       The "start" arg starts a Postfix instance
#
#       The "stop" arg stops it.
#
# OCF parameters:
#  OCF_RESKEY_binary
#  OCF_RESKEY_config_dir
#  OCF_RESKEY_parameters
#
##########################################################################

# Initialization:

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

: ${OCF_RESKEY_binary="/usr/sbin/postfix"}
: ${OCF_RESKEY_config_dir=""}
: ${OCF_RESKEY_parameters=""}
USAGE="Usage: $0 {start|stop|reload|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="postfix">
<version>0.1</version>
<longdesc lang="en">
This script manages Postfix as an OCF resource in a high-availability setup.
</longdesc>
<shortdesc lang="en">Manages a highly available Postfix mail server instance</shortdesc>

<parameters>

<parameter name="binary" unique="0" required="0">
<longdesc lang="en">
Full path to the Postfix binary.
For example, "/usr/sbin/postfix".
</longdesc>
<shortdesc lang="en">Full path to Postfix binary</shortdesc>
<content type="string" default="/usr/sbin/postfix" />
</parameter>

<parameter name="config_dir" unique="1" required="0">
<longdesc lang="en">
Full path to a Postfix configuration directory.
For example, "/etc/postfix".
</longdesc>
<shortdesc lang="en">Full path to configuration directory</shortdesc>
<content type="string" default="" />
</parameter>

<parameter name="parameters" unique="0" required="0">
<longdesc lang="en">
The Postfix daemon may be called with additional parameters.
Specify any of them here.
</longdesc>
<shortdesc lang="en"></shortdesc>
<content type="string" default="" />
</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
}

postfix_running() {
    local loglevel
    loglevel=${1:-err}

    # run Postfix status if available
    if ocf_is_true $status_support; then
        $binary $OPTION_CONFIG_DIR status 2>&1
        ret=$?
        if [ $ret -ne 0 ]; then
            ocf_log $loglevel "Postfix status: " $ret
        fi
        return $ret
    fi

    # manually check Postfix's pid
    PIDFILE=${queue_dir}/pid/master.pid
    if [ -f $PIDFILE ]; then
         PID=`head -n 1 $PIDFILE`
         kill -s 0 $PID >/dev/null 2>&1 && [ `ps -p $PID | grep master | wc -l` -eq 1 ]
         return $?
    fi

    # Postfix is not running
    false
}

postfix_start()
{
    # if Postfix is running return success
    if postfix_running info; then
        ocf_log info "Postfix already running."
        return $OCF_SUCCESS
    fi

    # start Postfix
    $binary $OPTIONS start >/dev/null 2>&1
    ret=$?

    if [ $ret -ne 0 ]; then
        ocf_log err "Postfix returned error: " $ret
        return $OCF_ERR_GENERIC
    fi

    # grant some time for startup/forking the sub processes
    # and loop initial monitoring until success or timeout
    while true; do
        sleep 1
        # break if postfix is up and running; log failure otherwise
        postfix_running info && break
        ocf_log info "Postfix failed initial monitor action: " $ret
    done

    ocf_log info "Postfix started."
    return $OCF_SUCCESS
}


postfix_stop()
{
    # if Postfix is not running return success
    if ! postfix_running info; then
        ocf_log info "Postfix already stopped."
        return $OCF_SUCCESS
    fi

    # stop Postfix
    $binary $OPTIONS stop >/dev/null 2>&1
    ret=$?

    if [ $ret -ne 0 ]; then
        ocf_log err "Postfix returned an error while stopping: " $ret
        return $OCF_ERR_GENERIC
    fi

    # grant some time for shutdown and recheck 5 times
    for i in 1 2 3 4 5; do
        if postfix_running info; then
            sleep 1
        else
            break
        fi
    done

    # escalate to abort if we did not stop by now
    # @TODO shall we loop here too?
    if postfix_running info; then
        ocf_log err "Postfix failed to stop. Escalating to 'abort'."

        $binary $OPTIONS abort >/dev/null 2>&1; ret=$?
        sleep 5

        # postfix abort did not succeed
        if postfix_running; then
            ocf_log err "Postfix failed to abort."
            return $OCF_ERR_GENERIC
        fi
    fi

    ocf_log info "Postfix stopped."
    return $OCF_SUCCESS
}

postfix_reload()
{
    if postfix_running; then
        ocf_log info "Reloading Postfix."
        $binary $OPTIONS reload
    fi
}

postfix_monitor()
{
    local status_loglevel="err"

    # Set loglevel to info during probe
    if ocf_is_probe; then
        status_loglevel="info"
    fi

    if postfix_running $status_loglevel; then
        return $OCF_SUCCESS
    fi

    return $OCF_NOT_RUNNING
}

postfix_validate_all()
{
    # check that the Postfix binaries exist and can be executed
    check_binary "$binary"
    check_binary "postconf"

    # if true, run in-depth directory checks
    dir_check=true

    # check config_dir and alternate_config_directories parameter
    if [ "x$config_dir" != "x" ]; then
        if [ ! -d "$config_dir" ]; then
            if ocf_is_probe; then
                ocf_log info "Postfix configuration directory '$config_dir' not readable during probe."
                # skip in-depth directory checks if config file isn't readable during probe
                dir_check=false
            else
                ocf_log err "Postfix configuration directory '$config_dir' does not exist or is not readable."
                return $OCF_ERR_INSTALLED
            fi
        fi

        alternate_config_directories=`postconf -h alternate_config_directories 2>/dev/null | grep "$config_dir/\?"`
        if [ "x$alternate_config_directories" = "x" ]; then
            ocf_log err "Postfix main configuration must contain correct 'alternate_config_directories' parameter."
            return $OCF_ERR_INSTALLED
        fi
    fi

    # check spool/queue and data directories (if applicable)
    # this is required because "postfix check" does not catch all errors
    if ocf_is_true $dir_check; then
        if [ ! -d "$queue_dir" ]; then
            if ocf_is_probe; then
                ocf_log info "Postfix queue directory '$queue_dir' not readable during probe."
            else
                ocf_log err "Postfix queue directory '$queue_dir' does not exist or is not readable."
                return $OCF_ERR_INSTALLED
            fi
        fi

        if ocf_is_true $status_support; then
            data_dir=`postconf $OPTION_CONFIG_DIR -h data_directory 2>/dev/null`
            data_dir_count=`echo "$data_dir" | tr ',' ' ' | wc -w`
            if [ $data_dir_count -gt 1 ]; then
            	ocf_log err "Postfix data directory '$orig_data_dir' cannot be set to multiple directories."
                return $OCF_ERR_INSTALLED
            fi
            if [ ! -d "$data_dir" ]; then
                if ocf_is_probe; then
                    ocf_log info "Postfix data directory '$data_dir' not readable during probe."
                else
                    ocf_log err "Postfix data directory '$data_dir' does not exist or is not readable."
                    return $OCF_ERR_INSTALLED
                fi
            fi
        fi

        # check directory permissions
        if ocf_is_true $status_support; then
            user=`postconf $OPTION_CONFIG_DIR -h mail_owner 2>/dev/null`
            for dir in "$data_dir"; do
                if ! su -s /bin/sh - $user -c "test -w $dir"; then
                    if ocf_is_probe; then
                        ocf_log info "Directory '$dir' is not writable by user '$user' during probe."
                    else
                        ocf_log err "Directory '$dir' is not writable by user '$user'."
                        return $OCF_ERR_PERM;
                    fi
                fi
            done
        fi
    fi

    # run Postfix internal check, if not probing
    if ! ocf_is_probe; then
        $binary $OPTIONS check >/dev/null 2>&1
        ret=$?
        if [ $ret -ne 0 ]; then
            ocf_log err "Postfix 'check' failed: " $ret
            return $OCF_ERR_GENERIC
        fi
    fi

    return $OCF_SUCCESS
}

#
# Main
#

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

binary=$OCF_RESKEY_binary
config_dir=$OCF_RESKEY_config_dir
parameters=$OCF_RESKEY_parameters


# handle parameters
case $1 in
    meta-data)  meta_data
                exit $OCF_SUCCESS
                ;;

    usage|help) usage
                exit $OCF_SUCCESS
                ;;
esac

# build Postfix options string *outside* to access from each method
OPTIONS=''
OPTION_CONFIG_DIR=''

# check for Postfix's postconf binary
check_binary "postconf"

# check if the Postfix config_dir exist
if [ "x$config_dir" != "x" ]; then
    # remove all trailing slashes to ease "postconf alternate_config_directories" match
    config_dir=`echo $config_dir | sed 's/\/*$//'`

    # reset config_dir if it equals Postfix's default config_directory
    postconf -h config_directory 2>/dev/null | grep -q "^$config_dir/\?$"
    if [ $? -eq 0 ]; then
        config_dir=""
    fi

    # set OPTIONS if config_dir is still set
    # save OPTION_CONFIG_DIR seperatly
    if [ "x$config_dir" != "x" ]; then
        OPTION_CONFIG_DIR="-c $config_dir"
        OPTIONS=$OPTION_CONFIG_DIR
   fi
fi

# add all additional parameters to options string
if [ "x$parameters" != "x" ]; then
    OPTIONS="$OPTIONS $parameters"
fi

# important directories, used in different methods
queue_dir=`postconf $OPTION_CONFIG_DIR -h queue_directory 2>/dev/null`

# check Postfix version and status support
status_support=false
postfix_version=`postconf -h mail_version 2>/dev/null`
ocf_version_cmp "$postfix_version" "2.5.0"
ret=$?

# we need Postfix 2.5.0 or greater for status/data_directory support
if [ $ret -eq 1 -o $ret -eq 2 ]; then
    status_support=true
fi


postfix_validate_all
ret=$?

LSB_STATUS_STOPPED=3
if [ $ret -ne $OCF_SUCCESS ]; then
    case $1 in
    stop)       exit $OCF_SUCCESS ;;
    *)          exit $ret;;
    esac
fi

case $1 in
    monitor)    postfix_monitor
                exit $?
                ;;
    start)      postfix_start
                exit $?
                ;;

    stop)       postfix_stop
                exit $?
                ;;

    reload)     postfix_reload
                exit $?
                ;;

    validate-all)   exit $OCF_SUCCESS
                    ;;

    *)          usage
                exit $OCF_ERR_UNIMPLEMENTED
                ;;
esac
