Files
op-packages/luci-app-haproxy-manager/root/usr/libexec/haproxy-manager/common.sh
T

362 lines
8.5 KiB
Bash
Executable File

#!/bin/sh
CONFIG=haproxy_manager
HAPROXY_CFG=/etc/haproxy.cfg
TMP_CFG=/tmp/haproxy-manager-generated.cfg
BACKUP_LIMIT=7
INCIDENT_LIMIT=7
FIREWALL_RULE_NAME='HAProxy Manager: WAN listeners'
FIREWALL_DISABLED_PREFIX='HAProxy Manager disabled: '
uci_get() {
local section="$1"
local option="$2"
local default="${3:-}"
uci -q get "$CONFIG.$section.$option" 2>/dev/null || printf '%s\n' "$default"
}
backup_dir() {
local dir
dir="$(uci_get main backup_dir /root/haproxy-manager-backups)"
is_safe_storage_dir "$dir" || {
echo "Recovery point directory must be a plain path under /root or /mnt: $dir" >&2
return 1
}
printf '%s\n' "$dir"
}
incident_dir() {
local dir
dir="$(uci_get main incident_dir /root/haproxy-manager-incidents)"
is_safe_storage_dir "$dir" || {
echo "Incident directory must be a plain path under /root or /mnt: $dir" >&2
return 1
}
printf '%s\n' "$dir"
}
is_safe_storage_dir() {
case "$1" in
/root/*|/mnt/*) ;;
*) return 1 ;;
esac
case "/${1#/}/" in
*/../*|*/./*|*[!A-Za-z0-9_./-]*) return 1 ;;
esac
return 0
}
resolve_storage_dir() {
local dir="$1" resolved
[ -d "$dir" ] || return 1
resolved="$(cd "$dir" 2>/dev/null && pwd -P)" || return 1
is_safe_storage_dir "$resolved" || return 1
printf '%s\n' "$resolved"
}
is_safe_temp_path() {
local path="$1" name
name="${path#/tmp/}"
[ "$path" = "/tmp/$name" ] || return 1
case "$name" in
haproxy-manager-[A-Za-z0-9._-]*) return 0 ;;
*) return 1 ;;
esac
}
operation_lock_acquire() {
local lock_dir="$1" lock_pid
if mkdir "$lock_dir" 2>/dev/null; then
printf '%s\n' "$$" > "$lock_dir/pid"
return 0
fi
lock_pid="$(cat "$lock_dir/pid" 2>/dev/null || true)"
case "$lock_pid" in
''|*[!0-9]*) lock_pid=0 ;;
esac
if [ "$lock_pid" -gt 1 ] 2>/dev/null && kill -0 "$lock_pid" 2>/dev/null; then
return 1
fi
rm -f "$lock_dir/pid"
rmdir "$lock_dir" 2>/dev/null || return 1
mkdir "$lock_dir" 2>/dev/null || return 1
printf '%s\n' "$$" > "$lock_dir/pid"
}
operation_lock_set_pid() {
printf '%s\n' "$2" > "$1/pid"
}
operation_lock_release() {
rm -f "$1/pid"
rmdir "$1" 2>/dev/null || true
}
atomic_install_file() {
local source="$1" destination="$2" temporary mode
[ -f "$source" ] || return 1
temporary="$destination.haproxy-manager.$$"
rm -f "$temporary"
if ! cp "$source" "$temporary"; then
rm -f "$temporary"
return 1
fi
mode="$(stat -c '%a' "$destination" 2>/dev/null || stat -c '%a' "$source" 2>/dev/null || echo 600)"
chmod "$mode" "$temporary" || { rm -f "$temporary"; return 1; }
mv -f "$temporary" "$destination" || { rm -f "$temporary"; return 1; }
}
active_mode() {
local mode
mode="$(uci_get main active_mode)"
case "$mode" in
generated|raw|none) printf '%s\n' "$mode" ;;
*)
if [ "$(uci_get main enabled 0)" = 1 ] &&
grep -q '^# Generated by luci-app-haproxy-manager\.' "$HAPROXY_CFG" 2>/dev/null; then
printf 'generated\n'
else
printf 'none\n'
fi
;;
esac
}
wan_interface() {
local interface
interface="$(uci_get main wan_interface wan)"
case "$interface" in
''|*[!A-Za-z0-9_.-]*) return 1 ;;
esac
printf '%s\n' "$interface"
}
wan_ip() {
local configured interface
configured="$(uci_get main wan_bind_ip auto)"
if [ "$configured" != auto ] && [ -n "$configured" ]; then
printf '%s\n' "$configured"
return 0
fi
interface="$(wan_interface)" || return 1
ubus call "network.interface.$interface" status 2>/dev/null \
| jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null \
| grep -m1 .
}
lan_ip() {
local configured
configured="$(uci_get main lan_bind_ip auto)"
if [ "$configured" != auto ] && [ -n "$configured" ]; then
printf '%s\n' "$configured"
return 0
fi
ubus call network.interface.lan status 2>/dev/null \
| jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null \
| grep -m1 .
}
route_name() {
printf '%s' "$1" | tr '.:-' '___' | tr -cd 'A-Za-z0-9_'
}
is_valid_host() {
case "$1" in
''|[!A-Za-z0-9]*|*[!A-Za-z0-9]) return 1 ;;
*[!A-Za-z0-9.-]*) return 1 ;;
esac
return 0
}
is_valid_ip_or_host() {
case "$1" in
''|*[!A-Za-z0-9_.:-]*) return 1 ;;
esac
return 0
}
is_valid_backup_id() {
case "$1" in
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) return 0 ;;
*) return 1 ;;
esac
}
backend_address() {
local host="$1"
local port="$2"
case "$host" in
*:*) printf '[%s]:%s\n' "$host" "$port" ;;
*) printf '%s:%s\n' "$host" "$port" ;;
esac
}
is_valid_port() {
local port="$1"
[ "$port" -ge 1 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null
}
route_sections() {
uci -q show "$CONFIG" \
| sed -n "s/^$CONFIG\\.\\(@route\\[[0-9][0-9]*\\]\\)=route/\\1/p"
}
route_kind() {
local section="$1"
local kind
kind="$(uci_get "$section" kind)"
[ -n "$kind" ] || kind="$(uci_get "$section" protocol http)"
printf '%s\n' "$kind"
}
route_port_maps() {
local section="$1"
local kind="$2"
local public_port backend_port
case "$kind" in
ssh)
public_port="$(uci_get "$section" ssh_listen_port 22)"
backend_port="$(uci_get "$section" ssh_backend_port 22)"
printf '%s:%s\n' "$public_port" "$backend_port"
;;
rdp)
public_port="$(uci_get "$section" rdp_listen_port 3389)"
backend_port="$(uci_get "$section" rdp_backend_port 3389)"
printf '%s:%s\n' "$public_port" "$backend_port"
;;
custom)
for map in $(uci_get "$section" port_map); do
printf '%s\n' "$map"
done
;;
tcp)
public_port="$(uci_get "$section" listen_port "$(uci_get "$section" backend_port)")"
backend_port="$(uci_get "$section" backend_port)"
printf '%s:%s\n' "$public_port" "$backend_port"
;;
esac
}
is_valid_port_map() {
local map="$1"
local public_port backend_port
case "$map" in
*:*:*) return 1 ;;
*:*) ;;
*) return 1 ;;
esac
public_port="${map%%:*}"
backend_port="${map#*:}"
is_valid_port "$public_port" && is_valid_port "$backend_port"
}
required_ports() {
local section enabled kind http_enabled https_enabled map
local http_port https_port
http_port="$(uci_get main http_port 80)"
https_port="$(uci_get main https_port 443)"
[ "$(uci_get main enabled 0)" = 1 ] || return 0
for section in $(route_sections); do
enabled="$(uci_get "$section" enabled 1)"
[ "$enabled" = 1 ] || continue
kind="$(route_kind "$section")"
case "$kind" in
web)
http_enabled="$(uci_get "$section" web_http 1)"
https_enabled="$(uci_get "$section" web_https 1)"
[ "$http_enabled" != 1 ] || printf '%s\n' "$http_port"
[ "$https_enabled" != 1 ] || printf '%s\n' "$https_port"
;;
http)
printf '%s\n' "$http_port"
;;
https)
printf '%s\n' "$https_port"
;;
both)
printf '%s\n%s\n' "$http_port" "$https_port"
;;
ssh|rdp|custom|tcp)
for map in $(route_port_maps "$section" "$kind"); do
printf '%s\n' "${map%%:*}"
done
;;
esac
done | sort -nu
}
port_spec_contains() {
local spec="$1"
local wanted="$2"
local token first last
for token in $(printf '%s' "$spec" | tr ',' ' '); do
case "$token" in
*-*)
first="${token%%-*}"
last="${token#*-}"
[ "$wanted" -ge "$first" ] 2>/dev/null && [ "$wanted" -le "$last" ] 2>/dev/null && return 0
;;
*)
[ "$token" = "$wanted" ] && return 0
;;
esac
done
return 1
}
set_uhttpd_list() {
local option values current address
option="$1"
values="$2"
current="$(uci -q get "uhttpd.main.$option" 2>/dev/null || true)"
[ "$current" = "$values" ] && return 0
uci -q delete "uhttpd.main.$option" || true
for address in $values; do
uci add_list "uhttpd.main.$option=$address"
done
UHTTPD_CHANGED=1
}
capture_uhttpd_bindings() {
local saved_http saved_https address
[ "$(uci_get main uhttpd_bind_owned 0)" != 1 ] || return 0
saved_http="$(uci -q get uhttpd.main.listen_http 2>/dev/null || true)"
saved_https="$(uci -q get uhttpd.main.listen_https 2>/dev/null || true)"
uci -q delete "$CONFIG.main.uhttpd_saved_listen_http" || true
uci -q delete "$CONFIG.main.uhttpd_saved_listen_https" || true
for address in $saved_http; do
uci add_list "$CONFIG.main.uhttpd_saved_listen_http=$address"
done
for address in $saved_https; do
uci add_list "$CONFIG.main.uhttpd_saved_listen_https=$address"
done
uci set "$CONFIG.main.uhttpd_bind_owned=1"
uci commit "$CONFIG"
}
restore_uhttpd_bindings() {
[ "$(uci_get main uhttpd_bind_owned 0)" = 1 ] || return 0
set_uhttpd_list listen_http "$(uci_get main uhttpd_saved_listen_http)"
set_uhttpd_list listen_https "$(uci_get main uhttpd_saved_listen_https)"
uci -q delete "$CONFIG.main.uhttpd_saved_listen_http" || true
uci -q delete "$CONFIG.main.uhttpd_saved_listen_https" || true
uci -q delete "$CONFIG.main.uhttpd_bind_owned" || true
uci commit "$CONFIG"
}