#!/bin/sh

# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

# This is keactrl script responsible for starting up Kea processes.
# This script is used to run Kea from installation directory,
# as well as for running tests.

### Logging functions ###

# Logs message at the error level.
log_error() {
    printf "ERROR/keactrl: ${1}\n"
}

# Logs message at the warning level.
log_warning() {
    printf "WARNING/keactrl: ${1}\n"
}

# Logs message at the info level.
log_info() {
    printf "INFO/keactrl: ${1}\n"
}

### Convenience functions ###

# Checks if the value is in the list. An example usage of this function
# is to determine whether the keactrl command belongs to the list of
# supported commands.
is_in_list() {
    local member=${1}  # Value to be checked
    local list="${2}"  # Comma separated list of items
    _inlist=0          # Return value: 0 if not in list, 1 otherwise.
    if [ -z ${member} ]; then
        log_error "missing ${class}"
    fi
    # Iterate over all items on the list and compare with the member.
    # If they match, return, otherwise log error and exit.
    for item in ${list}
    do
        if [ ${item} = ${member} ]; then
            _inlist=1
            return
        fi
    done
}

# Prints keactrl usage.
usage() {
    printf "usage is %s command [-c keactrl-config-file] [-s server[,server,..]]\n" $( basename ${0} )
    printf "commands: start stop reload status\n" $( basename ${0} )
}

### Functions managing Kea processes ###

# Returns a list of existing PIDs and a number of PIDs for the process
# having a name specified as an argument to the function.
get_pids() {
    local proc_name=${1}  # Process name.
    # Return the list of PIDs.
    _get_pids=$(ps axwwo pid,command | grep ${proc_name} | grep -v grep \
        | awk '{print $1}')
    # Return the number of PIDs.
    _get_pids_num=$(printf "%s" "${_get_pids}" | wc -w | awk '{print $1}')
}

# Checks if the specified process is running. Internally it calls get_pids
# to get the number of processes.
check_running() {
    local proc_name=${1} # Process name.
    # Initially mark the process as not running.
    _running=0
    get_pids ${proc_name}
    # If the number of pids is non-zero, the process is running.
    if [ ${_get_pids_num} -gt 0 ]; then
        _running=1
    fi
}

# Sends a signal to a group of processes having a specified name.
send_signal() {
    local sig=${1}        # Signal number
    local proc_name=${2}  # Process name.
    # Get all PIDs for the specified process name.
    get_pids ${proc_name}
    # If no processes running, there is no process we can send the signal
    # to. This is not neccessarily an error.
    if [ -z ${_get_pids} ]; then
        log_info "Skip sending signal ${sig} to process ${proc_name}: \
process is not running\n"
        return
    fi
    # Send a signal to all processes.
    kill -${sig} ${_get_pids} >/dev/null 2>&1
    if [ $? -ne 0 ]; then
        log_error "Failed to send signal ${sig} to process ${proc_name}.\n"
    fi
}

# Start the Kea process. Do not start the process if there is an instance
# already running.
start_server() {
    binary_path=${1}   # Full path to the binary.
    binary_args="${2}" # Arguments.
    # Extract the name of the binary from the path.
    local binary_name=$(basename ${binary_path})
    # Use the binary name to check if the process is already running.
    check_running ${binary_name}
    # If process is running, don't start another one. Just log a message.
    if [ ${_running} -ne 0 ]; then
        log_info "Skip starting ${binary_name} \
as another instance is already running."
    else
        log_info "Starting ${binary_name} ${args}"
        # Start the process.
        ${binary_path} ${args} &
    fi
}

# Run the specified command if the server has been enabled.
# In order for the command to run, the following conditions have to be met:
# - server must be on the list of servers (e.g. specified from command line)
#   or servers must contain all
# - if check_file_cfg is non zero, the server must be enabled in the
#   configuration file, so the variable named after server name should exist
#   and be set to yes, e.g. ${dhcp4} should be equal to yes if server name
#   is dhcp4
run_conditional() {
    local server=${1}         # Server name: dhcp4, dhcp6, dhcp_ddns
    local command="${2}"      # Command to execute
    local check_file_cfg=${3} # Check if server enabled in the configuration file

    # If keyword "all" is not on the list of servers we will have to check
    # if our specific server is on the list. If, not return.
    is_in_list "all" "${servers}"
    if [ ${_inlist} -eq 0 ]; then
        is_in_list ${server} "${servers}"
        if [ ${_inlist} -eq 0 ]; then
            return
        fi
    fi

    # Get the configuration value of the keactrl which indicates whether
    # the server should be enabled or not. Variables that hold these values
    # are: ${dhcp4}, ${dhcp6}, ${dhcp_ddns}.
    local file_config=$( eval printf "%s" \${$server} )
    # Run the command if we ignore the configuration setting or if the
    # setting is "yes".
    if [ ${check_file_cfg} -eq 0 ] || [ "${file_config}" = "yes" ]; then
        ${command}
    fi
}

### Script starts here ###

# Configure logger to log messages into the file.
# Do not set destination if the KEA_LOGGER_DESTINATION is set,
# because a unit test could have set this to some other location.
# Note that when the configuration is applied this location may be
# altered and only the handful of initial messages will be logged
# to the default file.
if [ -z ${KEA_LOGGER_DESTINATION} ]; then
    prefix=/usr
    export KEA_LOGGER_DESTINATION=/var/kea/kea.log
fi

