#!/bin/bash

## Copyright (C) 2012 - 2020 ENCRYPTED SUPPORT LP <adrelanos@riseup.net>
## Copyright (C) 2014 - 2015 Jason Mehring <nrgaway@gmail.com>
## See the file COPYING for copying conditions.

#### meta start
#### project Whonix
#### category networking and firewall
#### description
## firewall script
#### meta end

## --reject-with
## http://ubuntuforums.org/showthread.php?p=12011099

## Set to icmp-admin-prohibited because icmp-port-unreachable caused
## confusion. icmp-port-unreachable looks like a bug while
## icmp-admin-prohibited hopefully makes clear it is by design.

set -e

error_handler() {
   echo "$0 ##################################################"
   echo "$0 ERROR: Whonix firewall script failed!"
   echo "$0 ##################################################"

   exit 1
}

trap "error_handler" ERR

init() {
   output_cmd "OK: Loading Whonix firewall..."

   set -o pipefail
   set -o errtrace
}

source_config_folder() {
   shopt -s nullglob
   local i
   for i in \
      /etc/whonix_firewall.d/*.conf \
      /rw/config/whonix_firewall.d/*.conf \
      /usr/local/etc/whonix_firewall.d/*.conf \
      ; do
      bash_n_exit_code="0"
      bash_n_output="$(bash -n "$i" 2>&1)" || { bash_n_exit_code="$?" ; true; };
      if [ ! "$bash_n_exit_code" = "0" ]; then
         output_cmd "ERROR: Invalid config file: $i
   bash_n_exit_code: $bash_n_exit_code
   bash_n_output:
   $bash_n_output" >&2
         exit 1
      fi
      source "$i"
   done
}

variables_defaults() {
   [ -n "$iptables_cmd" ] || iptables_cmd="iptables --wait"
   [ -n "$ip6tables_cmd" ] || ip6tables_cmd="ip6tables --wait"

   [ -n "$WORKSTATION_TRANSPARENT_TCP" ] || WORKSTATION_TRANSPARENT_TCP=1
   [ -n "$WORKSTATION_TRANSPARENT_DNS" ] || WORKSTATION_TRANSPARENT_DNS=1
   [ -n "$WORKSTATION_ALLOW_SOCKSIFIED" ] || WORKSTATION_ALLOW_SOCKSIFIED=1
   [ -n "$CONTROL_PORT_FILTER_PROXY_ENABLE" ] || CONTROL_PORT_FILTER_PROXY_ENABLE=1
   [ -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 "$GATEWAY_TRANSPARENT_TCP" ] || GATEWAY_TRANSPARENT_TCP=0
   [ -n "$GATEWAY_TRANSPARENT_UDP" ] || GATEWAY_TRANSPARENT_UDP=0
   [ -n "$GATEWAY_TRANSPARENT_DNS" ] || GATEWAY_TRANSPARENT_DNS=0
   [ -n "$ALLOW_GATEWAY_ROOT_USER" ] || ALLOW_GATEWAY_ROOT_USER=0
   [ -n "$ALLOW_GATEWAY_USER_USER" ] || ALLOW_GATEWAY_USER_USER=0
   [ -n "$GATEWAY_ALLOW_INCOMING_SSH" ] || GATEWAY_ALLOW_INCOMING_SSH=0
   [ -n "$GATEWAY_ALLOW_INCOMING_ICMP" ] || GATEWAY_ALLOW_INCOMING_ICMP=0

   ## Get Tor username, distro specific!
   [ -n "$TOR_USER" ] || TOR_USER="$(id -u debian-tor)"

   ## Get user uids.
   [ -n "$CLEARNET_USER" ] || CLEARNET_USER="$(id -u clearnet)"
   [ -n "$USER_USER" ] || USER_USER="$(id -u user)" || true
   [ -n "$ROOT_USER" ] || ROOT_USER="$(id -u root)"
   [ -n "$TUNNEL_USER" ] || TUNNEL_USER="$(id -u tunnel)"
   [ -n "$SDWDATE_USER" ] || SDWDATE_USER="$(id -u sdwdate)"
   [ -n "$WHONIXCHECK_USER" ] || WHONIXCHECK_USER="$(id -u whonixcheck)"

   ## No NAT for clearnet user.
   NO_NAT_USERS+=" $CLEARNET_USER"

   ## No NAT for tunnel user.
   NO_NAT_USERS+=" $TUNNEL_USER"

   ## No NAT for user user.
   ## DISABLED BY DEFAULT. For testing/debugging only.
   if [ "$ALLOW_GATEWAY_USER_USER" = "1" ]; then
      if [ "$USER_USER" = "" ]; then
         output_cmd "INFO: USER_USER is unset. Not adding USER_USER to NO_NAT_USERS."
      else
         NO_NAT_USERS+=" $USER_USER"
      fi
   fi

   ## No NAT for root user.
   ## DISABLED BY DEFAULT. For testing/debugging only.
   if [ "$ALLOW_GATEWAY_ROOT_USER" = "1" ]; then
      NO_NAT_USERS+=" $ROOT_USER"
   fi

   ## Whonix-Gateway firewall does not support TUNNEL_FIREWALL_ENABLE=true yet.
   ## It only supports VPN_FIREWALL="1".
   ## In case someone confused this setting, i.e. using TUNNEL_FIREWALL_ENABLE=true
   ## since this is how it is done on Whonix-Workstation, then gracefully enable
   ## VPN_FIREWALL="1" to prevent users shooting their own feet.
   if [ "$TUNNEL_FIREWALL_ENABLE" = "true" ]; then
      VPN_FIREWALL="1"
   fi

   ## No NAT for Tor itself,
   ## unless VPN_FIREWALL mode is enabled.
   if [ "$VPN_FIREWALL" = "1" ]; then
      true
   else
      NO_NAT_USERS+=" $TOR_USER"
   fi

   if command -v "qubesdb-read" >/dev/null 2>&1 ; then
      [ -n "$INT_IF" ] || INT_IF="vif+"
      [ -n "$INT_TIF" ] || INT_TIF="vif+"
   fi

   ## External interface
   [ -n "$EXT_IF" ] || EXT_IF="eth0"
   ## Internal interface
   [ -n "$INT_IF" ] || INT_IF="eth1"
   ## Internal "tunnel" interface, usually the same as
   ## the Internal interface unless using vpn tunnels
   ## between workstations and gateway
   [ -n "$INT_TIF" ] || INT_TIF="eth1"

   if [ "$NON_TOR_GATEWAY" = "" ]; then
      if command -v "qubesdb-read" >/dev/null 2>&1 ; then
         NON_TOR_GATEWAY=""
      else
         ## 10.0.2.2-10.0.2.24: VirtualBox DHCP
         NON_TOR_GATEWAY="\
            127.0.0.0-127.0.0.24 \
            192.168.0.0-192.168.0.24 \
            192.168.1.0-192.168.1.24 \
            10.152.152.0-10.152.152.24 \
            10.0.2.2-10.0.2.24 \
         "
      fi
   fi

   [ -n "$VPN_INTERFACE" ] || VPN_INTERFACE="tun0"

   ## Destinations you do not routed through VPN, only for Whonix-Gateway.
   if [ "$LOCAL_NET" = "" ]; then
      if command -v "qubesdb-read" >/dev/null 2>&1 ; then
         LOCAL_NET="\
            127.0.0.0-127.0.0.24 \
            10.137.0.0-10.138.255.255 \
         "
      else
         ## 10.0.2.2/24: VirtualBox DHCP
         LOCAL_NET="\
            127.0.0.0-127.0.0.24 \
            192.168.0.0-192.168.0.24 \
            192.168.1.0-192.168.1.24 \
            10.152.152.0-10.152.152.24 \
            10.0.2.2-10.0.2.24 \
         "
      fi
   fi

   if [ "$WORKSTATION_DEST_SOCKSIFIED" = "" ]; then
      ## 10.152.152.10 - Non-Qubes-Whonix-Gateway IP
      ##
      ## 10.137.0.0/8  - persistent Qubes-Whonix-Gateway IP range
      ## 10.138.0.0/8  - DispVM Qubes-Whonix-Gateway IP range
      if command -v "qubesdb-read" >/dev/null 2>&1 ; then
         ## https://forums.whonix.org/t/whonix-gateway-not-reachable/7484/16
         ## 10.152.152.10 is hardcoded in some places.
         WORKSTATION_DEST_SOCKSIFIED="10.137.0.0/16,10.138.0.0/16,10.152.152.10"
      else
         WORKSTATION_DEST_SOCKSIFIED="10.152.152.10"
      fi
   fi

   ## The following ports are used
   ## - here in /usr/bin/whonix_firewall (package: whonix-gw-firewall)
   ## - by Tor in /usr/share/tor/tor-service-defaults-torrc (package: anon-gw-anonymizer-config)
   ##
   ## The following applications will be separated, preventing identity
   ## correlation through circuit sharing.

   ## Transparent Proxy Ports for Whonix-Workstation
   [ -n "$TRANS_PORT_WORKSTATION" ] || TRANS_PORT_WORKSTATION="9040"
   [ -n "$DNS_PORT_WORKSTATION" ] || DNS_PORT_WORKSTATION="5300"

   ## Transparent Proxy Ports for Whonix-Gateway
   [ -n "$TRANS_PORT_GATEWAY" ] || TRANS_PORT_GATEWAY="9041"
   [ -n "$DNS_PORT_GATEWAY" ] || DNS_PORT_GATEWAY="5400"

   ## Control Port Filter Proxy Port
   [ -n "$CONTROL_PORT_FILTER_PROXY_PORT" ] || CONTROL_PORT_FILTER_PROXY_PORT="9051"

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

   ## Socks Ports for per application circuits.
   [ -n "$SOCKS_PORT_TOR_DEFAULT" ] || SOCKS_PORT_TOR_DEFAULT="9050"
   [ -n "$SOCKS_PORT_TB" ] || SOCKS_PORT_TB="9100"
   [ -n "$SOCKS_PORT_IRC" ] || SOCKS_PORT_IRC="9101"
   [ -n "$SOCKS_PORT_TORBIRDY" ] || SOCKS_PORT_TORBIRDY="9102"
   [ -n "$SOCKS_PORT_IM" ] || SOCKS_PORT_IM="9103"
   [ -n "$SOCKS_PORT_APT_GET" ] || SOCKS_PORT_APT_GET="9104"
   [ -n "$SOCKS_PORT_GPG" ] || SOCKS_PORT_GPG="9105"
   [ -n "$SOCKS_PORT_SSH" ] || SOCKS_PORT_SSH="9106"
   [ -n "$SOCKS_PORT_GIT" ] || SOCKS_PORT_GIT="9107"
   [ -n "$SOCKS_PORT_SDWDATE" ] || SOCKS_PORT_SDWDATE="9108"
   [ -n "$SOCKS_PORT_WGET" ] || SOCKS_PORT_WGET="9109"
   [ -n "$SOCKS_PORT_WHONIXCHECK" ] || SOCKS_PORT_WHONIXCHECK="9110"
   [ -n "$SOCKS_PORT_BITCOIN" ] || SOCKS_PORT_BITCOIN="9111"
   [ -n "$SOCKS_PORT_PRIVOXY" ] || SOCKS_PORT_PRIVOXY="9112"
   [ -n "$SOCKS_PORT_POLIPO" ] || SOCKS_PORT_POLIPO="9113"
   [ -n "$SOCKS_PORT_WHONIX_NEWS" ] || SOCKS_PORT_WHONIX_NEWS="9114"
   [ -n "$SOCKS_PORT_TBB_DOWNLOAD" ] || SOCKS_PORT_TBB_DOWNLOAD="9115"
   [ -n "$SOCKS_PORT_TBB_GPG" ] || SOCKS_PORT_TBB_GPG="9116"
   [ -n "$SOCKS_PORT_CURL" ] || SOCKS_PORT_CURL="9117"
   [ -n "$SOCKS_PORT_RSS" ] || SOCKS_PORT_RSS="9118"
   [ -n "$SOCKS_PORT_TORCHAT" ] || SOCKS_PORT_TORCHAT="9119"
   [ -n "$SOCKS_PORT_MIXMASTERUPDATE" ] || SOCKS_PORT_MIXMASTERUPDATE="9120"
   [ -n "$SOCKS_PORT_MIXMASTER" ] || SOCKS_PORT_MIXMASTER="9121"
   [ -n "$SOCKS_PORT_KDE" ] || SOCKS_PORT_KDE="9122"
   [ -n "$SOCKS_PORT_GNOME" ] || SOCKS_PORT_GNOME="9123"
   [ -n "$SOCKS_PORT_APTITUDE" ] || SOCKS_PORT_APTITUDE="9124"
   [ -n "$SOCKS_PORT_YUM" ] || SOCKS_PORT_YUM="9125"
   [ -n "$SOCKS_PORT_TBB_DEFAULT" ] || SOCKS_PORT_TBB_DEFAULT="9150"

   ## For testing purposes only.
   ## To test if prerouting redirection rules for socksified interfere with transparent torification.
   ## https://phabricator.whonix.org/T462
   #[ -n "$SOCKS_PORT_HTTP" ] || SOCKS_PORT_HTTP="80"
   #[ -n "$SOCKS_PORT_SSL" ] || SOCKS_PORT_SSL="443"

   ## Adding more Socks Ports here should no longer be necessary.
   ## There are already lots of custom ports prepared that you can use.
   ## See documentation:
   ## https://www.whonix.org/wiki/Stream_Isolation
   ##
   ## Additional Socks Ports for per application circuits could be
   ## added here, but you would have to:
   ## - Edit '/usr/local/etc/torrc.d/50_user.conf' to add more 'SocksPort's.
   ## - And 'sudo service tor@default reload' afterwards.
   ## - Add more socks port variables to Whonix firewall configuration.
   ##   (For example to '/etc/whonix_firewall.d/50_user.conf'.)
   ##   Follow the 'SOCKS_PORT_...' naming scheme.
   ##   (For example 'SOCKS_PORT_CUSTOM_ONE', 'SOCKS_PORT_CUSTOM_TWO', etc.)
   ## - And issue "sudo /usr/bin/whonix_firewall" afterwards.

   socks_ports_list="$(compgen -v | grep SOCKS\_PORT\_)"
}

ipv4_defaults() {
   ## Set secure defaults.
   $iptables_cmd -P INPUT DROP

   ## FORWARD rules does not actually do anything if forwarding is disabled. Better be safe just in case.
   $iptables_cmd -P FORWARD DROP

   ## Will be lifted below.
   $iptables_cmd -P OUTPUT DROP
}

ipv4_preparation() {
   ## Flush old rules.
   $iptables_cmd -F
   $iptables_cmd -X
   $iptables_cmd -t nat -F
   $iptables_cmd -t nat -X
   $iptables_cmd -t mangle -F
   $iptables_cmd -t mangle -X
}

ipv4_drop_invalid_incoming_packages() {
   ## DROP INVALID
   $iptables_cmd -A INPUT -m conntrack --ctstate INVALID -j DROP
   $iptables_cmd -A INPUT -m state --state INVALID -j DROP

   ## DROP INVALID SYN PACKETS
   $iptables_cmd -A INPUT -p tcp --tcp-flags ALL ACK,RST,SYN,FIN -j DROP
   $iptables_cmd -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
   $iptables_cmd -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP

   ## DROP PACKETS WITH INCOMING FRAGMENTS. THIS ATTACK ONCE RESULTED IN KERNEL PANICS
   $iptables_cmd -A INPUT -f -j DROP

   ## DROP INCOMING MALFORMED XMAS PACKETS
   $iptables_cmd -A INPUT -p tcp --tcp-flags ALL ALL -j DROP

   ## DROP INCOMING MALFORMED NULL PACKETS
   $iptables_cmd -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
}

qubes() {
   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      true "timesync-fail-closed mode, skipping rest of function $FUNCNAME"
      return 0
   fi

   if [ -e /run/qubes/this-is-netvm ] || [ -e /run/qubes/this-is-proxyvm ]; then
      local int_if_item

      for int_if_item in $INT_IF; do
          ## Allow connections from port 8082 of internal vif interface for tinyproxy
          ## tinyproxy is responsible to handle TemplateVMs updates.
          $iptables_cmd -A INPUT -i "$int_if_item" -p tcp -m tcp --dport 8082 -j ACCEPT
          $iptables_cmd -A OUTPUT -o "$int_if_item" -p tcp -m tcp --sport 8082 -j ACCEPT
      done

      ## Qubes pre-routing. Will be able to intercept traffic destined for
      ## 10.137.255.254 to be re-routed to tinyproxy.
      $iptables_cmd -t nat -N PR-QBS-SERVICES
      $iptables_cmd -t nat -A PREROUTING -j PR-QBS-SERVICES

      for int_if_item in $INT_IF; do
          ## Redirects traffic destined for 10.137.255.154 to port 8082 (tinyproxy).
          $iptables_cmd -t nat -A PR-QBS-SERVICES -d 10.137.255.254/32 -i "$int_if_item" -p tcp -m tcp --dport 8082 -j REDIRECT
      done

      ## Forward tinyproxy output to port 5300/9040 on internal (Tor) interface (eth1) to be
      ## able to connect to Internet (via Tor) to proxy updates for TemplateVM.
      $iptables_cmd -t nat -A OUTPUT -p udp -m owner --uid-owner tinyproxy -m conntrack --ctstate NEW -j DNAT --to "127.0.0.1:${DNS_PORT_GATEWAY}"
      $iptables_cmd -t nat -A OUTPUT -p tcp -m owner --uid-owner tinyproxy -m conntrack --ctstate NEW -j DNAT --to "127.0.0.1:${TRANS_PORT_GATEWAY}"

      ## The same for squid from qubes-updates-cache, which runs as user vm-updates.
      if getent passwd vm-updates >/dev/null; then
         $iptables_cmd -t nat -A OUTPUT -p udp -m owner --uid-owner vm-updates -m conntrack --ctstate NEW -j DNAT --to "127.0.0.1:${DNS_PORT_GATEWAY}"
         $iptables_cmd -t nat -A OUTPUT -p tcp -m owner --uid-owner vm-updates -m conntrack --ctstate NEW -j DNAT --to "127.0.0.1:${TRANS_PORT_GATEWAY}"
      fi

      ## https://github.com/QubesOS/qubes-issues/issues/3201#issuecomment-338646742
      $iptables_cmd -A OUTPUT -p udp -m owner --uid-owner tinyproxy -m conntrack --ctstate NEW -d 127.0.0.1 --dport "${DNS_PORT_GATEWAY}" -j ACCEPT
      $iptables_cmd -A OUTPUT -p tcp -m owner --uid-owner tinyproxy -m conntrack --ctstate NEW -d 127.0.0.1 --dport "${TRANS_PORT_GATEWAY}" -j ACCEPT
   fi
}

ipv4_input_rules() {
   ## Traffic on the loopback interface is accepted.
   $iptables_cmd -A INPUT -i lo -j ACCEPT

   ## Established incoming connections are accepted.
   $iptables_cmd -A INPUT -m state --state ESTABLISHED -j ACCEPT

   ## Drop all incoming ICMP traffic by default.
   ## All incoming connections are dropped by default anyway, but should a user
   ## allow incoming ports (such as for incoming SSH or FlashProxy), ICMP should
   ## still be dropped to filter for example ICMP time stamp requests.
   if [ ! "$GATEWAY_ALLOW_INCOMING_ICMP" = "1" ]; then
      $iptables_cmd -A INPUT -p icmp -j DROP
   fi

   ## Allow all incoming connections on the virtual VPN network interface,
   ## when VPN_FIREWALL mode is enabled.
   ## DISABLED BY DEFAULT.
   if [ "$VPN_FIREWALL" = "1" ]; then
      $iptables_cmd -A INPUT -i "$VPN_INTERFACE" -j ACCEPT
   fi

   local ext_if_item

   for ext_if_item in $EXT_IF; do
      ## Allow incoming SSH connections on the external interface.
      ## DISABLED BY DEFAULT. For testing/debugging only.
      if [ "$GATEWAY_ALLOW_INCOMING_SSH" = "1" ]; then
         $iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport 22 -j ACCEPT
      fi

      ## Allow incoming Flash Proxy connections on the external interface.
      ## This has NOTHING to do with Adobe Flash.
      ## DISABLED BY DEFAULT.
      if [ "$GATEWAY_ALLOW_INCOMING_FLASHPROXY" = "1" ]; then
         $iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport "$FLASHPROXY_PORT" -j ACCEPT
      fi

      local local_port_to_open
      for local_port_to_open in $EXTERNAL_OPEN_PORTS; do
         $iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport "$local_port_to_open" -j ACCEPT
      done

      local local_udp_port_to_open
      for local_udp_port_to_open in $EXTERNAL_UDP_OPEN_PORTS; do
         $iptables_cmd -A INPUT -p udp --dport "$local_udp_port_to_open" -j ACCEPT
      done

      if [ "$EXTERNAL_OPEN_ALL" = "true" ]; then
         $iptables_cmd -A INPUT -j ACCEPT
      fi
   done

   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      true "timesync-fail-closed mode, skipping rest of function $FUNCNAME"
      return 0
   fi

   for ext_if_item in $EXT_IF; do
      ## Allow incoming DIRPORT connections for an optional Tor relay.
      ## DISABLED BY DEFAULT.
      if [ "$GATEWAY_ALLOW_INCOMING_DIR_PORT" = "1" ]; then
         $iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport "$DIR_PORT" -j ACCEPT
      fi

      ## Allow incoming ORPORT connections for an optional Tor relay.
      ## DISABLED BY DEFAULT.
      if [ "$GATEWAY_ALLOW_INCOMING_OR_PORT" = "1" ]; then
         $iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport "$OR_PORT" -j ACCEPT
      fi

      ## Custom Open Ports on external interface
      ## - untested, should work
      ## - Replace 22,9050,9051,9150,9151 with any ports you like to be open, example: 9050,9051
      ##   or just 9050
      ## - $iptables_cmd v1.4.14: multiport needs -p tcp, -p udp, -p udplite, -p sctp or -p dccp
      #$iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --match multiport --dports 22,9050,9051,9150,9151 -j ACCEPT
      #$iptables_cmd -A INPUT -i "$ext_if_item" -p udp --match multiport --dports 22,9050,9051,9150,9151 -j ACCEPT

      ## OPTIONAL Allow incoming OpenVPN connections on the external interface.
      #$iptables_cmd -A INPUT -i "$ext_if_item" -p tcp --dport 1194 -j ACCEPT
   done

   local int_tif_item
   local int_if_item

   for int_tif_item in $INT_TIF; do
      if [ "$WORKSTATION_TRANSPARENT_DNS" = "1" ]; then
         ## Allow DNS traffic to DnsPort.
         $iptables_cmd -A INPUT -i "$int_tif_item" -p udp --dport "$DNS_PORT_WORKSTATION" -j ACCEPT
      fi
   done

   for int_if_item in $INT_IF; do
      if [ "$WORKSTATION_TRANSPARENT_TCP" = "1" ]; then
         ## Allow TCP traffic TransPort.
         $iptables_cmd -A INPUT -i "$int_if_item" -p tcp --dport "$TRANS_PORT_WORKSTATION" -j ACCEPT
      fi
   done

   for int_tif_item in $INT_TIF; do
      ## Allow TCP traffic to Control Port Filter Proxy.
      if [ "$CONTROL_PORT_FILTER_PROXY_ENABLE" = "1" ]; then
         $iptables_cmd -A INPUT -i "$int_tif_item" -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" -j ACCEPT
      fi

      ## Allow socksified applications.
      if [ "$WORKSTATION_ALLOW_SOCKSIFIED" = "1" ]; then
         for socks_port in $socks_ports_list; do
            true "$socks_port: ${!socks_port}"
            $iptables_cmd -A INPUT -i "$int_tif_item" -p tcp --dport "${!socks_port}" -j ACCEPT
         done

         ## Accept ports 9152-9189 prepared for user custom applications.
         ## See /usr/share/tor/tor-service-defaults-torrc for more comments.
         $iptables_cmd -A INPUT -i "$int_tif_item" -p tcp --match multiport --dports 9152:9189 -j ACCEPT
      fi
   done

   for int_if_item in $INT_IF; do
      ## Redirect Control Port Filter Proxy to Control Port Filter Proxy port.
      if [ "$CONTROL_PORT_FILTER_PROXY_ENABLE" = "1" ]; then
         $iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -d "$WORKSTATION_DEST_SOCKSIFIED" -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" -j REDIRECT --to-ports "$CONTROL_PORT_FILTER_PROXY_PORT"
      fi

      if [ "$WORKSTATION_ALLOW_SOCKSIFIED" = "1" ]; then
         for socks_port in $socks_ports_list; do
            true "$socks_port: ${!socks_port}"
            ## Redirect Browser/IRC/TorBirdy, etc. to SocksPort.
            $iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -d "$WORKSTATION_DEST_SOCKSIFIED" -p tcp --dport "${!socks_port}" -j REDIRECT --to-ports "${!socks_port}"
         done

         ## Redirect ports 9152-9189 prepared for user custom applications.
         $iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -d "$WORKSTATION_DEST_SOCKSIFIED" -p tcp --dport 9152:9189 -j REDIRECT
      fi

      if [ "$WORKSTATION_TRANSPARENT_DNS" = "1" ]; then
         ## Redirect remaining DNS traffic to DNS_PORT_WORKSTATION.
         ## Only user installed applications not configured to use a SocksPort are affected.
         $iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT_WORKSTATION"
      fi

      if [ "$WORKSTATION_TRANSPARENT_TCP" = "1" ]; then
         ## Catch all remaining TCP and redirect to TransPort.
         ## Only user installed applications not configured to use a SocksPort are affected.
         $iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -p tcp --syn -j REDIRECT --to-ports "$TRANS_PORT_WORKSTATION"

         ## Optionally restrict TransPort.
         ## Replace above rule with a more restrictive one, e.g.:
         #$iptables_cmd -t nat -A PREROUTING -i "$int_if_item" -p tcp --match multiport --dports 80,443 --syn -j REDIRECT --to-ports "$TRANS_PORT_WORKSTATION"
      fi
   done
}

ipv4_input_defaults() {
   ## Log.
   #$iptables_cmd -A INPUT -j LOG --log-prefix "Whonix blocked input4: "

   ## Reject anything not explicitly allowed above.
   ## Drop is better than reject here, because we do not want to reveal it's a Whonix-Gateway.
   ## (In case someone running Whonix-Gateway on bare metal.)
   $iptables_cmd -A INPUT -j DROP
}

ipv4_forward() {
   ## Log.
   #$iptables_cmd -A FORWARD -j LOG --log-prefix "Whonix blocked forward4: "

   ## Reject everything.
   $iptables_cmd -A FORWARD -j REJECT --reject-with icmp-admin-prohibited
}

ipv4_reject_invalid_outgoing_packages() {
   ## Drop invalid outgoing packages,
   ## unless NO_REJECT_INVALID_OUTGOING_PACKAGES is set to 1.
   if [ ! "$NO_REJECT_INVALID_OUTGOING_PACKAGES" = "1" ]; then
      ## https://lists.torproject.org/pipermail/tor-talk/2014-March/032507.html
      $iptables_cmd -A OUTPUT -m conntrack --ctstate INVALID -j REJECT --reject-with icmp-admin-prohibited
      $iptables_cmd -A OUTPUT -m state --state INVALID -j REJECT --reject-with icmp-admin-prohibited
      #$iptables_cmd -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,FIN ACK,FIN -j REJECT --reject-with icmp-admin-prohibited
      #$iptables_cmd -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,RST ACK,RST -j REJECT --reject-with icmp-admin-prohibited

      ## DROP INVALID SYN PACKETS
      $iptables_cmd -A OUTPUT -p tcp --tcp-flags ALL ACK,RST,SYN,FIN -j REJECT --reject-with icmp-admin-prohibited
      $iptables_cmd -A OUTPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j REJECT --reject-with icmp-admin-prohibited
      $iptables_cmd -A OUTPUT -p tcp --tcp-flags SYN,RST SYN,RST -j REJECT --reject-with icmp-admin-prohibited

      ## DROP PACKETS WITH INCOMING FRAGMENTS. THIS ATTACK ONCE RESULTED IN KERNEL PANICS
      $iptables_cmd -A OUTPUT -f -j REJECT --reject-with icmp-admin-prohibited

      ## DROP INCOMING MALFORMED XMAS PACKETS
      $iptables_cmd -A OUTPUT -p tcp --tcp-flags ALL ALL -j REJECT --reject-with icmp-admin-prohibited

      ## DROP INCOMING MALFORMED NULL PACKETS
      $iptables_cmd -A OUTPUT -p tcp --tcp-flags ALL NONE -j REJECT --reject-with icmp-admin-prohibited
   fi
}

ipv4_output() {
   ## Allow outgoing traffic on VPN interface,
   ## if VPN_FIREWALL mode is enabled.
   ## DISABLED BY DEFAULT.
   if [ "$VPN_FIREWALL" = "1" ]; then
      $iptables_cmd -A OUTPUT -o "$VPN_INTERFACE" -j ACCEPT
   fi

   local no_nat_user
   for no_nat_user in $NO_NAT_USERS ; do
      $iptables_cmd -t nat -A OUTPUT -m owner --uid-owner "$no_nat_user" -j RETURN
   done

   if [ "$firewall_mode" = "full" ]; then
      ## Redirect of Gateway DNS traffic to DNS_PORT_GATEWAY.
      ## DISABLED BY DEFAULT. default. Using SocksPort instead.
      if [ "$GATEWAY_TRANSPARENT_DNS" = "1" ]; then
         $iptables_cmd -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT_GATEWAY"
      fi
   fi

   if [ "$firewall_mode" = "full" ]; then
      ## Exclude connections to local network, Whonix-Workstation, VirtualBox from being redirected through Tor,
      ## unless VPN_FIREWALL mode is enabled.
      ## ENABLED BY DEFAULT.
      if [ ! "$VPN_FIREWALL" = "1" ]; then
         local non_tor_gateway_item
         for non_tor_gateway_item in $NON_TOR_GATEWAY; do
            $iptables_cmd -t nat -A OUTPUT -m iprange --dst-range "$non_tor_gateway_item" -j RETURN
         done
      fi
   fi

   if [ "$firewall_mode" = "full" ]; then
      ## Redirect all Gateway TCP traffic to TRANS_PORT_GATEWAY.
      ## DISABLED BY DEFAULT. Using SocksPort instead.
      if [ "$GATEWAY_TRANSPARENT_TCP" = "1" ]; then
         $iptables_cmd -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports "$TRANS_PORT_GATEWAY"
      fi
   fi

   ## Existing connections are accepted.
   $iptables_cmd -A OUTPUT -m state --state ESTABLISHED -j ACCEPT

   if [ "$firewall_mode" = "full" ]; then
      ## Accept outgoing connections to local network, Whonix-Workstation and VirtualBox,
      ## unless VPN_FIREWALL mode is enabled.
      ## ENABLED BY DEFAULT.
      if [ ! "$VPN_FIREWALL" = "1" ]; then
         for non_tor_gateway_item in $NON_TOR_GATEWAY; do
            $iptables_cmd -A OUTPUT -m iprange --dst-range "$non_tor_gateway_item" -j ACCEPT
         done
      fi
   fi

   if [ "$firewall_mode" = "full" ]; then
      ## Accept outgoing connections to local network,
      ## when VPN_FIREWALL mode is enabled.
      ## DISABLED BY DEFAULT.
      if [ "$VPN_FIREWALL" = "1" ]; then
         local local_net_item
         for local_net_item in $LOCAL_NET; do
            $iptables_cmd -A OUTPUT -m iprange --dst-range "$local_net_item" -j ACCEPT
         done
      fi
   fi

   ## Prevent connections to Tor SocksPorts.
   ## https://phabricator.whonix.org/T533#11025
   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      local socks_port_item
      for socks_port_item in $socks_ports_list; do
         true "$socks_port_item: ${!socks_port_item}"
         if [ "$SOCKS_PORT_SDWDATE" = "${!socks_port_item}" ]; then
            continue
         fi
         $iptables_cmd -A OUTPUT -p tcp --dport "${!socks_port_item}" --dst "127.0.0.1" -j REJECT
      done
   fi

   ## Access to localhost is required even in timesync-fail-closed mode,
   ## otherwise breaks applications such as konsole and kwrite.
   $iptables_cmd -A OUTPUT -o lo -j ACCEPT

   for no_nat_user in $NO_NAT_USERS ; do
      $iptables_cmd -A OUTPUT -m owner --uid-owner "$no_nat_user" -j ACCEPT
   done

   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      ## Allow sdwdate talking to localhost and Tor in Whonix firewall timesync-fail-closed mode.
      ## Otherwise in Whonix firewall full mode this rule is redundant.
      $iptables_cmd -A OUTPUT -m owner --uid-owner "$SDWDATE_USER" -m iprange --dst-range "127.0.0.1" -j ACCEPT
      $iptables_cmd -A OUTPUT -m owner --uid-owner "$WHONIXCHECK_USER" -m iprange --dst-range "127.0.0.1" -j ACCEPT

      $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "127.0.0.1" -j ACCEPT
   fi

   ## Log.
   #$iptables_cmd -A OUTPUT -j LOG --log-prefix "Whonix blocked output4: "

   ## Reject all other outgoing traffic.
   $iptables_cmd -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
}

ipv6() {
   ## Policy DROP for all traffic as fallback.
   $ip6tables_cmd -P INPUT DROP
   $ip6tables_cmd -P OUTPUT DROP
   $ip6tables_cmd -P FORWARD DROP

   ## Flush old rules.
   $ip6tables_cmd -F
   $ip6tables_cmd -X
   $ip6tables_cmd -t mangle -F
   $ip6tables_cmd -t mangle -X

   ## Allow unlimited access on loopback.
   ## Not activated, since we do not need it.
   #$ip6tables_cmd -A INPUT -i lo -j ACCEPT
   #$ip6tables_cmd -A OUTPUT -o lo -j ACCEPT

   ## Log.
   #$ip6tables_cmd -A INPUT -j LOG --log-prefix "Whonix blocked input6: "
   #$ip6tables_cmd -A OUTPUT -j LOG --log-prefix "Whonix blocked output6: "
   #$ip6tables_cmd -A FORWARD -j LOG --log-prefix "Whonix blocked forward6: "

   ## Drop/reject all other traffic.
   $ip6tables_cmd -A INPUT -j DROP
   ## --reject-with icmp-admin-prohibited not supported by ip6tables
   $ip6tables_cmd -A OUTPUT -j REJECT
   ## --reject-with icmp-admin-prohibited not supported by ip6tables
   $ip6tables_cmd -A FORWARD -j REJECT
}

status_files() {
   mkdir --parents /run/whonix_firewall
   if [ -e /run/whonix_firewall/first_run_current_boot.status ]; then
      touch /run/whonix_firewall/consecutive_run.status
      return 0
   fi
   touch /run/whonix_firewall/first_run_current_boot.status
}

date_cmd(){
   date -u +"%Y-%m-%d %T"
}

output_cmd() {
   echo "$(date_cmd) - $0 - $@"
}

firewall_mode_detection() {
   if [ ! "$firewall_mode" = "" ]; then
      output_cmd "OK: Skipping firewall mode detection since already set to '$firewall_mode'."
      if [ "$firewall_mode" = "timesync-fail-closed" ]; then
         output_cmd "OK: (Only local Tor control port connections and torified sdwdate allowed.)"
         return 0
      elif [ "$firewall_mode" = "full" ]; then
         output_cmd "OK: (Full torified network access allowed.)"
         return 0
      else
         output_cmd "ERROR: firewall_mode must be set to either 'full' or 'timesync-fail-closed'."
         error_handler
      fi
   fi

   ## Run Whonix firewall in full mode if sdwdate already succeeded.
   if [ -e /run/sdwdate/first_success ]; then
      firewall_mode=full
      output_cmd "OK: (/run/sdwdate/first_success exists.)"
   elif [ -e /run/sdwdate/success ]; then
      firewall_mode=full
      output_cmd "OK: (/run/sdwdate/success exists.)"
   ## /run/whonix_firewall/first_run_current_boot.status already exists,
   ## therefore have Whonix firewall run in full mode.
   elif [ -e /run/whonix_firewall/first_run_current_boot.status ]; then
      firewall_mode=full
      output_cmd "OK: (/run/whonix_firewall/first_run_current_boot.status exists.)"
   else
      ## /run/whonix_firewall/first_run_current_boot.status does not yet exist,
      ## therefore return 'yes, timesync-fail-closed'.
      firewall_mode=timesync-fail-closed
   fi

   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      output_cmd "OK: First run during current boot, therefore running in timesync-fail-closed mode."
      output_cmd "OK: (Only local Tor control port connections and torified sdwdate allowed.)"
   else
      output_cmd "OK: Consecutive run during current boot, therefore running in full mode."
      output_cmd "OK: (Full torified network access allowed.)"
   fi
}

end() {
   output_cmd "OK: Whonix firewall loaded."

   exit 0
}

main() {
   init
   firewall_mode_detection
   variables_defaults
   ipv4_defaults
   ipv4_preparation
   ipv4_drop_invalid_incoming_packages
   qubes
   ipv4_input_rules
   ipv4_input_defaults
   ipv4_forward
   ipv4_reject_invalid_outgoing_packages
   ipv4_output
   if [ -d /proc/sys/net/ipv6/ ]; then
     ipv6
   fi
   status_files
   end
}

source_config_folder
main
