#!/bin/sh

# create ipset or ipfw table from resolved ip's
# $1=no-update    - do not update ipset, only create if its absent
# $1=clear        - clear ipset

EXEDIR="$(dirname "$0")"
EXEDIR="$(cd "$EXEDIR"; pwd)"

. "$EXEDIR/def.sh"
. "$ZAPRET_BASE/common/fwtype.sh"
. "$ZAPRET_BASE/common/nft.sh"

IPSET_CMD="$TMPDIR/ipset_cmd.txt"
IPSET_SAVERAM_CHUNK_SIZE=20000
IPSET_SAVERAM_MIN_FILESIZE=131072

NFSET_TEMP="$TMPDIR/nfset_temp.txt"
NFSET_SAVERAM_MIN_FILESIZE=16384
NFSET_SAVERAM_CHUNK_SIZE=1000

IPSET_HOOK_TEMP="$TMPDIR/ipset_hook.txt"

while [ -n "$1" ]; do
	[ "$1" = "no-update" ] && NO_UPDATE=1
	[ "$1" = "clear" ] && DO_CLEAR=1
	shift
done


file_extract_lines()
{
	# $1 - filename
	# $2 - from line (starting with 0)
	# $3 - line count
	# awk "{ err=1 } NR < $(($2+1)) { next } { print; err=0 } NR == $(($2+$3)) { exit err } END {exit err}" "$1"
	$AWK "NR < $(($2+1)) { next } { print } NR == $(($2+$3)) { exit }" "$1"
}
ipset_restore_chunked()
{
	# $1 - filename
	# $2 - chunk size
	local pos lines
	[ -f "$1" ] || return
	lines=$(wc -l <"$1")
	pos=$lines
	while [ "$pos" -gt "0" ]; do
		pos=$((pos-$2))
		[ "$pos" -lt "0" ] && pos=0
		file_extract_lines "$1" $pos $2 | ipset -! restore
		sed -i "$(($pos+1)),$ d" "$1"
	done
}


ipset_get_script()
{
	# $1 - ipset name
	sed -nEe "s/^.+$/add $1 &/p"
}
ipset_get_script_from_file()
{
	# $1 - filename
	# $2 - ipset name
	zzcat "$1" | sort -u | ipset_get_script $2
}
ipset_restore()
{
	# $1 - ipset name
	# $2 - filename
	zzexist "$2" || return
	local fsize=$(zzsize "$2")
	local svram=0
	# do not saveram small files. file can also be gzipped
	[ "$SAVERAM" = "1" ] && [ "$fsize" -ge "$IPSET_SAVERAM_MIN_FILESIZE" ] && svram=1

	local T="Adding to ipset $1 "
	[ "$svram" = "1" ] && T="$T (saveram)"
	T="$T : $2"
	echo $T

	if [ "$svram" = "1" ]; then
		ipset_get_script_from_file "$2" "$1" >"$IPSET_CMD"
		ipset_restore_chunked "$IPSET_CMD" $IPSET_SAVERAM_CHUNK_SIZE
		rm -f "$IPSET_CMD"
	else
		ipset_get_script_from_file "$2" "$1" | ipset -! restore
	fi
}
create_ipset()
{
	if [ "$1" -eq "6" ]; then
		FAMILY=inet6
	else
		FAMILY=inet
	fi
	ipset create $2 $3 $4 family $FAMILY 2>/dev/null || {
		[ "$NO_UPDATE" = "1" ] && return 0
	}
	ipset flush $2
	[ "$DO_CLEAR" = "1" ] || {
		for f in "$5" "$6" ; do
			ipset_restore "$2" "$f"
		done
		[ -n "$IPSET_HOOK" ] && $IPSET_HOOK $2 | ipset_get_script $2 | ipset -! restore
	}
	return 0
}

nfset_get_script_multi()
{
	# $1 - set name
	# $2,$3,... - filenames

	# all in one shot. this allows to merge overlapping ranges
	# good but eats lots of RAM

	local set=$1 nonempty N=1 f
	
	shift
	# first we need to make sure at least one element exists or nft will fail
	while :
	do
		eval f=\$$N
		[ -n "$f" ] || break
		nonempty=$(zzexist "$f" && zzcat "$f" 2>/dev/null | head -n 1)
		[ -n "$nonempty" ] && break
		N=$(($N+1))
	done

	[ -n "$nonempty" ] && {
		echo "add element inet $ZAPRET_NFT_TABLE $set {"
		while [ -n "$1" ]; do
			zzexist "$1" && zzcat "$1" | sed -nEe "s/^.+$/&,/p"
			shift
		done
		echo "}"
	}
}
nfset_restore()
{
	# $1 - set name
	# $2,$3,... - filenames

	echo "Adding to nfset $1 : $2 $3 $4 $5"
	local hookfile
	[ -n "$IPSET_HOOK" ] && {
		$IPSET_HOOK $1 >"$IPSET_HOOK_TEMP"
		[ -s "$IPSET_HOOK_TEMP" ] && hookfile=$IPSET_HOOK_TEMP
	}
	nfset_get_script_multi "$@" $hookfile | nft -f -
	rm -f "$IPSET_HOOK_TEMP"
}
create_nfset()
{
	# $1 - family
	# $2 - set name
	# $3 - maxelem
	# $4,$5 - list files

	local policy
	[ $SAVERAM = "1" ] && policy="policy memory;"
	nft_create_set $2 "type ipv${1}_addr; size $3; flags interval; auto-merge; $policy" || {
		[ "$NO_UPDATE" = "1" ] && return 0
		nft flush set inet $ZAPRET_NFT_TABLE $2
	}
	[ "$DO_CLEAR" = "1" ] || {
		nfset_restore $2 $4 $5
	}
	return 0
}

