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


# -------------
# - Network Device Stuff
# -------------

# get virtual ethernet interfaces and the master of the given bridge
#
get_vth_ports() {
  local br="$1"
  # lists virtual interfaces (veth*)) and the master interface of the given bridge
  ip -o link show master "$br" 2>/dev/null | awk -F': ' '{print $2}'
}

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

