#!/bin/sh -e

. /lib/rcscripts/sh/error.sh
. /etc/conf.d/iptables || error "Failed to source config file"

DESCRIPTION="packet filters"

# Hardcode these instead of relying on values from the config
IPTABLES=/usr/sbin/iptables
IP6TABLES=/usr/sbin/ip6tables

[ -x "$IPTABLES" ] || IPTABLES=dummytables
[ -x "$IP6TABLES" ] || IP6TABLES=dummytables

dummytables() {
	:
}

FAILED=0

fail() {
	if [ "$FAILED" != "1" ]; then
		FAILED=1
		stop_filter
	fi
	error "Failed to $@"
}

flush() {
	$IPTABLES -F || fail "flush tables!"
	$IP6TABLES -F || fail "flush tables!"
}

set_policy() {
	$IPTABLES -P INPUT $1 || fail "set $1 policy!"
	$IP6TABLES -P INPUT $1 || fail "set $1 policy!"
}

addr_family() {
	local family
	# This function determines AF but does not validate addresses
	# ':' is found in IPv6 addresses but never in IPv4 addresses
	[ $# -eq 2 ] || error "addr_family(): Invalid number of arguments"
	case $1 in
		*[!.:[:xdigit:][:blank:]\/,]*)
			error "Invalid characters in IP address"
			;
		*:*)
			family=6
			;
		*.*)
			family=4
			;
		*)
			error "Invalid ip address '$1'"
			;
	esac
	eval $2=\$family || error "Failed to evaluate IP address family"
}

start_filter() {
	local added_iptables=0 added_ip6tables=0 af=0

	flush

	if [ "$IPTABLES_INPUT_POLICY" = allow ]; then
		FILTER_ACTION=ACCEPT
		set_policy DROP
	else
		FILTER_ACTION=DROP
		set_policy ACCEPT
	fi

	$IPTABLES -A INPUT -i lo -j ACCEPT || fail "ACCEPT loopback!"
	$IP6TABLES -A INPUT -i lo -j ACCEPT || fail "ACCEPT loopback!"

	$IPTABLES -A INPUT -p icmp -j ACCEPT || fail "ACCEPT ICMP!"
	$IP6TABLES -A INPUT -p icmpv6 -j ACCEPT || fail "ACCEPT ICMP!"

	# Accept incoming packets on locally initiated TCP connections.
	$IPTABLES -A INPUT -p tcp ! --syn -j ACCEPT || fail "ACCEPT TCP !SYN!"
	$IP6TABLES -A INPUT -p tcp ! --syn -j ACCEPT || fail "ACCEPT TCP !SYN!"

	for SRC in $IPTABLES_ACCEPT; do
		addr_family "$SRC" af
		if [ $af = 4 ]; then
			if [ "$IPTABLES_LOG_ENABLED" = yes ] &&
			   [ "$FILTER_ACTION" = DROP ]; then
				$IPTABLES -A INPUT -s "$SRC" -j LOG --log-prefix "IP_FILTER: " ||
					fail "LOG source address \"$SRC\"!"
			fi

			added_iptables=1

			$IPTABLES -A INPUT -s "$SRC" -j $FILTER_ACTION ||
				fail "$FILTER_ACTION source address \"$SRC\"!"

		elif [ $af = 6 ]; then
			if [ "$IPTABLES_LOG_ENABLED" = yes ] &&
			   [ "$FILTER_ACTION" = DROP ]; then
				$IP6TABLES -A INPUT -s "$SRC" -j LOG --log-prefix "IP_FILTER: " ||
					fail "LOG source address \"$SRC\"!"
			fi

			added_ip6tables=1

			$IP6TABLES -A INPUT -s "$SRC" -j $FILTER_ACTION ||
				fail "$FILTER_ACTION source address \"$SRC\"!"
		fi
	done

	if [ "$IPTABLES_LOG_ENABLED" = yes ] && [ $added_iptables -eq 1 ] &&
	   [ "$FILTER_ACTION" = ACCEPT ]; then
		$IPTABLES -A INPUT -j LOG --log-prefix "IP_FILTER: " ||
			fail "LOG source address \"$SRC\"!"
	fi

	if [ "$IPTABLES_LOG_ENABLED" = yes ] && [ $added_ip6tables -eq 1 ] &&
	   [ "$FILTER_ACTION" = ACCEPT ]; then
		$IP6TABLES -A INPUT -j LOG --log-prefix "IP_FILTER: " ||
			fail "LOG source address \"$SRC\"!"
	fi

	# drop in-wire ipv4-mapped addresses
	$IP6TABLES -A INPUT -s ::ffff:0:1/96 -j DROP
	$IP6TABLES -A INPUT -d ::ffff:0:1/96 -j DROP
}

stop_filter() {
	flush
	set_policy ACCEPT
}

conditional_start() {
	if [ "$IPTABLES_ENABLED" = yes ]; then
		start_filter
	else
		information "disabled"
		stop_filter
	fi
}

case "$1" in
	start)
		information "Starting $DESCRIPTION"
		conditional_start
		;
	stop)
		information "Stopping $DESCRIPTION"
		stop_filter
		;
	*)
		error "Usage: $0 start|stop"
		;
esac

exit 0