#!/bin/bash

## Copyright (C) 2012 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## Copyright (C) 2024 - 2024 Benjamin Grande M. S. <ben.grande.b@gmail.com>
## See the file COPYING for copying conditions.

set -eu -o pipefail -o errtrace

# shellcheck source=../libexec/whonix-firewall/firewall-common
source /usr/libexec/whonix-firewall/firewall-common

variables_defaults(){
  common_variables_defaults_post

  ## 10.0.2.2/24: VirtualBox DHCP
  [ -n "${NON_TOR_GATEWAY:-}" ] || NON_TOR_GATEWAY="192.168.1.0/24 192.168.0.0/24 127.0.0.0/8 10.152.152.0/24 10.0.2.2/24"
  [ -n "${NON_TOR_GATEWAY_IP6:-}" ] || NON_TOR_GATEWAY_IP6="::1/128 fd19:c33d:88bc::0/96 ::ffff:192.168.1.0/120 ::ffff:192.168.0.0/120 ::ffff:127.0.0.0/104 ::ffff:10.152.152.0/120 ::ffff:10.0.2.2/120"

  ## Space separated list of VPN servers, which Whonix-Gateway is allowed to connect to.
  [ -n "${VPN_SERVERS:-}" ] || VPN_SERVERS="198.252.153.26"

  ## Destinations you do not routed through VPN, only for Whonix-Gateway.
  ## 10.0.2.2/24: VirtualBox DHCP
  [ -n "${LOCAL_NET:-}" ] || LOCAL_NET="192.168.1.0/24 192.168.0.0/24 127.0.0.0/8 10.152.152.0/24 10.0.2.2/24"
  [ -n "${LOCAL_NET_IP6:-}" ] || LOCAL_NET_IP6="::1/128 fd19:c33d:88bc::0/96 ::ffff:192.168.1.0/120 ::ffff:192.168.0.0/120 ::ffff:127.0.0.0/104 ::ffff:10.152.152.0/120 ::ffff:10.0.2.2/120"

  [ -n "${GATEWAY_ALLOW_INCOMING_RELATED_STATE:-}" ] || GATEWAY_ALLOW_INCOMING_RELATED_STATE=""
  [ -n "${GATEWAY_ALLOW_INCOMING_ICMP:-}" ] || GATEWAY_ALLOW_INCOMING_ICMP=0
  [ -n "${GATEWAY_ALLOW_INCOMING_ICMP_FRAG_NEEDED:-}" ] || GATEWAY_ALLOW_INCOMING_ICMP_FRAG_NEEDED=1
  [ -n "${GATEWAY_ALLOW_INCOMING_SSH:-}" ] || GATEWAY_ALLOW_INCOMING_SSH=0

  [ -n "${GATEWAY_ALLOW_INCOMING_FLASHPROXY:-}" ] || GATEWAY_ALLOW_INCOMING_FLASHPROXY="0"
  [ -n "${FLASHPROXY_PORT:-}" ] || FLASHPROXY_PORT="9000"

  ## Variables required by nft_input_rules_gateway() in full mode.
  [ -n "${GATEWAY_ALLOW_INCOMING_DIR_PORT:-}" ] || GATEWAY_ALLOW_INCOMING_DIR_PORT=0
  [ -n "${GATEWAY_ALLOW_INCOMING_OR_PORT:-}" ] || GATEWAY_ALLOW_INCOMING_OR_PORT=0
  [ -n "${DIR_PORT:-}" ] || DIR_PORT=80
  [ -n "${OR_PORT:-}" ] || OR_PORT=443
  [ -n "${CONTROL_PORT_FILTER_PROXY_ENABLE:-}" ] || CONTROL_PORT_FILTER_PROXY_ENABLE=0
  [ -n "${WORKSTATION_ALLOW_SOCKSIFIED:-}" ] || WORKSTATION_ALLOW_SOCKSIFIED=0
  [ -n "${WORKSTATION_TRANSPARENT_DNS:-}" ] || WORKSTATION_TRANSPARENT_DNS=0
  [ -n "${WORKSTATION_TRANSPARENT_TCP:-}" ] || WORKSTATION_TRANSPARENT_TCP=0

  ## Internal/tunnel interface (not used on host but required by shared functions).
  [ -n "${INT_IF:-}" ] || INT_IF="eth1"
  [ -n "${INT_TIF:-}" ] || INT_TIF="eth1"

  ## Ports (not used on host but required by shared functions).
  [ -n "${TRANS_PORT_WORKSTATION:-}" ] || TRANS_PORT_WORKSTATION="9040"
  [ -n "${DNS_PORT_WORKSTATION:-}" ] || DNS_PORT_WORKSTATION="5300"
  [ -n "${TRANS_PORT_GATEWAY:-}" ] || TRANS_PORT_GATEWAY="9041"
  [ -n "${DNS_PORT_GATEWAY:-}" ] || DNS_PORT_GATEWAY="5400"
}

