
RNCN_UNPRIVILEGED_USER=rncn-noaccess

set -f

# Usage: update_route_cfg_file <ip4-networks-file> <ip4-router> <ip6-networks-file> <ip6-router> <cfg-dir>
update_route_cfg_file() {
	local net
	if [ -f "$1" ]; then
		printf '# Generated from %s, on ' "$1"
		date "+%F %T %Z"
		if [ -f "$5/ipv4-ra-exclusion" ]; then
			grep --fixed-strings --line-regexp --invert-match --file "$5/ipv4-ra-exclusion" "$1"
		else
			cat "$1"
		fi | while read net; do
			printf 'route %s via %s;\n' "$net" "$2"
		done
		printf '# End of list from %s\n' "$1"
	fi >> "$5/ipv4-ra"
	if [ -n "$4" ] && [ -f "$3" ]; then
		printf '# Generated from %s, on ' "$3"
		date "+%F %T %Z"
		if [ -f "$5/ipv6-ra-exclusion" ]; then
			grep --fixed-strings --line-regexp --invert-match --file "$5/ipv6-ra-exclusion" "$3"
		else
			cat "$3"
		fi | while read net; do
			printf 'route %s via %s;\n' "$net" "$4"
		done
		printf '# End of list from %s\n' "$1"
	fi >> "$5/ipv6-ra"
}

# Usage: generate_fallback_route_filter <networks-file> <excluded-networks-file> <ip-version> <cfg-dir>
generate_fallback_route_filter() {
	cat << EOT
export filter {
	if
		net ~ [
EOT
	local net count=0
	while read net; do
		grep --fixed-strings --line-regexp --quiet --silent "$net" "$2" "$4/ipv$3-ra-exclusion" && continue
		[ $count != 0 ] && echo ,
		count=$((count+1))
		printf "			%s" "$net"
	done < "$1" 2> /dev/null
	cat << EOT

		]
	then {
		ospf_metric1 = 100;
	} else {
		ospf_metric1 = 0;
	}
	accept;
};
EOT
} > "$4/ipv$3-export-to-ospf-filter"

