#!/bin/bash

## Copyright (C) 2012 - 2020 ENCRYPTED SUPPORT LP <adrelanos@riseup.net>
## See the file COPYING for copying conditions.

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

## NOTE: If you make changes to this firewall, think about, if it would
##       make sense to add the changes to Whonix-Gateway script as well.
##       Some things like dropping invalid packages, should be shared.

## TODO:
## - Should allow unlimited TCP/UDP/IPv6 traffic on the virtual external interface (OnionCat / OpenVPN).

## source for some rules:
## http://www.cyberciti.biz/faq/ip6tables-ipv6-firewall-for-linux/

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"

   ## Legacy.
   if [ "$VPN_FIREWALL" = "1" ]; then
      TUNNEL_FIREWALL_ENABLE="true"
   fi

   ## Not in use/defined yet.
   ## INT_IF could be the internal network.
   ## EXT_IF could be an additional virtual network adapter,
   ##        such as OnionCat or OpenVPN.

   ## External interface
   [ -n "$EXT_IF" ] || EXT_IF="eth0"
   ## Internal interface
   [ -n "$INT_IF" ] || INT_IF="eth1"

   if command -v "qubesdb-read" >/dev/null 2>&1 ; then
      ## Would fail if netvm is set to 'none',
      ## which is the case in Qubes R4 TemplateVMs.
      [ -n "$GATEWAY_IP" ] || GATEWAY_IP="$(qubesdb-read /qubes-gateway 2>/dev/null)" || GATEWAY_IP="127.0.0.1"
   else
      [ -n "$GATEWAY_IP" ] || GATEWAY_IP="10.152.152.10"
   fi

   ## Since hardcoded in anon-ws-disable-stacked-tor.
   [ -n "$GATEWAY_IP_HARDCODED" ] || GATEWAY_IP_HARDCODED="10.152.152.10"

   [ -n "$TUNNEL_USER" ] || TUNNEL_USER="$(id -u tunnel)"
   [ -n "$NOTUNNEL_USER" ] || NOTUNNEL_USER="$(id -u notunnel)"
   [ -n "$UPDATESPROXYCHECK_USER" ] || UPDATESPROXYCHECK_USER="$(id -u updatesproxycheck)"

   [ -n "$SDWDATE_USER" ] || SDWDATE_USER="$(id -u sdwdate)" || true
   [ -n "$WHONIXCHECK_USER" ] || WHONIXCHECK_USER="$(id -u whonixcheck)" || true

   [ -n "$TUNNEL_FIREWALL_ALLOW_NOTUNNEL_USER" ] || TUNNEL_FIREWALL_ALLOW_NOTUNNEL_USER="true"

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

   [ -n "$qubes_updates_proxy_port" ] || qubes_updates_proxy_port="8082"

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

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

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

   ## Destinations you do not routed through VPN.
   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
}

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() {
   ## Not yet required. Just so Whonix-Workstation firewall can be more similar
   ## to Whonix-Gateway firewall.
   true
}

qubes_dns() {
   local counter
   counter=0

   ## Using '2>/dev/null' because 'qubesdb-read' DNS would fail in Qubes R4
   ## TemplateVMs, because these are non-networked by default.

   if qubes_primary_dns="$(qubesdb-read /qubes-primary-dns 2>/dev/null)" ; then
      $iptables_cmd -A OUTPUT -p udp --dport 53 --dst "$qubes_primary_dns" -j ACCEPT
      counter=$(( counter + 1 ))
   fi

   if qubes_secondary_dns="$(qubesdb-read /qubes-secondary-dns 2>/dev/null)" ; then
      $iptables_cmd -A OUTPUT -p udp --dport 53 --dst "$qubes_secondary_dns" -j ACCEPT
      counter=$(( counter + 1 ))
   fi

   if [ "$counter" -ge "2" ]; then
      output_cmd "OK: Qubes DNS firewall rules ok."
   else
      $iptables_cmd -A OUTPUT -p udp --dport 53 -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

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

   if [ "$firewall_mode" = "timesync-fail-closed" ]; then
      true "firewall_mode is $firewall_mode, therefore not opening EXTERNAL_OPEN_PORTS."
   else
      local local_port_to_open
      for local_port_to_open in $EXTERNAL_OPEN_PORTS; do
         $iptables_cmd -A INPUT -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
   fi
}

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

   ## Required for Control Port Filter Proxy Connection.
   ## https://phabricator.whonix.org/T112
   $iptables_cmd -A INPUT -p tcp -j REJECT --reject-with tcp-reset

   ## Reject anything not explicitly allowed above.
   $iptables_cmd -A INPUT -j REJECT --reject-with icmp-port-unreachable
}

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

   $iptables_cmd -A FORWARD -j DROP
}

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
}