command=${1}
if [ -z ${command} ]; then
    log_error "missing command"
    usage
    exit 1
fi
is_in_list "${command}" "start stop reload status"
if [ ${_inlist} -eq 0 ]; then
    log_error "invalid command: ${command}"
    exit 1
fi

# Get the location of the keactrl configuration file.
prefix=/usr
keactrl_conf=/etc/kea/keactrl.conf

servers="all"

shift
while [ ! -z "${1}" ]
do
    option=${1}
    case ${option} in
        # Override keactrl configuration file.
        -c|--ctrl-config)
            shift
            keactrl_conf=${1}
            if [ -z ${keactrl_conf} ]; then
                log_error "keactrl-config-file not specified"
                usage
                exit 1
            fi
            ;;
        # Get the specific servers for which the command will be
        # executed.
        -s|--server)
            shift
            servers=$( printf "%s" ${1} | tr "," "\n" )
            if [ -z "${servers}" ]; then
                log_error "servers not specified"
                usage
                exit 1
            fi
            # Validate that the specified server names are correct.
            for s in ${servers}
            do
                is_in_list "${s}" "all dhcp4 dhcp6 dhcp_ddns"
                if [ ${_inlist} -eq 0 ]; then
                    log_error "invalid server name: ${s}"
                    exit 1
                fi
            done
            ;;
        *)
            log_error "invalid option: ${option}"
            usage
            exit 1
    esac
    shift
done

# Check if the file exists. If it doesn't, it is a fatal error.
if [ ! -f ${keactrl_conf} ]; then
    log_error "keactrl configuration file doesn't exist in ${keactrl_conf}."
    exit 1
fi

# Include the configuration file.
. ${keactrl_conf}

# Get location of the DHCPv4 server binary.
if [ -z ${dhcp4_srv} ]; then
    log_error "dhcp4_srv parameter not specified"
    exit 1
fi

# Get location of the DHCPv6 server binary.
if [ -z ${dhcp6_srv} ]; then
    log_error "dhcp6_srv parameter not specified"
    exit 1
fi

# Get location of the DHCP DDNS server binary.
if [ -z ${dhcp_ddns} ]; then
    log_error "dhcp_ddns parameter not specified"
    exit 1
fi

# Check if the Kea configuration file location has been specified in the
# keactrl configuration file. If not, it is a fatal error.
if [ -z ${kea_config_file} ]; then
    log_error "Configuration file for Kea not specified."
    exit 1
elif [ ! -f ${kea_config_file} ]; then
    log_error "Configuration file for Kea does not exist: ${kea_config_file}."
    exit 1
fi

# dhcp4 and dhcp6 (=yes) indicate if we should start DHCPv4 and DHCPv6 server
# respectively.
dhcp4=$( printf "%s" ${dhcp4} | tr [:upper:] [:lower:] )
dhcp6=$( printf "%s" ${dhcp6} | tr [:upper:] [:lower:] )
dhcp_ddns=$( printf "%s" ${dhcp_ddns} | tr [:upper:] [:lower:] )

case ${command} in
    # Start the servers.
    start)
        args="-c ${kea_config_file}"

        if [ "${kea_verbose}" = "yes" ]; then
            args="${args} -d"
        fi

        # Run servers if they are on the list of servers from the command line
        # and if they are enabled in the keactrl configuration file.
        run_conditional "dhcp4" "start_server ${dhcp4_srv} \"${args}\"" 1
        run_conditional "dhcp6" "start_server ${dhcp6_srv} \"${args}\"" 1
        run_conditional "dhcp_ddns" "start_server ${dhcp_ddns_srv} \"${args}\"" 1

        exit 0 ;;

    # Stop running servers.
    stop)
        # Stop all servers or servers specified from the command line.
        run_conditional "dhcp4" "send_signal 15 $(basename ${dhcp4_srv})" 0
        run_conditional "dhcp6" "send_signal 15 $(basename ${dhcp6_srv})" 0
        run_conditional "dhcp_ddns" "send_signal 15 $(basename ${dhcp_ddns_srv})" 0

        exit 0 ;;

    # Reconfigure the servers.
    reload)
        # Reconfigure all servers or servers specified from the command line.
        run_conditional "dhcp4" "send_signal 1 $(basename ${dhcp4_srv})" 0
        run_conditional "dhcp6" "send_signal 1 $(basename ${dhcp6_srv})" 0
        run_conditional "dhcp_ddns" "send_signal 1 $(basename ${dhcp_ddns_srv})" 0

        exit 0 ;;

    status)
        kea4_status="inactive"
        check_running $(basename ${dhcp4_srv})
        if [ ${_running} -eq 1 ]; then
            kea4_status="active"
        fi
        printf "DHCPv4 server: %s\n" ${kea4_status}

        kea6_status="inactive"
        check_running $(basename ${dhcp6_srv})
        if [ ${_running} -eq 1 ]; then
            kea6_status="active"
        fi
        printf "DHCPv6 server: %s\n" ${kea6_status}

        d2_status="inactive"
        check_running $(basename ${dhcp_ddns_srv})
        if [ ${_running} -eq 1 ]; then
            d2_status="active"
        fi
        printf "DHCP DDNS: %s\n" ${d2_status}
        printf "Kea configuration file: %s\n" ${kea_config_file}
        printf "keactrl configuration file: %s\n" ${keactrl_conf}

        exit 0 ;;

    # No other commands are supported.
    *)
        log_error "Invalid command:  ${command}."
        exit 1 ;;
esac
fi
