#!/bin/sh

### BEGIN INIT INFO
# Provides:          dtc-xen-firewall
# Required-Start:    $remote_fs $all
# Required-Stop:     $remote_fs
# Should-Start:      $local_fs
# Should-Stop:       $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: A small firewall script for your dom0
# Description:       If running in a production environment, you might want
#                    to have a basic firewall running on your dom0 to avoid
#                    having DoS attack. This is not the state-of-the-art, but
#                    just another attempt to make things a bit more smooth.
### END INIT INFO

# To maintainers of this script:
# NEVER a dtc-xen-fw should have an ACCEPT statement, as this would disable the
# Xen anti-spoof rules. Instead, use RETURN to exit the chain.

DESC="DTC-Xen firewall"
NAME="dtc-xen-firewall"

. /lib/lsb/init-functions

if [ -r /lib/init/vars.sh ] ; then
	. /lib/init/vars.sh
fi

IPTABLES=/sbin/iptables

if [ -f /etc/dtc-xen/dtc-xen-firewall.sh ] ; then
	. /etc/dtc-xen/dtc-xen-firewall.sh
fi

if [ -e /etc/dtc-xen/dtc-xen-firewall-config ] ; then
	. /etc/dtc-xen/dtc-xen-firewall-config
fi

if [ -z ${soap_server_allowed_ip} ] ; then
	soap_server_allowed_ip=0.0.0.0
fi

accept_localhost_traffic () {
	${IPTABLES} -A dtc-xen-in -i lo -j ACCEPT
}

soap_server_limit () {
	# Allow only our management server to connect
	if ! [ ${soap_server_allowed_ip} = "0.0.0.0" ] ; then
		${IPTABLES} -A dtc-xen-in -p tcp --dport 8089 ! -s ${soap_server_allowed_ip} -j LOGREJECT
	fi
	if [ -z "${SOAP_ACCEPTING_RATE}" ] ; then
		SOAP_ACCEPTING_RATE=20
	fi
	if [ -z "${SOAP_ACCEPTING_TIME}" ] ; then
		SOAP_ACCEPTING_TIME=5
	fi
	# Rate limit connections to our SOAP server (20 connections per minutes should be more than enough...)
	${IPTABLES} -A dtc-xen-in -p tcp --dport 8089 -m state --state NEW -m recent --set
	${IPTABLES} -A dtc-xen-in -p tcp --dport 8089 -m state --state NEW -m recent --update --seconds ${SOAP_ACCEPTING_TIME} --hitcount ${SOAP_ACCEPTING_RATE} -j LOGREJECT
}

port25_reject () {
	${IPTABLES} -A dtc-xen-in -p tcp --dport 25 -j LOGREJECT
}

call_add_custom_rules () {
	if [ -e /etc/dtc-xen/dtc-xen-firewall-custom-rules ] ; then
		. /etc/dtc-xen/dtc-xen-firewall-custom-rules
		add_custom_rules
	fi
}

limit_ssh_login_rate () {
	if [ -z "${SSH_ACCEPTING_RATE}" ] ; then
		SSH_ACCEPTING_RATE=10
	fi
	if [ -z "${SSH_ACCEPTING_TIME}" ] ; then
		SSH_ACCEPTING_TIME=300
	fi
	if [ -z "${SSH_FORWARDING_RATE}" ] ; then
		SSH_FORWARDING_RATE=5
	fi
	if [ -z "${SSH_FORWARDING_TIME}" ] ; then
		SSH_FORWARDING_TIME=10
	fi

	# Anti DoS SSH : deny ssh for 300 seconds after 4 attempts
	# This can't be too low because of the use of scp
	# For the dom0 first:
	${IPTABLES} -A dtc-xen-in -p tcp --dport 22 -m state --state NEW -m recent --set 
	${IPTABLES} -A dtc-xen-in -p tcp --dport 22 -m state --state NEW -m recent --update --seconds ${SSH_ACCEPTING_TIME} --hitcount ${SSH_ACCEPTING_RATE} -j LOGREJECT
	# The for the domUs:
	${IPTABLES} -A dtc-xen-fw -p tcp --dport 22 -m state --state NEW -m recent --set 
	${IPTABLES} -A dtc-xen-fw -p tcp --dport 22 -m state --state NEW -m recent --update --seconds ${SSH_FORWARDING_TIME} --hitcount ${SSH_FORWARDING_RATE} -j LOGREJECT
}