qubes_updates_proxy() {
   ## Detect Qubes.
   if ! command -v "qubesdb-read" >/dev/null 2>&1 ; then
      return 0
   fi

   ## Detect being run inside TemplateVM.
   if [ ! -f "/run/qubes/this-is-templatevm" ]; then
      return 0
   fi

   ## Detect if torified Qubes updates proxy was detected.
   if test -f "/run/qubes-service/whonix-secure-proxy" ; then
      output_cmd "OK: Torified Qubes Updates Proxy check ok. Full access to Qubes Updates Proxy."
      return 0
   fi

   output_cmd "OK: Torified Qubes Updates Proxy check not done yet. Limiting access to Qubes Updates Proxy to user 'updatesproxycheck'."

   $iptables_cmd -A OUTPUT -m owner --uid-owner "$UPDATESPROXYCHECK_USER" -m iprange --dst-range "127.0.0.1" -p tcp --dport "$qubes_updates_proxy_port" -j ACCEPT
   $iptables_cmd -A OUTPUT -m owner --uid-owner "$UPDATESPROXYCHECK_USER" -m iprange --dst-range "10.137.255.254" -p tcp --dport "$qubes_updates_proxy_port" -j ACCEPT

   $iptables_cmd -A OUTPUT -m iprange --dst-range "127.0.0.1" -p tcp --dport "$qubes_updates_proxy_port" -j REJECT --reject-with icmp-admin-prohibited
   $iptables_cmd -A OUTPUT -m iprange --dst-range "10.137.255.254" -p tcp --dport "$qubes_updates_proxy_port" -j REJECT --reject-with icmp-admin-prohibited
}

