#!/bin/sh
PATH="/bin:/usr:/usr/bin:/sbin:/usr/sbin"

# Personal firewall script.
# Copyright (C) 2000-2007 Nicholas J. Kain <njk at aerifal dot cx>
# Licensed under BSD.
# Absolutely no warranty -- I never intended to distribute it, anyway.

# Note that I don't filter output at all; I like to be able to send
# specially constructed packets if necessary, even if I never actually use
# the functionality.

# the adapter that handles our trusted internal network
INT_IF=eth0
INT_NET="10.0.0.0/24"

# our WAN settings
BRIDGE_IF=eth1
EXT_IF=`ifconfig | grep ppp | cut -c1-4`
EXT_IP=`ifconfig $EXT_IF | awk '/inet/ {sub(/.*:/,"",$2);print $2}'`

# our wireless access point
WIFI_IF=ath0
WIFI_IP=`ifconfig $WIFI_IF | awk '/inet/ {sub(/.*:/,"",$2);print $2}'`
WIFI_NET="172.16.1.0/24"

# our OpenVPN tunnel settings
TUN_IF=tun+
TUN_NET="10.10.0.0/24"

# our old external IP
OLDEXTIP=`cat /etc/OLDEXTIP`

# boolean: should we allow IPV6-in-IPV4?
USE_IPV6="NO"

# boolean ("NO" or "YES"): should we allow ICMP Echo (ping) requests?
ALLOW_PING="YES"

# boolean: should we allow DHCP on the external interface?
USE_DHCP="NO"
# IP: IP address of the ISP's DHCP server
#DHCPSIP=""

# boolean: Do we need to clamp MSS?  Set true for PPPOE.
CLAMP_MSS="YES"

# boolean: should we limit ssh?
LIMIT_SSH="YES"

# service ports on the local machine that should be remotely accessible
TCPSERV="20 21 smtp auth www 8080 8081"
UDPSERV="domain 500"

# disable ECN to these hosts
BADECN="205.230.159.0/24"

# low latency ports
TCPLL="domain ntp 6667"
UDPLL="domain ntp"

# bulk ports
TCPBULK=""
UDPBULK=""

# min cost ports
TCPMINCOST="smtp"
UDPMINCOST=""

# Bogus networks: http://www.cymru.com/Bogons/
BOGONS=`cat /etc/bogons`

# ICMP types that are dropped by default
BADICMP="5 6 9" # redirect, alt host addy, router ad

# hostile hosts that we ban from communicating with us
BAN="216.98.48.19"

# westell adsl modem diagnostic info
ADSL_DIAG="YES"

# uids denied access to the internal network
LIMUIDS=""

IPT="iptables"
IPP="/sbin/ip"

MODULES="ip_nat_ftp ip_nat_irc"

if [ "USE_IPV6" = "YES" ]; then
	TTLAR=`echo $EXT_IP | tr "." " "`
	EXT_IPV6=`printf "2002:%02x%02x:%02x%02x::1" $TTLAR`
fi

# save our current external ip in case it changes...
echo $EXT_IP > /etc/OLDEXTIP

