#!/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