ipv4_output() {
   ## 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

   qubes_updates_proxy

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

   ## Allow outgoing traffic on VPN interface,
   ## if TUNNEL_FIREWALL_ENABLE mode is enabled.
   ## DISABLED BY DEFAULT.
   if [ "$TUNNEL_FIREWALL_ENABLE" = "true" ]; then
      if [ "$firewall_mode" = "timesync-fail-closed" ]; then
         true "firewall_mode is $firewall_mode, therefore prohibiting user $TUNNEL_USER traffic."
      else
         true "firewall_mode is $firewall_mode, therefore allowing user $TUNNEL_USER traffic."
         ## Connections to VPN servers are allowed,
         $iptables_cmd -A OUTPUT -o "$VPN_INTERFACE" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$TUNNEL_USER" -j ACCEPT
      fi

      if [ "$TUNNEL_FIREWALL_ALLOW_SDWDATE_USER" = "true" ]; then
         $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 "$SDWDATE_USER" -m iprange --dst-range "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$SDWDATE_USER" -m iprange --dst-range "$GATEWAY_IP_HARDCODED" -j ACCEPT
      fi

      if [ "$TUNNEL_FIREWALL_ALLOW_NOTUNNEL_USER" = "true" ]; then
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$NOTUNNEL_USER" -m iprange --dst-range "127.0.0.1" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$NOTUNNEL_USER" -m iprange --dst-range "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$NOTUNNEL_USER" -m iprange --dst-range "$GATEWAY_IP_HARDCODED" -j ACCEPT
      fi

      ## Accept outgoing connections to local network.
      if [ "$TUNNEL_FIREWALL_ALLOW_LOCAL_NET" = "true" ]; then
         if [ "$firewall_mode" = "timesync-fail-closed" ]; then
            true
         else
            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

      if [ "$TUNNEL_FIREWALL_ALLOW_CONTROL_PORT_FILTER_PROXY" = "true" ]; then
         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "127.0.0.1" -j ACCEPT
         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "$GATEWAY_IP_HARDCODED" -j ACCEPT
      fi

      if [ "$TUNNEL_FIREWALL_ALLOW_TB_UPDATER" = "true" ]; then
         if [ "$firewall_mode" = "timesync-fail-closed" ]; then
            true
         else
            local socks_port_tbb
            for socks_port_tbb in $SOCKS_PORT_TBB_DOWNLOAD $SOCKS_PORT_TBB_GPG ; do
               $iptables_cmd -A OUTPUT -p tcp --dport "$socks_port_tbb" --dst "$GATEWAY_IP" -j ACCEPT
               $iptables_cmd -A OUTPUT -p tcp --dport "$socks_port_tbb" --dst "$GATEWAY_IP_HARDCODED" -j ACCEPT
            done
         fi
      fi

      if [ "$TUNNEL_FIREWALL_ALLOW_WHONIXCHECK" = "true" ]; then
         if [ "$firewall_mode" = "timesync-fail-closed" ]; then
            true
         else
            $iptables_cmd -A OUTPUT -m owner --uid-owner "$WHONIXCHECK_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 "$GATEWAY_IP" -j ACCEPT
            $iptables_cmd -A OUTPUT -m owner --uid-owner "$WHONIXCHECK_USER" -m iprange --dst-range "$GATEWAY_IP_HARDCODED" -j ACCEPT
         fi
      fi
   else
      if [ "$firewall_mode" = "timesync-fail-closed" ]; then
         true "firewall_mode is $firewall_mode, therefore prohibiting DNS traffic."
      else
         true "firewall_mode is $firewall_mode, therefore allowing DNS traffic."
         ## Allow Whonix-Workstation to query Whonix-Gateway for DNS.
         $iptables_cmd -A OUTPUT -p udp --dport 53 --dst "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -p udp --dport 53 --dst "$GATEWAY_IP_HARDCODED" -j ACCEPT
         if command -v "qubesdb-read" >/dev/null 2>&1 ; then
            qubes_dns
         fi
      fi

      ## Not sure about the next one. UDP is not supported by Tor, why not
      ## block any outgoing UDP. Might have unwanted side effects when tunneling
      ## UDP over Tor.
      ## https://www.whonix.org/wiki/Tunnel_UDP_over_Tor
      ##
      ## All other non-TCP protocol traffic gets rejected.
      ## iptables knows 7 different protocols and all.
      ## (tcp, udp, udplite, icmp, esp, ah, sctp or all)
      ##
      ## (1) ping torproject.org
      ##     4 packets transmitted, 0 received, 100% packet loss, time 3000ms
      ##
      ## (2) ping torproject.org
      ##     From 10.152.152.11 icmp_seq=1 Destination Port Unreachable
      ##     0 packets transmitted, 0 received, +100 errors
      ##
      ## The next rule ensures, that only tcp can leave and achieves the desired result from (2).
      $iptables_cmd -A OUTPUT ! -p tcp -j REJECT --reject-with icmp-port-unreachable

      if [ "$firewall_mode" = "timesync-fail-closed" ]; then
         true "firewall_mode is $firewall_mode, therefore prohibiting all outgoing traffic."

         ## 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 "$SDWDATE_USER" -m iprange --dst-range "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$SDWDATE_USER" -m iprange --dst-range "$GATEWAY_IP_HARDCODED" -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 -m owner --uid-owner "$WHONIXCHECK_USER" -m iprange --dst-range "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -m owner --uid-owner "$WHONIXCHECK_USER" -m iprange --dst-range "$GATEWAY_IP_HARDCODED" -j ACCEPT

         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "127.0.0.1" -j ACCEPT
         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "$GATEWAY_IP" -j ACCEPT
         $iptables_cmd -A OUTPUT -p tcp --dport "$CONTROL_PORT_FILTER_PROXY_PORT" --dst "$GATEWAY_IP_HARDCODED" -j ACCEPT
      else
         true "firewall_mode is $firewall_mode, therefore allowing all outgoing traffic."
         ## Allow full outgoing connection but no incoming stuff.
         $iptables_cmd -A OUTPUT -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-port-unreachable
   fi
}

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.
   $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 icmp6-port-unreachable
   ## --reject-with icmp-admin-prohibited not supported by ip6tables
   $ip6tables_cmd -A FORWARD -j DROP
}

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