if [ "$1" = "start" ]; then

	for i in ${MODULES}
	do
		modprobe $i
	done

	# reset tables and delete user chains
	$IPT -F
	$IPT -X
	$IPT -t nat -F
	$IPT -t nat -X
	$IPT -t mangle -F
	$IPT -t mangle -X

	# Deny input unless explicitly accepted.
	$IPT -P INPUT DROP
	$IPT -P FORWARD DROP
	$IPT -P OUTPUT ACCEPT

	###### CHAINS ######

	# Drop packets coming from obviously bogus IPs
	$IPT -N drop-bogons
        for x in ${BOGONS}
        do
                $IPT -A drop-bogons -s $x -j DROP
        done

	# Drop potentially malicious ICMP
	$IPT -N drop-icmp
	for x in ${BADICMP}
	do
		$IPT -A drop-icmp -p icmp --icmp-type $x -j REJECT
	done

	# Drop invalid connections that may be attacks
	$IPT -N drop-invalid
	# reject new tcp packets that aren't SYN packets
	$IPT -A drop-invalid -p tcp ! --syn -m state --state NEW \
				-j REJECT --reject-with tcp-reset
	# reject syn/ack packets to prevent sequence # spoofing on other hosts
	$IPT -A drop-invalid -p tcp --tcp-flags SYN,ACK SYN,ACK -m state \
				--state NEW -j REJECT --reject-with tcp-reset
	$IPT -A drop-invalid -p tcp --tcp-flags ALL FIN,URG,PSH -m state \
				--state NEW -j REJECT --reject-with tcp-reset
	$IPT -A drop-invalid -p tcp --tcp-flags ALL NONE -m state \
				--state NEW -j REJECT --reject-with tcp-reset
	$IPT -A drop-invalid -p tcp --tcp-flags SYN,RST SYN,RST -m state \
				--state NEW -j REJECT --reject-with tcp-reset
	$IPT -A drop-invalid -p tcp --tcp-flags SYN,FIN SYN,FIN -m state \
				--state NEW -j REJECT --reject-with tcp-reset
	$IPT -A drop-invalid -m state --state INVALID -j REJECT

	# allow a slight burst for app-marked min-delay, but throttle by size
	$IPT -t mangle -N tosfix
	$IPT -t mangle -A tosfix -p tcp -m length --length 0:512 -j RETURN
	$IPT -t mangle -A tosfix -m limit --limit 2/s --limit-burst 10 \
		-j RETURN
	$IPT -t mangle -A tosfix -j TOS --set-tos Maximize-Throughput

	# Maximize the speed of ACKs, so long as they're not too long.
	$IPT -t mangle -N check-acks
	$IPT -t mangle -A check-acks -m tos --tos ! Normal-Service -j RETURN
	$IPT -t mangle -A check-acks -p tcp -m length --length 0:128 \
		-j TOS --set-tos Minimize-Delay
	$IPT -t mangle -A check-acks -p tcp -m length --length 128: \
		-j TOS --set-tos Maximize-Throughput

	# Traffic priority tagging
	$IPT -t mangle -N mark-qos
	for x in ${TCPLL}
	do
		$IPT -t mangle -A mark-qos -p tcp --dport $x -j TOS \
			--set-tos Minimize-Delay
	done
	for x in ${TCPBULK}
	do
		$IPT -t mangle -A mark-qos -p tcp --sport $x -j TOS \
			--set-tos Maximize-Throughput
	done
	for x in ${TCPMINCOST}
	do
		$IPT -t mangle -A mark-qos -p tcp --sport $x -j TOS \
			--set-tos Minimize-Cost
	done

	for x in ${UDPLL}
	do
		$IPT -t mangle -A mark-qos -p udp --dport $x -j TOS \
			--set-tos Minimize-Delay
	done
	for x in ${UDPBULK}
	do
		$IPT -t mangle -A mark-qos -p udp --sport $x -j TOS \
			--set-tos Maximize-Throughput
	done
	for x in ${UDPMINCOST}
	do
		$IPT -t mangle -A mark-qos -p udp --sport $x -j TOS \
			--set-tos Minimize-Cost
	done

	##### END CHAINS #####

	# Clamp MSS on external interfaces if required.
	if [ "$CLAMP_MSS" = "YES" ]; then
		iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
			-o $EXT_IF -m tcpmss --mss 1452:65535 \
			-j TCPMSS --clamp-mss-to-pmtu
	fi

	# We trust things that don't come from outside.
	for x in $INT_IF $TUN_IF lo
	do
		$IPT -A INPUT -i $x -j ACCEPT
		$IPT -A FORWARD -i $x -j ACCEPT
	done

        # Drop invalid packets ASAP.
        $IPT -A INPUT -j drop-invalid
	$IPT -A FORWARD -j drop-invalid

        # Deny bogus networks as input from external interfaces.
        $IPT -A INPUT -i $EXT_IF -j drop-bogons
        $IPT -A FORWARD -i $EXT_IF -j drop-bogons

        # We don't like these hosts.
        for x in ${BAN}
        do
                $IPT -A INPUT -i $EXT_IF -s $x -j REJECT
        done

	# Detect and drop potentially malicious ICMP
	$IPT -A INPUT -j drop-icmp
	$IPT -A FORWARD -j drop-icmp

	# Trust state data on the Big Three Protocols
	$IPT -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
	$IPT -A INPUT -p udp -m state --state ESTABLISHED -j ACCEPT
	$IPT -A INPUT -p icmp -m state --state ESTABLISHED,RELATED -j ACCEPT
	if [ "$USE_IPV6" = "YES" ]; then
		$IPT -A INPUT -p ipv6 -m state --state ESTABLISHED,RELATED \
				-j ACCEPT
	fi

	# Allow ICMP echo requests.
	if [ "$ALLOW_PING" = "YES" ]; then
		$IPT -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
		$IPT -A FORWARD -p icmp --icmp-type time-exceeded -m state --state ESTABLISHED,RELATED -j ACCEPT
	fi

        # pass in DHCP, keeping state
	if [ "$USE_DHCP" = "YES" ]; then
	        $IPT -A INPUT -i $EXT_IF -p udp -s $DHCPSIP --dport bootpc \
		-m state --state NEW -j ACCEPT
	fi

	# allow diagnostic multicast packets from the westell modem
	if [ "$ADSL_DIAG" = "YES" ]; then
		$IPT -A INPUT -i $BRIDGE_IF -p udp -d 224.73.193.62/32 \
			-s 192.168.1.1/32 --sport 1875 --dport 1875 -j ACCEPT
	fi

	# rate limit ssh connections if necessary
	if [ "$LIMIT_SSH" = "YES" ]; then
	    for i in $EXT_IF $WIFI_IF; do
		$IPT -A INPUT -i $i -p tcp --dport ssh -m state --state \
			ESTABLISHED,RELATED -j ACCEPT
		$IPT -A INPUT -i $i -p tcp --dport ssh -m state --state \
			NEW -m limit --limit 3/min --limit-burst 3 -j ACCEPT
	    done
	fi

	# Allow DHCP and OpenVPN requests on wifi
	$IPT -A INPUT -i $WIFI_IF -p udp --dport 67:68 --sport 67:68 -j ACCEPT
	$IPT -A INPUT -i $WIFI_IF -p udp  --dport 1194 -j ACCEPT

	# Allow some services to be accessed from the outside.
	for x in ${TCPSERV}
	do
		$IPT -A INPUT -p tcp --dport $x -m state --state NEW -j ACCEPT
	done
	for x in ${UDPSERV}
	do
		$IPT -A INPUT -p udp --dport $x -m state --state NEW -j ACCEPT
	done

	# Ban remote accounts from accessing the internal network.
	for x in ${LIMUIDS}
	do
		$IPT -A OUTPUT -m owner --uid-owner $x -o $INT_IF -j REJECT \
			--reject-with icmp-port-unreachable
	done

	# LAN NAT rules
	$IPT -A FORWARD -i $INT_IF -s $INT_NET -m state \
		--state NEW,ESTABLISHED -j ACCEPT
	$IPT -A FORWARD -i ! $INT_IF -o $INT_IF -d $INT_NET -m state \
		--state ESTABLISHED -j ACCEPT
	$IPT -t nat -A POSTROUTING -o $EXT_IF -s $INT_NET -j MASQUERADE

	# VPN Tunnel rules
	$IPT -A FORWARD -i $TUN_IF -s $TUN_NET -m state \
		--state NEW,ESTABLISHED -j ACCEPT
	$IPT -A FORWARD -i ! $TUN_IF -o $TUN_IF -d $TUN_NET -m state \
		--state ESTABLISHED -j ACCEPT
	$IPT -t nat -A POSTROUTING -o $EXT_IF -s $TUN_NET -j MASQUERADE

	# Some broken places don't like ECN; we can disable ECN for them.
	for x in ${BADECN}
	do
		$IPT -t mangle -A POSTROUTING -p tcp -o $EXT_IF -d $x \
			-j ECN --ecn-tcp-remove
	done

	# Sanitize packets that are marked low cost by apps
	$IPT -t mangle -A POSTROUTING -p tcp -o $EXT_IF \
		-m tos --tos Minimize-Delay -j tosfix

	# Prioritize ACKs, so long as they are not too long
	$IPT -t mangle -A POSTROUTING -p tcp -m tcp \
		--tcp-flags SYN,RST,ACK ACK -o $EXT_IF -j check-acks

	# Prioritize TCP SYN and RST
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -p tcp -m tcp \
		--tcp-flags ! SYN,RST,ACK ACK -j TOS --set-tos Minimize-Delay

	# Deprioritize long-lived, high bandwidth connections
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -p tcp -m connbytes \
		--connbytes 500000: --connbytes-dir both --connbytes-mode \
		bytes -j TOS --set-tos Minimize-Cost

	# Initialize custom priority chain
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -j mark-qos

	# map TOS bit onto classes
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -m tos --tos Minimize-Delay \
		-j CLASSIFY --set-class 1:10
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -m tos --tos \
		Maximize-Throughput -j CLASSIFY --set-class 1:20
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -m tos --tos \
		Minimize-Cost -j CLASSIFY --set-class 1:30
	$IPT -t mangle -A POSTROUTING -o $EXT_IF -m tos --tos \
		Maximize-Reliability -j CLASSIFY --set-class 1:30

	if [ "USE_IPV6" = "YES" ]; then
		$IPP tunnel add name sit1 mode sit remote any \
			local $EXT_IP ttl 64
		$IPP link set dev sit1 up 
		$IPP -6 addr add $EXT_IPV6/64 dev sit1 
		$IPP -6 route add 2000::/3 via ::192.88.99.1 dev sit1 metric 1
	fi
	# run the QoS shaper
	#/etc/init.d/shaper start

	# re-init forwarding rules
	/etc/forwarding/redoall

elif [ "$1" = "stop" ]; then
	#/etc/init.d/shaper stop
	/etc/forwarding/clearall

	# reset tables and delete user chains
	$IPT -F
	$IPT -X
	$IPT -t nat -F
	$IPT -t nat -X
	$IPT -t mangle -F
	$IPT -t mangle -X

	$IPT -P INPUT ACCEPT
	$IPT -P FORWARD ACCEPT
	$IPT -P OUTPUT ACCEPT

	if [ "USE_IPV6" = "YES" ]; then
		$IPP -6 route flush dev sit1
		$IPP link set dev sit1 down
		$IPP tunnel del sit1
	fi

elif [ "$1" = "restart" ]; then
	$0 stop
	$0 start
fi