nft_defaults(){
  ## Flush old rules.
  ## Using delete (not flush) to ensure a clean slate.
  ## flush only removes rules but keeps chains intact,
  ## which could leave stale chains from previous configurations.
  ## delete removes the entire table including all chains.
  ## Pattern: add (ensure exists) -> delete -> add (recreate empty).
  $nftables_cmd add table inet nat
  $nftables_cmd add table inet filter
  $nftables_cmd add table ip6 nat

  $nftables_cmd delete table inet nat
  $nftables_cmd delete table inet filter
  $nftables_cmd delete table ip6 nat

  $nftables_cmd add table inet nat
  $nftables_cmd add table inet filter
  $nftables_cmd add table ip6 nat

  ## Set secure defaults.
  $nftables_cmd "add chain inet filter input { type filter hook input priority 0; policy drop; }"

  ## forward rules does not actually do anything if forwarding is disabled. Better be safe just in case.
  $nftables_cmd "add chain inet filter forward { type filter hook forward priority 0; policy drop; }"

  ## Will be lifted below.
  $nftables_cmd "add chain inet filter output { type filter hook output priority 0; policy drop; }"

  $nftables_cmd "add chain inet nat prerouting { type nat hook prerouting priority -100; }"
  $nftables_cmd "add chain inet nat output { type nat hook output priority -100; }"

  $nftables_cmd "add chain ip6 nat output { type nat hook output priority -100; }"
}

qubes() {
  qubes_gateway
}

nft_input_rules(){
  nft_input_rules_gateway
}

nft_input_defaults(){
  nft_input_defaults_gateway
}

nft_forward(){
  nft_forward_gateway
}

nft_output(){
  local addr_family

  ## NO OUTPUT FILTERING YET!

  ## Allow outgoing traffic on VPN interface,
  ## if VPN_FIREWALL mode is enabled.
  ## DISABLED BY DEFAULT.
  if [ "$VPN_FIREWALL" = "1" ]; then
    $nftables_cmd add rule inet filter output oifname "$VPN_INTERFACE" counter accept
  fi

  ## Access to localhost is required,
  ## otherwise breaks applications such as konsole and kwrite.
  $nftables_cmd add rule inet filter output oifname "lo" counter accept

  ## Existing connections are accepted.
  $nftables_cmd add rule inet filter output ct state established counter accept

  ## Accept ICMPv6 neighbor discovery.
  ## Required for IPv6 connectivity: without ND, the host cannot discover
  ## routers or resolve link-layer addresses of IPv6 neighbors.
  ## Matches the gateway and workstation firewall behavior.
  $nftables_cmd add rule inet filter output icmpv6 type "{ nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect }" counter accept

  if [ "$VPN_FIREWALL" = "1" ]; then
    ## Connections to VPN servers are allowed,
    ## when VPN_FIREWALL mode is enabled.
    ## DISABLED BY DEFAULT.
    for SERVER in $VPN_SERVERS; do
      if [[ "$SERVER" = *:* ]]; then
        addr_family="ip6"
      else
        addr_family="ip"
      fi

      $nftables_cmd add rule inet filter output "$addr_family" daddr "$SERVER" counter accept
    done
  else
    ## Accept outgoing connections to local network, Whonix-Workstation and VirtualBox,
    ## unless VPN_FIREWALL mode is enabled.
    ## ENABLED BY DEFAULT.
    for NET in $NON_TOR_GATEWAY; do
      $nftables_cmd add rule inet filter output ip daddr "$NET" counter accept
    done
    for NET in $NON_TOR_GATEWAY_IP6; do
      $nftables_cmd add rule inet filter output ip6 daddr "$NET" counter accept
    done
  fi

  ## clearnet user is allowed to connect any outside target.
  ## This rule must be in the filter table (not nat) because the filter
  ## output chain policy is drop - only filter accept rules allow traffic
  ## through. A nat table accept only controls NAT evaluation, not
  ## whether the packet is allowed.
  if [ -n "$CLEARNET_USER" ]; then
    $nftables_cmd add rule inet filter output skuid "$CLEARNET_USER" counter accept
  fi

  ## Reject all other outgoing traffic.
  $nftables_cmd add rule inet filter output counter reject
}

variable_list="
CLEARNET_USER
EXTERNAL_OPEN_ALL
EXTERNAL_OPEN_PORTS
EXTERNAL_UDP_OPEN_PORTS
EXT_IF
FLASHPROXY_PORT
GATEWAY_ALLOW_INCOMING_FLASHPROXY
GATEWAY_ALLOW_INCOMING_ICMP
GATEWAY_ALLOW_INCOMING_ICMP_FRAG_NEEDED
GATEWAY_ALLOW_INCOMING_RELATED_STATE
GATEWAY_ALLOW_INCOMING_SSH
INTERNAL_OPEN_PORTS
LOCAL_NET
LOCAL_NET_IP6
NON_TOR_GATEWAY
NON_TOR_GATEWAY_IP6
NO_REJECT_INVALID_OUTGOING_PACKAGES
VPN_FIREWALL
VPN_INTERFACE
VPN_SERVERS
firewall_mode
firewall_mode_detected
info_enabled
source_only
"

main() {
  get_options "${@}"
  if [ "$source_only" = "1" ]; then
    return 0
  fi
  init
  source_config_folder
  variables_defaults
  set -f
  nft_script_header
  firewall_mode_detection
  print_variables
  nft_defaults
  nft_drop_invalid_incoming_packages
  qubes
  nft_input_rules
  nft_input_defaults
  nft_forward
  nft_reject_invalid_outgoing_packages
  nft_output
  nft_check
  nft_load
  status_files
  end
}

main "${@}"
