#!/bin/sh
# requires ip6tables-mod-nat and ipset

clean_tables () {
	echo "Cleaning captive-portal rules"
	for iface in $(uci get pirania.base_config.catch_bridged_interfaces); do
		ebtables -t nat -D PREROUTING -i $iface -j mark --mark-set 0x9124714
	done

	for ipvX in ipv4 ipv6 ; do
		if [ "$ipvX" = "ipv4" ] ; then
			iptables=iptables
			family=inet
			ipaddr=ipaddr
		else
			iptables=ip6tables
			family=inet6
			ipaddr=ip6addr
		fi

		$iptables -t mangle -D PREROUTING -m mark --mark 0x9124714 -j pirania

		for interface in $(uci get pirania.base_config.catch_interfaces); do
			$iptables -t mangle -D PREROUTING -i $interface -j pirania
		done

		$iptables -t nat -D PREROUTING -j pirania
		$iptables -t filter -D FORWARD -j pirania
		for table in mangle nat filter; do
			$iptables -t $table -F pirania
			$iptables -t $table -X pirania
		done
	done
}

clean_sets () {
	ipset flush pirania-auth-macs
	for ipvX in ipv4 ipv6 ; do
		ipset flush pirania-allowlist-$ipvX
	done
}

set_iptables () {
	echo "Apply captive-portal rules"

	append_ipt_rules=$(uci get pirania.base_config.append_ipt_rules 2> /dev/null)
	if [ "$append_ipt_rules" = "1" ] ; then
		AorI="A"
	else
		AorI="I"
	fi

	# Mark every packet from catch_bridged_interfaces to be handled later.
	# bridged interfaces cant be handled by iptables.
	for iface in $(uci get pirania.base_config.catch_bridged_interfaces); do
		ebtables -t nat -$AorI PREROUTING -i $iface -j mark --mark-set 0x9124714
	done

	for ipvX in ipv4 ipv6 ; do
		if [ "$ipvX" = "ipv4" ] ; then
			iptables=iptables
			family=inet
			anygw=$(uci get network.lm_net_br_lan_anygw_if.ipaddr)
		else
			iptables=ip6tables
			family=inet6
			anygw=[$(uci get network.lan.ip6addr | cut -d/ -f1)]
		fi

		### Buildup: create a pirania chain in each table
		for table in mangle nat filter; do
			$iptables -t $table -N pirania
		done

		# Redirect to pirania chain every packet from catch_bridged_interfaces
		if [ -n "$(uci get pirania.base_config.catch_bridged_interfaces)" ] ; then
			$iptables -t mangle -$AorI PREROUTING -m mark --mark 0x9124714 -j pirania
		fi

		# Redirect to pirania chain every packet from catch_interfaces
		for interface in $(uci get pirania.base_config.catch_interfaces); do
			$iptables -t mangle -$AorI PREROUTING -i $interface -j pirania
		done

		# stop processing the chain for authorized macs and allowed ips (so they are accepted)
		$iptables -t mangle -A pirania -m set --match-set pirania-auth-macs src -j RETURN
		$iptables -t mangle -A pirania -m set --match-set pirania-allowlist-$ipvX dst -j RETURN

		# mark other packages to be rejected later
		$iptables -t mangle -A pirania -j MARK --set-mark 0x66/0xff
		# except their dest port is 80, in this case mark to be redirected later
		$iptables -t mangle -A pirania -p tcp -m tcp --dport 80 -j MARK --set-mark 0x80/0xff

		# marked packages reach nat-prerouting table, send them to nat-pirania chain.
		$iptables -t nat -$AorI PREROUTING --jump pirania
	
		# in nat-pirania chain do:
		# send DNS requests, that are not from valid ips or macs, to our own captive portal DNS at 59053
		$iptables -t nat -A pirania -p udp  -m set ! --match-set pirania-allowlist-$ipvX src -m set ! --match-set pirania-auth-macs src --dport 53 -j DNAT --to-destination $anygw:59053
		# redirect packets with dest port 80 to port 59080 of this host (the captive portal page).
		$iptables -t nat -A pirania -p tcp -m tcp -m mark --mark 0x80/0xff -j REDIRECT --to-ports 59080
		
		# Other packets, if intended to be forwarded will reach filter-forward chain, send them to filter-pirania chain.  
		$iptables -t filter -$AorI FORWARD --jump pirania
		# And in there let's reject them with the best suited reject reason.
		$iptables -t filter -A pirania -p tcp -m mark --mark 0x66/0xff -j REJECT --reject-with tcp-reset
		$iptables -t filter -A pirania -m mark --mark 0x66/0xff -j REJECT
	done
}

update_ipsets () {
	# using temporary ipset sets and swaping them so the update
	# implies minimal disturb to the network and a previous clean-up
	# is not needed
	ipset -exist create pirania-auth-macs hash:mac timeout 0
	ipset -exist create pirania-auth-macs-tmp hash:mac timeout 0
	for mac in $(pirania_authorized_macs) ; do
		ipset -exist add pirania-auth-macs-tmp $mac
	done
	ipset swap pirania-auth-macs-tmp pirania-auth-macs
	ipset destroy pirania-auth-macs-tmp

	for ipvX in ipv4 ipv6 ; do
		if [ "$ipvX" = "ipv4" ] ; then
			family=inet
		else
			family=inet6
		fi
		ipset -exist create pirania-allowlist-${ipvX} hash:net family $family
		ipset -exist create pirania-allowlist-${ipvX}-tmp hash:net family $family
		for item in $(uci get pirania.base_config.allowlist_$ipvX); do
			ipset -exist add pirania-allowlist-${ipvX}-tmp $item
		done
		ipset swap pirania-allowlist-${ipvX}-tmp pirania-allowlist-${ipvX}
		ipset destroy pirania-allowlist-${ipvX}-tmp
	done
}

# check if captive-portal is enabled in /etc/config/pirania
enabled=$(uci get pirania.base_config.enabled)

if  [ "$1" = "start" ]; then
	echo "Running captive-portal"
	clean_tables
	update_ipsets
	set_iptables
	exit
elif [ "$1" = "update" ] ; then
	update_ipsets
	exit
elif [ "$1" = "clean" ] || [ "$1" = "stop" ] ; then
	clean_tables
	clean_sets
	exit
elif [ "$enabled" = "1" ]; then
	clean_tables
	update_ipsets
	set_iptables
	exit
else
	echo "Pirania captive-portal is disabled. Try running captive-portal start"
	exit
fi

