257 lines
6.7 KiB
Bash
257 lines
6.7 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# - Set firewall command (either iptables or ip6tables)
|
|
#
|
|
if [[ -x "${ip6t}" ]] ; then
|
|
fw_command="${ip6t}"
|
|
elif [[ -x "${ipt}" ]] ; then
|
|
fw_command="${ipt}"
|
|
fi
|
|
|
|
# -------------
|
|
# --- Some functions
|
|
# -------------
|
|
|
|
echononl(){
|
|
echo X\\c > /tmp/shprompt$$
|
|
if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then
|
|
echo -e -n "$*\\c" 1>&2
|
|
else
|
|
echo -e -n "$*" 1>&2
|
|
fi
|
|
rm /tmp/shprompt$$
|
|
}
|
|
echo_done() {
|
|
echo -e "\033[75G[ \033[32mdone\033[m ]"
|
|
}
|
|
echo_ok() {
|
|
echo -e "\033[75G[ \033[32mok\033[m ]"
|
|
}
|
|
echo_warning() {
|
|
echo -e "\033[75G[ \033[33m\033[1mwarn\033[m ]"
|
|
}
|
|
echo_failed(){
|
|
echo -e "\033[75G[ \033[1;31mfailed\033[m ]"
|
|
}
|
|
echo_skipped() {
|
|
echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]"
|
|
}
|
|
|
|
|
|
fatal (){
|
|
echo ""
|
|
echo -e "fatal Error: $*"
|
|
echo ""
|
|
echo -e "\t\033[31m\033[1mScript will be interrupted..\033[m\033[m"
|
|
echo ""
|
|
exit 1
|
|
}
|
|
|
|
error(){
|
|
echo ""
|
|
echo -e "\t[ \033[31m\033[1mFehler\033[m ]: $*"
|
|
echo ""
|
|
}
|
|
|
|
warn (){
|
|
echo ""
|
|
echo -e "\t[ \033[33m\033[1mWarning\033[m ]: $*"
|
|
echo ""
|
|
}
|
|
|
|
info (){
|
|
echo ""
|
|
echo -e "\t[ \033[32m\033[1mInfo\033[m ]: $*"
|
|
echo ""
|
|
}
|
|
|
|
## - Check if a given array (parameter 2) contains a given string (parameter 1)
|
|
## -
|
|
containsElement () {
|
|
local e
|
|
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
|
|
return 1
|
|
}
|
|
|
|
is_number() {
|
|
|
|
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
|
|
|
# - also possible
|
|
# -
|
|
#[[ ! -z "${1##*[!0-9]*}" ]] && return 0 || return 1
|
|
#return $([[ ! -z "${1##*[!0-9]*}" ]])
|
|
}
|
|
|
|
trim() {
|
|
local var="$*"
|
|
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
|
|
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
|
|
echo -n "$var"
|
|
}
|
|
|
|
|
|
is_container() {
|
|
command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt --container >/dev/null 2>&1
|
|
}
|
|
|
|
|
|
# -------------
|
|
# - IPv6 handling
|
|
# -------------
|
|
|
|
ENABLE_IPV6="auto" # auto | yes | no
|
|
IPV6_ACTIVE=0
|
|
|
|
ipv6_sysctl_enabled() {
|
|
sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null | grep -qx 0
|
|
}
|
|
|
|
has_ipv6_addr() {
|
|
ip -6 addr show scope global 2>/dev/null | grep -q "inet6"
|
|
}
|
|
|
|
detect_ipv6() {
|
|
case "$ENABLE_IPV6" in
|
|
yes) return 0 ;;
|
|
no) return 1 ;;
|
|
auto) ipv6_sysctl_enabled ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
|
|
# -------------
|
|
# - Fail2ban
|
|
# -------------
|
|
|
|
FAIL2BAN_CONFIG_FILE="/etc/fail2ban/jail.local"
|
|
FAIL2BAN_WAS_RUNNING=false
|
|
fail2ban_client="$(command -v fail2ban-client 2>/dev/null)"
|
|
has_fail2ban() {
|
|
command -v fail2ban-client >/dev/null 2>&1
|
|
}
|
|
|
|
fail2ban_running() {
|
|
systemctl is-active --quiet fail2ban >/dev/null 2>&1
|
|
}
|
|
|
|
# -------------
|
|
# - Debian 12/13 compatibility helpers (best effort)
|
|
# -------------
|
|
ensure_mod() {
|
|
|
|
# ---
|
|
# Load a kernel module if possible (no hard failure).
|
|
# NOTE: In containers module loading is not possible; modules must be loaded on the host.
|
|
# ---
|
|
|
|
local m="$1"
|
|
|
|
# Already loaded?
|
|
if lsmod 2>/dev/null | awk '{print $1}' | grep -qx "$m" ; then
|
|
return 0
|
|
fi
|
|
|
|
# Skip in containers/guests without module loading capability
|
|
#
|
|
is_container && return 0
|
|
|
|
# Best effort modprobe
|
|
/sbin/modprobe "$m" >/dev/null 2>&1 || warn "Loading module '$m' failed (ok if not needed on this host)."
|
|
}
|
|
|
|
# --- Feature detection helpers (Debian 12/13 + containers)
|
|
module_loaded() {
|
|
lsmod 2>/dev/null | awk '{print $1}' | grep -qx "$1"
|
|
}
|
|
|
|
can_use_recent() {
|
|
# xt_recent is the kernel module behind "-m recent"
|
|
# In containers lsmod may be restricted; also accept presence of /proc/net/xt_recent.
|
|
module_loaded xt_recent && return 0
|
|
[ -d /proc/net/xt_recent ] && return 0
|
|
# As a last resort, ask iptables to parse the match (works if userspace has it)
|
|
"$ipt" -m recent -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_hashlimit() {
|
|
# xt_hashlimit is the kernel module behind "-m hashlimit"
|
|
module_loaded xt_hashlimit && return 0
|
|
[ -d /proc/net/xt_hashlimit ] && return 0
|
|
"${fw_command}" -m hashlimit -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_connlimit() {
|
|
# xt_connlimit is the kernel module behind "-m connlimit"
|
|
module_loaded xt_connlimit && return 0
|
|
"${fw_command}" -m connlimit -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_owner() {
|
|
# xt_owner is the kernel module behind "-m owner"
|
|
module_loaded xt_owner && return 0
|
|
"${fw_command}" -m owner -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_ct_target() {
|
|
# Check if iptables CT target exists (iptables-nft should support it when kernel has nf_tables CT support)
|
|
"${fw_command}" -t raw -j CT -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_helper_match() {
|
|
# Check if helper match exists
|
|
"${fw_command}" -m helper -h >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
can_use_nft() {
|
|
command -v nft >/dev/null 2>&1 && return 0
|
|
return 1
|
|
}
|
|
|
|
setup_ftp_conntrack_helper_output() {
|
|
# Prefer explicit helper assignment (safe with nf_conntrack_helper=0)
|
|
if can_use_ct_target ; then
|
|
"${fw_command}" -A OUTPUT -t raw -p tcp --dport "$standard_ftp_port" -j CT --helper ftp
|
|
return 0
|
|
fi
|
|
|
|
# nft fallback (nft-native helper assignment); keeps us "nft-ready"
|
|
if can_use_nft ; then
|
|
# Best-effort; may fail in containers without CAP_NET_ADMIN
|
|
nft add table ip fwhelper >/dev/null 2>&1 || true
|
|
nft add chain ip fwhelper output '{ type filter hook output priority raw; policy accept; }' >/dev/null 2>&1 || true
|
|
nft add ct helper ip fwhelper ftp '{ type "ftp" protocol tcp; }' >/dev/null 2>&1 || true
|
|
nft add rule ip fwhelper output tcp dport "$standard_ftp_port" ct helper set "ftp" >/dev/null 2>&1 && return 0
|
|
fi
|
|
|
|
warn "No CT helper assignment available (iptables CT target and nft fallback both unavailable). FTP active/passive may fail; FTPS workaround relies on recent/port rules."
|
|
return 1
|
|
}
|
|
|
|
setup_ftp_conntrack_helper_prerouting() {
|
|
# Prefer explicit helper assignment (safe with nf_conntrack_helper=0)
|
|
if can_use_ct_target ; then
|
|
"$ipt" -A PREROUTING -t raw -p tcp --dport "$standard_ftp_port" -j CT --helper ftp
|
|
return 0
|
|
fi
|
|
|
|
# nft fallback (nft-native helper assignment); keeps us "nft-ready"
|
|
if can_use_nft ; then
|
|
nft add table ip fwhelper >/dev/null 2>&1 || true
|
|
nft add chain ip fwhelper prerouting '{ type filter hook prerouting priority raw; policy accept; }' >/dev/null 2>&1 || true
|
|
nft add ct helper ip fwhelper ftp '{ type "ftp" protocol tcp; }' >/dev/null 2>&1 || true
|
|
nft add rule ip fwhelper prerouting tcp dport "$standard_ftp_port" ct helper set "ftp" >/dev/null 2>&1 && return 0
|
|
fi
|
|
|
|
warn "No CT helper assignment available (iptables CT target and nft fallback both unavailable). FTP server traffic may fail; consider enabling passive port ranges."
|
|
return 1
|
|
}
|
|
|