#!/bin/bash

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

###########################
## debugging
###########################

#set -x

###########################
## error_handler
###########################

set -o pipefail

error_handler() {
  echo "##################################################"
  echo "Whonix firewall script failed!"
  echo "##################################################"
  exit 1
}

trap "error_handler" ERR

###########################
## source config folder
###########################

shopt -s nullglob
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
      echo "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

###########################
## comments
###########################

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

###########################
## /usr/bin/whonix_firewall
###########################

echo "OK: Loading Whonix firewall..."

###########################
## interfaces
###########################

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

###########################
## NON_TOR_GATEWAY
###########################

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

################
## VPN related #
################

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

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

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

###########################
## IPv4 DEFAULTS
###########################

## Set secure defaults.
iptables -P INPUT DROP

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

## Not yet filtering outgoing traffic.
#iptables -P OUTPUT DROP

###########################
## IPv4 PREPARATIONS
###########################

## Flush old rules.
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

######################################
## IPv4 DROP INVALID INCOMING PACKAGES
######################################

## DROP INVALID
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -m state --state INVALID -j DROP

## DROP INVALID SYN PACKETS
iptables -A INPUT -p tcp --tcp-flags ALL ACK,RST,SYN,FIN -j DROP
iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
iptables -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 -A INPUT -f -j DROP

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

## DROP INCOMING MALFORMED NULL PACKETS
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP

###########################
## IPv4 INPUT
###########################

## Traffic on the loopback interface is accepted.
iptables -A INPUT -i lo -j ACCEPT

## Established incoming connections are accepted.
iptables -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 -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 -A INPUT -i "$VPN_INTERFACE" -j ACCEPT
fi

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

## Log.
#iptables -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 -A INPUT -j DROP

###########################
## IPv4 FORWARD
###########################

## Log.
#iptables -A FORWARD -j LOG --log-prefix "Whonix blocked forward4: "

## Reject everything.
iptables -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 -A OUTPUT -m conntrack --ctstate INVALID -j REJECT --reject-with icmp-admin-prohibited
   iptables -A OUTPUT -m state --state INVALID -j REJECT --reject-with icmp-admin-prohibited
   #iptables -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 -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 -A OUTPUT -p tcp --tcp-flags ALL ACK,RST,SYN,FIN -j REJECT --reject-with icmp-admin-prohibited
   iptables -A OUTPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j REJECT --reject-with icmp-admin-prohibited
   iptables -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 -A OUTPUT -f -j REJECT --reject-with icmp-admin-prohibited

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

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

###########################
## IPv4 OUTPUT
###########################

## No output filtering yet!

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

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

## 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 NET in $NON_TOR_GATEWAY; do
      iptables -A OUTPUT -d "$NET" -j ACCEPT
   done
fi

## Connections to VPN servers are allowed,
## when VPN_FIREWALL mode is enabled.
## DISABLED BY DEFAULT.
if [ "$VPN_FIREWALL" = "1" ]; then
   for SERVER in $VPN_SERVERS; do
      iptables -A OUTPUT -d "$SERVER" -j ACCEPT
   done
fi

## clearnet user is allowed to connect any outside target.
iptables -A OUTPUT -m owner --uid-owner "$CLEARNET_USER" -j ACCEPT

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

## No output filtering yet!

if [ "$VPN_FIREWALL" = "1" ]; then
   ## Reject all other outgoing traffic.
   iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
fi

###########################
## IPv6
###########################

## Policy DROP for all traffic as fallback.
ip6tables -P INPUT DROP
ip6tables -P OUTPUT DROP
ip6tables -P FORWARD DROP

## Flush old rules.
ip6tables -F
ip6tables -X
ip6tables -t mangle -F
ip6tables -t mangle -X

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

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

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

###########################
## End
###########################

echo "OK: Whonix firewall loaded."

exit 0