ping_flood_protect () {
	if [ -z "${PING_ACCEPTING_RATE}" ] ; then
		PING_ACCEPTING_RATE=5
	fi
	if [ -z "${PING_FORWARDING_RATE}" ] ; then
		PING_FORWARDING_RATE=50
	fi

	# Limit for dom0
	${IPTABLES} -A dtc-xen-in -p icmp --icmp-type echo-request -m limit --limit ${PING_ACCEPTING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-in -p icmp --icmp-type echo-request -j LOGDROP
	# There is no reason why a 20 VPS would be ping more than 50 times per seconds
	${IPTABLES} -A dtc-xen-fw -p icmp --icmp-type echo-request -m limit --limit ${PING_FORWARDING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-fw -p icmp --icmp-type echo-request -j LOGDROP
}
syn_flood_protect () {
	if [ -z "${SYN_ACCEPTING_RATE}" ] ; then
		SYN_ACCEPTING_RATE=10
	fi
	if [ -z "${SYN_FORWARDING_RATE}" ] ; then
		SYN_FORWARDING_RATE=100
	fi
	# For dom0
	${IPTABLES} -A dtc-xen-in -p tcp --syn -m limit --limit ${SYN_ACCEPTING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-in -p tcp --syn -j LOGDROP
	# For VPS
	${IPTABLES} -A dtc-xen-fw -p tcp --syn -m limit --limit ${SYN_FORWARDING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-fw -p tcp --syn -j LOGDROP
}

port_scanner_limitation () {
	if [ -z "${GLOB_CONNECT_ACCEPTING_RATE}" ] ; then
		GLOB_CONNECT_ACCEPTING_RATE=10
	fi
	if [ -z "${GLOB_CONNECT_FORWARDING_RATE}" ] ; then
		GLOB_CONNECT_FORWARDING_RATE=1000
	fi
	#Furtive port scanner a bit more annoying...
	${IPTABLES} -A dtc-xen-in -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit --limit ${GLOB_CONNECT_ACCEPTING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-in -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j LOGDROP
	${IPTABLES} -A dtc-xen-fw -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit --limit ${GLOB_CONNECT_FORWARDING_RATE}/s -j RETURN
	${IPTABLES} -A dtc-xen-fw -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j LOGDROP
}

dtcxen_init () {
	# Create the dtc-xen-in chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep dtc-xen-in | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "dtc-xen-inChain" ] ; then
		# Here, both chains and -j dtc-xen-in exist: just flush dtc-xen-in chain
		${IPTABLES} -F dtc-xen-in
	elif [ "${CHAINTEST}" = "Chain" ] ; then
		# Here, only the chain exist, not the -j dtc-xen-in: just add the action
		${IPTABLES} -I INPUT -j dtc-xen-in
		${IPTABLES} -F dtc-xen-in
	else
		# Here, nothing exists, create the chain and redirect to it
		${IPTABLES} --new-chain dtc-xen-in
		${IPTABLES} -I INPUT -j dtc-xen-in
	fi

	# Create the dtc-xen-in chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep dtc-xen-fw | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "dtc-xen-fwChain" ] ; then
		# Here, both chains and -j dtc-xen-in exist: just flush dtc-xen-in chain
		${IPTABLES} -F dtc-xen-fw
	elif [ "${CHAINTEST}" = "Chain" ] ; then
		# Here, only the chain exist, not the -j dtc-xen-in: just add the action
		${IPTABLES} -I FORWARD -j dtc-xen-fw
		${IPTABLES} -F dtc-xen-fw
	else
		# Here, nothing exists, create the chain and redirect to it
		${IPTABLES} --new-chain dtc-xen-fw
		${IPTABLES} -I FORWARD -j dtc-xen-fw
	fi

	# Setup the LOGDROP chain if it didn't exist
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep LOGDROP | head -n 1 | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "Chain" ] ; then
		${IPTABLES} -F LOGDROP
	else
		${IPTABLES} -N LOGDROP
	fi
	${IPTABLES} -A LOGDROP -m limit --limit 1/s -j LOG --log-prefix "LOGDROP: "
	${IPTABLES} -A LOGDROP -j DROP

	# And same with the LOGREJECT chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep LOGREJECT | head -n 1 | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "Chain" ] ; then
	        ${IPTABLES} -F LOGREJECT
	else
		${IPTABLES} -N LOGREJECT
	fi
        ${IPTABLES} -A LOGREJECT -m limit --limit 1/s -j LOG --log-prefix "LOGREJECT: "
        ${IPTABLES} -A LOGREJECT -j REJECT
}

dtcxen_clean () {
	# Delete the dtc-xen-in chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep dtc-xen-in | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "dtc-xen-inChain" ] ; then
		${IPTABLES} -D INPUT -j dtc-xen-in
		${IPTABLES} -F dtc-xen-in
		${IPTABLES} -X dtc-xen-in
	elif [ "${CHAINTEST}" = "Chain" ] ; then
		${IPTABLES} -F dtc-xen-in
		${IPTABLES} -X dtc-xen-in
	fi

	# Delete the dtc-xen-fw chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep dtc-xen-fw | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "dtc-xen-fwChain" ] ; then
		${IPTABLES} -D FORWARD -j dtc-xen-fw
		${IPTABLES} -F dtc-xen-fw
		${IPTABLES} -X dtc-xen-fw
	elif [ "${CHAINTEST}" = "Chain" ] ; then
		${IPTABLES} -F dtc-xen-fw
		${IPTABLES} -X dtc-xen-fw
	fi

	# Delete the LOGDROP chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep LOGDROP | head -n 1 | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "Chain" ] ; then
		${IPTABLES} -F LOGDROP
		${IPTABLES} -X LOGDROP
	fi
	# Delete the LOGREJECT chain
	CHAINTEST=`LC_ALL=C iptables -t filter -L -n | grep LOGREJECT | head -n 1 | awk '{printf $1}'`
	if [ "${CHAINTEST}" = "Chain" ] ; then
		${IPTABLES} -F LOGREJECT
		${IPTABLES} -X LOGREJECT
	fi
}

case "${1}" in
start)
	[ "${VERBOSE}" != no ] && log_daemon_msg "Starting ${DESC}" ${NAME}
	dtcxen_init
	accept_localhost_traffic
	port25_reject
	soap_server_limit
	call_add_custom_rules
	limit_ssh_login_rate
	ping_flood_protect
	syn_flood_protect
	port_scanner_limitation
	[ "${VERBOSE}" != no ] && log_end_msg 0
;;
stop)
	[ "${VERBOSE}" != no ] && log_daemon_msg "Stopping ${DESC}" ${NAME}
	dtcxen_clean
	[ "${VERBOSE}" != no ] && log_end_msg 0
;;
restart|reload|force-reload)
	${0} stop
	${0} start
;;
*)
	echo "Usage: ${0} "'{start|stop|restart|reload}'
	exit 1
esac

exit 0