add_ipfw_table()
{
	# $1 - table name
	sed -nEe "s/^.+$/table $1 add &/p" | ipfw -q /dev/stdin
}
populate_ipfw_table()
{
	# $1 - table name
	# $2 - ip list file
	zzexist "$2" || return
	zzcat "$2" | sort -u | add_ipfw_table $1
}
create_ipfw_table()
{
	# $1 - table name
	# $2 - table options
	# $3,$4, ... - ip list files. can be v4,v6 or mixed

	local name=$1
	ipfw table "$name" create $2 2>/dev/null || {
		[ "$NO_UPDATE" = "1" ] && return 0
	}
	ipfw -q table $1 flush
	shift
	shift
	[ "$DO_CLEAR" = "1" ] || {
		while [ -n "$1" ]; do
			echo "Adding to ipfw table $name : $1"
			populate_ipfw_table $name "$1"
			shift
		done
		[ -n "$IPSET_HOOK" ] && $IPSET_HOOK $name | add_ipfw_table $name
	}
	return 0
}

print_reloading_backend()
{
	# $1 - backend name
	local s="reloading $1 backend"
	if [ "$NO_UPDATE" = 1 ]; then
		s="$s (no-update)"
	elif [ "$DO_CLEAR" = 1 ]; then
		s="$s (clear)"
	else
		s="$s (forced-update)"
	fi
	echo $s
}


oom_adjust_high
get_fwtype

if [ -n "$LISTS_RELOAD" ] ; then
	if [ "$LISTS_RELOAD" = "-" ] ; then
		echo not reloading ip list backend
		true
	else
		echo executing custom ip list reload command : $LISTS_RELOAD
		$LISTS_RELOAD
		[ -n "$IPSET_HOOK" ] && $IPSET_HOOK
	fi
else
	case "$FWTYPE" in
		iptables)
			# ipset seem to buffer the whole script to memory
			# on low RAM system this can cause oom errors
			# in SAVERAM mode we feed script lines in portions starting from the end, while truncating source file to free /tmp space
			# only /tmp is considered tmpfs. other locations mean tmpdir was redirected to a disk
			SAVERAM=0
			[ "$TMPDIR" = "/tmp" ] && {
				RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}')
				[ "$RAMSIZE" -lt "110000" ] && SAVERAM=1
			}
			print_reloading_backend ipset
			[ "$DISABLE_IPV4" != "1" ] && {
				create_ipset 4 $ZIPSET hash:net "$IPSET_OPT" "$ZIPLIST" "$ZIPLIST_USER"
				create_ipset 4 $ZIPSET_IPBAN hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN"
				create_ipset 4 $ZIPSET_EXCLUDE hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE"
			}
			[ "$DISABLE_IPV6" != "1" ] && {
				create_ipset 6 $ZIPSET6 hash:net "$IPSET_OPT" "$ZIPLIST6" "$ZIPLIST_USER6"
				create_ipset 6 $ZIPSET_IPBAN6 hash:net "$IPSET_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
				create_ipset 6 $ZIPSET_EXCLUDE6 hash:net "$IPSET_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6"
			}
			true
			;;
		nftables)
			nft_create_table && {
				SAVERAM=0
				RAMSIZE=$($GREP MemTotal /proc/meminfo | $AWK '{print $2}')
				[ "$RAMSIZE" -lt "420000" ] && SAVERAM=1
				print_reloading_backend "nftables set"
				[ "$DISABLE_IPV4" != "1" ] && {
					create_nfset 4 $ZIPSET $SET_MAXELEM "$ZIPLIST" "$ZIPLIST_USER"
					create_nfset 4 $ZIPSET_IPBAN $SET_MAXELEM "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN"
					create_nfset 4 $ZIPSET_EXCLUDE $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE"
				}
				[ "$DISABLE_IPV6" != "1" ] && {
					create_nfset 6 $ZIPSET6 $SET_MAXELEM "$ZIPLIST6" "$ZIPLIST_USER6"
					create_nfset 6 $ZIPSET_IPBAN6 $SET_MAXELEM "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
					create_nfset 6 $ZIPSET_EXCLUDE6 $SET_MAXELEM_EXCLUDE "$ZIPLIST_EXCLUDE6"
				}
				true
			}
			;;
		ipfw)
			print_reloading_backend "ipfw table"
			if [ "$DISABLE_IPV4" != "1" ] && [ "$DISABLE_IPV6" != "1" ]; then
				create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER" "$ZIPLIST6" "$ZIPLIST_USER6"
				create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
				create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE" "$ZIPLIST_EXCLUDE6"
			elif [ "$DISABLE_IPV4" != "1" ]; then
				create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST" "$ZIPLIST_USER"
				create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN" "$ZIPLIST_USER_IPBAN"
				create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE"
			elif [ "$DISABLE_IPV6" != "1" ]; then
				create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT" "$ZIPLIST6" "$ZIPLIST_USER6"
				create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT" "$ZIPLIST_IPBAN6" "$ZIPLIST_USER_IPBAN6"
				create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE" "$ZIPLIST_EXCLUDE6"
			else
				create_ipfw_table $ZIPSET "$IPFW_TABLE_OPT"
				create_ipfw_table $ZIPSET_IPBAN "$IPFW_TABLE_OPT"
				create_ipfw_table $ZIPSET_EXCLUDE "$IPFW_TABLE_OPT_EXCLUDE"
			fi
			true
			;;
		*)
			echo no supported ip list backend found
			true
			;;
		esac

fi
