#!/bin/sh

# Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# 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"
}

### Functions managing Kea processes ###
# Constructs a server's PID file based on its binary name, the config file,
# and the --localstatedir and returns the contents as $_pid.   If the file
# does not exist, the value of $_pid is 0.  If the file exists but cannot
# be read the function exists with a error message. Note the PID file name
# is always returned in $_pid_file.
get_pid_from_file() {
    local proc_name=${1} # Process name.

    # Extract the name portion of the config file
    local conf_name=$(basename ${kea_config_file} | cut -f1 -d'.')

    # Default the directory to --localstatedir
    local pid_file_dir=/var/kea

    # Use directory override if set (primarily for testing only)
    if [ ! -z $KEA_PIDFILE_DIR ]; then
        pid_file_dir=${KEA_PIDFILE_DIR}
    fi

    # construct the PID file name
    _pid_file="${pid_file_dir}/${conf_name}.${proc_name}.pid"

    # Grab the PID if the file exists
    if [ -e ${_pid_file} ]; then
        _pid=`cat ${_pid_file}`
        if [ $? -ne 0 ]; then
            log_error "Error reading PID file: ${_pid_file}"
        fi
    else
        # No file, means no pid
        _pid=0;
    fi
}


# Checks if the specified process is running by reading its
# PID file and checking the PID it contains.  If the file does
# not exist, the process is assumed to not be running.
check_running() {
    local proc_name=${1} # Process name.
    # Initially mark the process as not running.
    _running=0

    # Get the PID from the PID file (if it exists)
    get_pid_from_file ${proc_name}
    if [ ${_pid} -gt 0 ]; then
        # Use ps to check if PID is alive
        ps -p ${_pid} 1>/dev/null
        if [ $? -eq 0 ]; then
            # No error, so PID IS ALIVE
            _running=1
        fi
    fi
}

# Sends a signal to a process based on its PID file
send_signal() {
    local sig=${1}        # Signal number
    local proc_name=${2}  # Process name.

    get_pid_from_file ${proc_name}
    if [ ${_pid} -eq 0 ]; then
        log_info "Skip sending signal ${sig} to process ${proc_name}: \
process is not running\n"
    else
        kill -${sig} ${_pid}
        if [ $? -ne 0 ]; then
            log_error "Failed to send signal ${sig} to process ${proc_name}, PID {$_pid}.\n"
        fi
    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 "${binary_name} appears to be running, see: \
PID ${_pid}, PID file: ${_pid_file}."
    else
        log_info "Starting ${binary_name} ${args}"
        # Start the process.
        ${binary_path} ${args} &
    fi
}

# Instruct Kea process to shutdown by sending it signal 15
stop_server() {
    binary_path=${1}   # Full path to the binary.
    local sig=15
    # 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 isn't running, don't start another one. Just log a message.
    if [ ${_running} -eq 0 ]; then
        log_info "${binary_name} isn't running."
    else
        log_info "Stopping ${binary_name}..."
        kill -${sig} ${_pid}
        if [ $? -ne 0 ]; then
            log_error "Stop failed, could not send signal ${sig} \
to process ${proc_name}, PID ${_pid}.\n"
        fi
    fi
}

# Instruct Kea process to reload config by sending it signal 1
reload_server() {
    binary_path=${1}   # Full path to the binary.
    local sig=1
    # 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 isn't running, don't start another one. Just log a message.
    if [ ${_running} -eq 0 ]; then
        log_info "${binary_name} isn't running."
    else
        log_info "Reloading ${binary_name}..."
        kill -${sig} ${_pid}
        if [ $? -ne 0 ]; then
            log_error "Reload failed, could not send signal ${sig} \
to process ${proc_name}, PID ${_pid}.\n"
        fi
    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
}

### Functions testing the existence of the Kea config file

# Check if the Kea configuration file location has been specified in the
# keactrl configuration file. If not, it is a warning or a fatal error.
check_kea_conf() {
    local conf_file=${1} # Kea config file name.
    if [ -z ${conf_file} ]; then
        log_error "Configuration file for Kea not specified."
        exit 1
    elif [ ! -f ${conf_file} ]; then
        log_error "Configuration file for Kea does not exist: ${conf_file}."
        exit 1
    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/local
    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/local
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

# 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)
        check_kea_conf ${kea_config_file}

        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)
        check_kea_conf ${kea_config_file}

        # Stop all servers or servers specified from the command line.
        run_conditional "dhcp4" "stop_server ${dhcp4_srv}" 0
        run_conditional "dhcp6" "stop_server ${dhcp6_srv}" 0
        run_conditional "dhcp_ddns" "stop_server ${dhcp_ddns_srv}" 0

        exit 0 ;;

    # Reconfigure the servers.
    reload)
        check_kea_conf ${kea_config_file}

        # Reconfigure all servers or servers specified from the command line.
        run_conditional "dhcp4" "reload_server ${dhcp4_srv}" 0
        run_conditional "dhcp6" "reload_server ${dhcp6_srv}" 0
        run_conditional "dhcp_ddns" "reload_server ${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}

        check_kea_conf ${kea_config_file}

        exit 0 ;;

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