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