is_inet4_address() {
	[ "${1#.}" = "$1" ] && [ "${1%.}" = "$1" ] || return
	local ORIG_IFS="$IFS"
	IFS=.
	set -- $1
	IFS="$ORIG_IFS"
	[ $# = 4 ] || return
	local i=1 v
	while [ $i -lt 5 ]; do
		eval "v=\"\${$i}\""
		[ "$v" -le 255 ] && [ "$v" -ge 0 ] || return
		[ "$v" -gt 0 ] && [ "${v#0}" != "$v" ] && return 1
		i=$((i+1))
	done
	[ $1 != 0 ] && return
	[ $2 = 0 ] || return
	[ $3 = 0 ] || return
	[ $4 = 0 ] || return
	true
}

is_inet6_address() {
	printf %s\\n "$1" | grep -Eq '^\[(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4})?::([0-9a-f]{1,4})?)\]$'
}

run_filter() {
	sudo -u "$RNCN_UNPRIVILEGED_USER" sh -c "$1"
}

process_url_file() {
	url_file="$1"
	output_dir="$2"
	ip4_routes=""
	ip6_routes=""
	cfg_file_suffix=

	# Read the URL file line by line
	{
		wget --no-verbose --no-cache --inet4-only "$url_file" -O -
		echo
	} | while read -r line; do
		[ -z "$line" ] && continue
		[ "${line#\#}" != "$line" ] && continue
		set -- $line
		case "$1" in
			fallback)
				cfg_file_suffix=-fallback
				;;
			end-fallback)
				cfg_file_suffix=
				;;
			dig)
				if [ $# != 2 ]; then
					printf "Wrong number of arguments in '%s'\\n" "$line" 1>&2
					continue
				fi
				domain="$2"
				dig +short -t A "@$NAME_SERVER" "$domain" | while read record; do
					if is_inet4_address "$record"; then
						printf %s\\n "$record/32"
					fi
				done >> "$output_dir/ipv4-ra$cfg_file_suffix"
				dig +short -t AAAA "@$NAME_SERVER" "$domain" | while read record; do
					if is_inet6_address "$record"; then
						printf %s\\n "$record/128"
					fi
				done >> "$output_dir/ipv6-ra$cfg_file_suffix"
				;;
			cidr)
				if [ $# != 2 ]; then
					printf "Wrong number of arguments in '%s'\\n" "$line" 1>&2
					continue
				fi
				cidr_url="$2"
				{
					wget --no-verbose --no-cache --inet4-only "$cidr_url" -O -
					echo
				} | while read -r cidr_line; do
					case "$cidr_line" in
						*.*.*.*/*)
							printf %s\\n "$cidr_line" >> "$output_dir/ipv4-ra$cfg_file_suffix"
							;;
						*::*/*)
							printf %s\\n "$cidr_line" >> "$output_dir/ipv6-ra$cfg_file_suffix"
							;;
					esac
				done
				;;
			parse-cidr)
				if [ $# -lt 2 ]; then
					printf "Wrong number of arguments in '%s'\\n" "$line" 1>&2
					continue
				fi
				if ! read filter; then
					printf "Failed to read filter command for '%s'\\n" "$line" 1>&2
					break
				fi
				# Multiple URLs can be specified here as a failover mode
				{
					shift
					for url in "$@"; do
						wget --no-verbose --no-cache --inet4-only "$url" -O - && break
					done
				} | run_filter "$filter" | while read -r cidr_line; do
					case "$cidr_line" in
						*.*.*.*/*)
							printf %s\\n "$cidr_line" >> "$output_dir/ipv4-ra$cfg_file_suffix"
							;;
						*::*/*)
							printf %s\\n "$cidr_line" >> "$output_dir/ipv6-ra$cfg_file_suffix"
							;;
					esac
				done
				;;
			*)
				printf "Unrecognized directive '%s'\\n" "$1" 1>&2
				;;
		esac
	done
}

if ! sudo -u "$RNCN_UNPRIVILEGED_USER" true; then
	printf "Warning: can't switch to user '%s' for running filter command\\n" "$RNCN_UNPRIVILEGED_USER" 1>&2
fi

if [ "$#" -ne 1 ]; then
	printf %s\\n "Usage: $0 <output-dir>" 1>&2
	exit 1
fi

tmp_dir="`mktemp -d`"
trap 'rm -rf "$tmp_dir"' EXIT
trap 'rm -rf "$tmp_dir"; exit 255' INT TERM
trap "" HUP

while true; do
	while read line; do
		process_url_file "$line" "$tmp_dir"
	done < /etc/bird/ra.list

	rm -f "$1/ipv4-ra" "$1/ipv6-ra"
	update_route_cfg_file "$tmp_dir/ipv4-ra" "$LOCAL_IP4_ROUTER" "$tmp_dir/ipv6-ra" "$LOCAL_IP6_ROUTER" "$1"
	update_route_cfg_file "$tmp_dir/ipv4-ra-fallback" "$LOCAL_IP4_ROUTER" "$tmp_dir/ipv6-ra-fallback" "$LOCAL_IP6_ROUTER" "$1"
	[ -f "$tmp_dir/ipv4-ra-fallback" ] && generate_fallback_route_filter "$tmp_dir/ipv4-ra-fallback" "$tmp_dir/ipv4-ra" 4 "$1"
	[ -f "$tmp_dir/ipv6-ra-fallback" ] && generate_fallback_route_filter "$tmp_dir/ipv6-ra-fallback" "$tmp_dir/ipv6-ra" 6 "$1"
	rm -f "$tmp_dir/ipv4-ra" "$tmp_dir/ipv6-ra" "$tmp_dir/ipv4-ra-fallback" "$tmp_dir/ipv6-ra-fallback"

	sleep 86000
done
