#!/bin/sh # Shebang line included so editors and shellcheck/shfmt know this contains # shell code # Common helper functions for NUT server initscript # In recent (relevant) versions of shellcheck busybox is a valid shell type # shellcheck shell=busybox # Pre-requisite sourcing for functions this script uses # * /lib/functions.sh has been sourced # We use explicit return 0 at the end of functions throughout, except where they # would be dead code, for clarity # Preserve current umask in CUR_UMASK, unless a umask was previously captured CUR_UMASK="${CUR_UMASK:-$(umask)}" restore_umask() { umask "$CUR_UMASK" } restore_umask_on_exit() { # Restore umask on exit # shellcheck disable=SC3047 trap restore_umask EXIT } # Check string to contain only characters allowed in an UCI section name check_safe_uci_name() { local val="$1" [ -n "$val" ] || return 1 case "$val" in *[!a-zA-Z0-9_-]*) return 1 ;; esac return 0 } # Check string to contain only characters valid in a RFC822 Date header check_safe_date() { local in_date="$1" [ -n "$in_date" ] || return 1 # Some AI code reviewers get confused by \(\) and \space in the case # pattern, but this is correct for busybox ash in modern OpenWrt # and does not allow \ through the validation. # We want to allow space, comma, and hyphen as they can all appear in an # RFC822 Date: header case "$in_date" in *[!a-zA-Z0-9_:+\(\),\ -]*) return 1 ;; esac return 0 } # Check if input is a valid user or group name check_valid_user_group_name() { local in_name="$1" case "$in_name" in [a-zA-Z_][a-zA-Z0-9_-]*) ;; # Includes the case of an empty string *) return 1 ;; esac return 0 } # Just a check for safe/allowed characters, not a full validation check_safe_hostname_or_ip() { local hostname="$1" case "$hostname" in "" | *[!a-zA-Z0-9.:-\[\]-]*) return 1 ;; esac return 0 } # shellcheck disable=SC2329,SC2317 check_valid_hex_number() { local number="$1" local max_length="$2" [ -n "$max_length" ] || max_length=4 case "$number" in '') # Empty string is not valid return 1 ;; 0x*) case "$number" in 0x[!0-9a-fA-F]*) return 1 ;; 0x[0-9a-fA-F]*) # Minimum of 1 hex digit # Has a max of 4 hex digits (1-max_length hex digits allowed) and min of 1 if [ "${#number}" -gt "$((max_length + 2))" ] || [ "${#number}" -lt 1 ]; then return 1 fi return 0 ;; *) # Redundant, but add clarity for reviewers, especially automated # ones that can get confused without it. return 1 ;; esac ;; *[!0-9a-fA-F]*) return 1 ;; *) # Has a maximum of 4 hex digits (1-max_length hex digits allowed) if [ "${#number}" -gt "$max_length" ]; then return 1 fi return 0 ;; esac # We do not return 0 here as it is unnecessary (all case branches, including # catch-all have a return) and shellcheck will complain if we do. } # also passes on an empty value check_signed_int() { local number="$1" # a minus sign with no number is invalid if [ "$number" = "-" ]; then return 1 fi case "${number#[-]}" in *[!0123456789]*) return 1 ;; *) return 0 ;; esac } # also passes on an empty value check_unsigned_int() { local number="$1" case "$number" in *[!0123456789]*) return 1 ;; *) return 0 ;; esac } # check for valid port, if present # also passes on an empty value check_port() { local port="$1" if [ -n "$port" ]; then case "$port" in *[!0-9]*) return 1 ;; esac # Catches port numbers outside valid range (1-65535) if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then return 1 fi fi return 0 } # Wrap logging, in case we decide to update the logging, everywhere log_msg() { local reason="$1" local current_script="$2" local syslog_id="$3" local level="${4:-notice}" local facility="${5:-daemon}" logger -s -t "$syslog_id" -p "${facility}.${level}" "'$reason' in '$current_script'" || { # We use 'tr' as changing case via variable parameter substitution is not available in ash on OpenWrt # shellcheck disable=SC2018,SC2019 level="$(echo "$level" | tr 'a-z' 'A-Z')" printf "'%s': Logging failed during $level: '%s' in '%s'\n" "$syslog_id" "$reason" "$current_script" >&2 } case "$level" in crit) exit 1 ;; err | error | warn) return 1 ;; *) return 0 ;; esac } log_source_error() { local sourced_script="$1" local current_script="$2" local syslog_id="$3" log_msg "Unable to source '$sourced_script'" "$current_script" "$syslog_id" "crit" } log_config_load_error() { local config="$1" local current_script="$2" local syslog_id="$3" log_msg "Unable to load configuration '$config'" "$current_script" "$syslog_id" "crit" } # Wrap logging of errors, in case we decide to update the logging, everywhere # we do error logging with script exit log_error_exit() { local reason="$1" local current_script="$2" local syslog_id="$3" log_msg "$reason" "$current_script" "$syslog_id" "crit" } # Wrap logging of errors, in case we decide to update the logging, everywhere # we do error logging without hard exit log_error() { local reason="$1" local current_script="$2" local syslog_id="$3" log_msg "$reason" "$current_script" "$syslog_id" "error" } note_section_match() { local section="$1" local wanted="$2" if [ -n "$section" ] && [ "$section" = "$wanted" ]; then have_expected_section="true" fi } have_section_named() { local section_type="$1" local section_name="$2" local ret # 'pseudo-global' to capture result from config_foreach local have_expected_section="false" config_foreach note_section_match "$section_type" "$section_name" ret=$? if [ "$have_expected_section" = "true" ]; then return 0 fi if [ "$ret" = "0" ]; then return 1 else return $ret fi }