diff options
Diffstat (limited to 'src/bin/keactrl/keactrl.in')
-rw-r--r-- | src/bin/keactrl/keactrl.in | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/src/bin/keactrl/keactrl.in b/src/bin/keactrl/keactrl.in new file mode 100644 index 0000000..450e997 --- /dev/null +++ b/src/bin/keactrl/keactrl.in @@ -0,0 +1,599 @@ +#!/bin/sh + +# Copyright (C) 2014-2022 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. + +# shellcheck disable=SC2034 +# SC2034: ... appears unused. Verify use (or export if used externally). + +# shellcheck disable=SC2039 +# SC2039: In POSIX sh, 'local' is undefined. + +# shellcheck disable=SC2154 +# SC2154: ... is referenced but not assigned. +# Reason: some variables are taken from keactrl.conf + +# Exit with error if commands exit with non-zero and if undefined variables are +# used. +set -eu + +VERSION="@PACKAGE_VERSION@" + +# Set the have_netconf flag to know if netconf is available. +if test '@HAVE_NETCONF@' = 'yes'; then + have_netconf=true +else + have_netconf=false +fi + +### Logging functions ### + +# Logs message at the error level. +log_error() { + printf "ERROR/keactrl: %s\n" "${1}" +} + +# Logs message at the warning level. +log_warning() { + printf "WARNING/keactrl: %s\n" "${1}" +} + +# Logs message at the info level. +log_info() { + printf "INFO/keactrl: %s\n" "${1}" +} + +### 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 ${member}" + 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 version\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. + +# There are some variables set in /etc/kea/keactrl.conf that's included here. +# Since we run shellcheck against keactrl.in rather than an installed file, +# we get false warnings about the variable being referenced but not assigned. +get_pid_from_file() { + local proc_name="${1}" # Process name. + + local kea_config_file= + case ${proc_name} in + kea-dhcp4) + kea_config_file=${kea_dhcp4_config_file} + ;; + kea-dhcp6) + kea_config_file=${kea_dhcp6_config_file} + ;; + kea-dhcp-ddns) + kea_config_file=${kea_dhcp_ddns_config_file} + ;; + kea-ctrl-agent) + kea_config_file=${kea_ctrl_agent_config_file} + ;; + kea-netconf) + kea_config_file=${kea_netconf_config_file} + ;; + esac + + # Extract the name portion (from last slash to last dot) of the config file name + # File name and extension are documented in src/lib/util/filename.h + local conf_name + conf_name=$(basename -- "${kea_config_file}" | rev | cut -f2- -d'.' | rev) + + # Default the directory to --localstatedir / run + local pid_file_dir + pid_file_dir="@runstatedir@/@PACKAGE@" + + # Use directory override if set (primarily for testing only) + if test -n "${KEA_PIDFILE_DIR+x}"; 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 + _pid=$(cat "${_pid_file}" 2> /dev/null || true) + if test -z "${_pid}"; then + # 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 + if ps -p ${_pid} 1>/dev/null; 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" + else + if ! kill "-${sig}" "${_pid}"; then + log_error "Failed to send signal ${sig} to process ${proc_name}, PID {$_pid}." + 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. + # Extract the name of the binary from the path. + local binary_name + 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 ${*}" + # Start the process. + "${@}" & + 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 + 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}..." + if ! kill "-${sig}" "${_pid}"; then + log_error "Stop failed, could not send signal ${sig} \ +to process ${proc_name}, PID ${_pid}." + 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 + 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}..." + if ! kill "-${sig}" "${_pid}"; then + log_error "Reload failed, could not send signal ${sig} \ +to process ${proc_name}, PID ${_pid}." + fi + fi +} + +# Print Kea daemon version +print_version() { + name=${1} + binary_path=${2} + + if [ -e "${binary_path}" ]; then + if ! ver=$(${binary_path} -v); then + log_error "Error checking version of binary file: ${binary_path}" + fi + else + # No file, means no pid + ver="unknown, ${binary_path} missing"; + fi + + echo "${name}: ${ver}" +} + +### 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 +} + +# 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, ctrl_agent, netconf + local commands="${2}" # Commands to execute + local check_file_cfg="${3}" # Check if server enabled in the configuration file + local is_all=0 # is all servers or a specific one + + # 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 + else + is_all=1 + fi + # Return for for netconf when not available. + if [ "${server}" = "netconf" ]; then + if ! ${have_netconf}; then + return + fi + # reload is not supported for netconf. + if [ "${command}" = "reload" ]; then + if [ "${is_all}" -eq 1 ]; then + return + fi + log_warning "netconf does not support reload" + 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 + file_config=$( eval printf "%s" "\${$server}" ) + # Run the commands if we ignore the configuration setting or if the + # setting is "yes". + if [ "${check_file_cfg}" -eq 0 ] || [ "${file_config}" = "yes" ]; then + ${commands} + 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+x}" ]; then + prefix="@prefix@" + export KEA_LOGGER_DESTINATION="@localstatedir@/log/kea.log" +fi + +command=${1-} +if [ -z "${command}" ]; then + log_error "missing command" + usage + exit 1 +fi + +# Check if this is a simple question about version. +if test "${command}" = "-v" || test "${command}" = "--version" ; then + echo "${VERSION}" + exit 0 +fi + +is_in_list "${command}" "start stop reload status version" +if [ "${_inlist}" -eq 0 ]; then + log_error "invalid command: ${command}" + exit 1 +fi + +# Get the location of the keactrl configuration file. +prefix="@prefix@" +localstatedir="@localstatedir@" +keactrl_conf="@sysconfdir@/@PACKAGE@/keactrl.conf" + +servers="all" + +shift +while test ${#} -gt 0 +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 + server_list="all dhcp4 dhcp6 dhcp_ddns ctrl_agent" + if ${have_netconf}; then + server_list="${server_list} netconf" + fi + is_in_list "${s}" "${server_list}" + 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. +# Shellcheck complaints about not being to follow the source. Let's ingore it. +# shellcheck disable=SC1090 +. "${keactrl_conf}" + +# Get location of the DHCPv4 server binary. +if [ -z "${dhcp4_srv+x}" ]; then + log_error "dhcp4_srv parameter not specified" + exit 1 +fi + +# Get location of the DHCPv6 server binary. +if [ -z "${dhcp6_srv+x}" ]; then + log_error "dhcp6_srv parameter not specified" + exit 1 +fi + +# Get location of the DHCP DDNS server binary. +if [ -z "${dhcp_ddns+x}" ]; then + log_error "dhcp_ddns parameter not specified" + exit 1 +fi + +# Get location of the Control Agent binary. +if [ -z "${ctrl_agent_srv+x}" ]; then + log_error "ctrl_agent_srv parameter not specified" + exit 1 +fi + +# Get location of the Netconf binary. +if ${have_netconf}; then + if [ -z "${netconf_srv+x}" ]; then + log_error "netconf_srv parameter not specified" + exit 1 + fi +fi + +# dhcp4 and dhcp6 (=yes) indicate if we should start DHCPv4 and DHCPv6 server +# respectively. The same is true for ddns, ctrl-agent and netconf. +dhcp4=$( printf "%s" "${dhcp4}" | tr '[:upper:]' '[:lower:]' ) +dhcp6=$( printf "%s" "${dhcp6}" | tr '[:upper:]' '[:lower:]' ) +dhcp_ddns=$( printf "%s" "${dhcp_ddns}" | tr '[:upper:]' '[:lower:]' ) +ctrl_agent=$( printf "%s" "${ctrl_agent}" | tr '[:upper:]' '[:lower:]' ) +if ${have_netconf}; then + netconf=$( printf "%s" "${netconf}" | tr '[:upper:]' '[:lower:]' ) +fi + +case ${command} in + # Start the servers. + start) + args="" + # kea_verbose is set in keactrl.conf that shellcheck is unable to load. + if [ "${kea_verbose}" = "yes" ]; then + 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. + # The variables (dhcp4_srv, dhcp6_serv, dhcp_ddns_srv etc) are set in the + # keactrl.conf file that shellcheck is unable to read. + run_conditional "dhcp4" "start_server ${dhcp4_srv} -c ${kea_dhcp4_config_file} ${args}" 1 + run_conditional "dhcp6" "start_server ${dhcp6_srv} -c ${kea_dhcp6_config_file} ${args}" 1 + run_conditional "dhcp_ddns" "start_server ${dhcp_ddns_srv} -c ${kea_dhcp_ddns_config_file} \ +${args}" 1 + run_conditional "ctrl_agent" "start_server ${ctrl_agent_srv} -c ${kea_ctrl_agent_config_file} \ +${args}" 1 + if ${have_netconf}; then + run_conditional "netconf" "start_server ${netconf_srv} -c ${kea_netconf_config_file} \ +${args}" 1 + fi + + exit 0 ;; + + # Stop running servers. + stop) + # 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 + run_conditional "ctrl_agent" "stop_server ${ctrl_agent_srv}" 0 + if ${have_netconf}; then + run_conditional "netconf" "stop_server ${netconf_srv}" 0 + fi + + exit 0 ;; + + # Reconfigure the servers. + reload) + # 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 + run_conditional "ctrl_agent" "reload_server ${ctrl_agent_srv}" 0 + if ${have_netconf}; then + run_conditional "netconf" "reload_server ${netconf_srv}" 0 + fi + + exit 0 ;; + + status) + if [ -t 1 ]; then + inactive="\033[91minactive\033[0m" + active="\033[92mactive\033[0m" + else + inactive="inactive" + active="active" + fi + + kea4_status=$inactive + # This case of nested double quotes looks confusing, but it is actually + # correct. For details, see this fine explanation: + # https://unix.stackexchange.com/questions/443989/whats-the-right-way-to-quote-command-arg + check_running "$(basename -- "${dhcp4_srv}")" + if [ "${_running}" -eq 1 ]; then + kea4_status=$active + fi + printf "DHCPv4 server: %b\n" "${kea4_status}" + + kea6_status=$inactive + check_running "$(basename -- "${dhcp6_srv}")" + if [ "${_running}" -eq 1 ]; then + kea6_status=$active + fi + printf "DHCPv6 server: %b\n" "${kea6_status}" + + d2_status=$inactive + check_running "$(basename -- "${dhcp_ddns_srv}")" + if [ "${_running}" -eq 1 ]; then + d2_status=$active + fi + printf "DHCP DDNS: %b\n" "${d2_status}" + + agent_status=$inactive + check_running "$(basename -- "${ctrl_agent_srv}")" + if [ "${_running}" -eq 1 ]; then + agent_status=$active + fi + printf "Control Agent: %b\n" "${agent_status}" + + if ${have_netconf}; then + netconf_status=$inactive + check_running "$(basename -- "${netconf_srv}")" + if [ "${_running}" -eq 1 ]; then + netconf_status=$active + fi + printf "Netconf agent: %b\n" "${netconf_status}" + fi + + printf "Kea DHCPv4 configuration file: %s\n" "${kea_dhcp4_config_file}" + printf "Kea DHCPv6 configuration file: %s\n" "${kea_dhcp6_config_file}" + printf "Kea DHCP DDNS configuration file: %s\n" "${kea_dhcp_ddns_config_file}" + printf "Kea Control Agent configuration file: %s\n" "${kea_ctrl_agent_config_file}" + if ${have_netconf}; then + printf "Kea Netconf configuration file: %s\n" "${kea_netconf_config_file}" + fi + printf "keactrl configuration file: %s\n" "${keactrl_conf}" + + check_kea_conf "${kea_dhcp4_config_file}" + check_kea_conf "${kea_dhcp6_config_file}" + check_kea_conf "${kea_dhcp_ddns_config_file}" + check_kea_conf "${kea_ctrl_agent_config_file}" + if ${have_netconf}; then + check_kea_conf "${kea_netconf_config_file}" + fi + + exit 0 ;; + + version) + echo "keactrl: ${VERSION}" + run_conditional "dhcp4" "print_version kea-dhcp4 ${dhcp4_srv}" 0 + run_conditional "dhcp6" "print_version kea-dhcp6 ${dhcp6_srv}" 0 + run_conditional "dhcp_ddns" "print_version kea-dhcp-ddns ${dhcp_ddns_srv}" 0 + run_conditional "ctrl_agent" "print_version kea-ctrl-agent ${ctrl_agent_srv}" 0 + if ${have_netconf}; then + run_conditional "netconf" "print_version kea-netconf ${netconf_srv}" 0 + fi + + exit 0 ;; + + # No other commands are supported. + *) + log_error "Invalid command: ${command}." + exit 1 ;; +esac |