#!/usr/bin/env bash

# Debian based OS installation

########################### Change for your purpose ###########################

MYSRCDIR="/home/daniel/fqdn/isloc"		# can be filled with multiple directories, separated
			# with spaces /etc/nftables.d/fqdn/isloc is always present
MYTABLENAME="firewall"	# maybe you call it "filter"?
MYFAMILY="inet"		# possible value "ip|ip6|inet"
LITE="no"		# possible value "yes|no"
#NOIP4=""		# possible value "yes|no|"blank""
#NOIP6="yes"		# possible value "yes|no|"blank""
MODEAI="no"		# possible value "yes|no" for accept incoming
MODEAO="yes"		# possible value "yes|no" for accept outgoing
MODEDI="no"		# possible value "yes|no" for reject incoming
MODEDO="no"		# possible value "yes|no" for reject outgoing

###############################################################################

if [ "$MODEAI" = "yes" ]
then
	MODE+=("ai")
	CHAINRULE+=("fq-acc-i")
	LIST4+=("fq4-acc-i")
	LIST6+=("fq6-acc-i")
fi
if [ "$MODEAO" = "yes" ]
then
	MODE+=("ao")
	CHAINRULE+=("fq-acc-o")
	LIST4+=("fq4-acc-o")
	LIST6+=("fq6-acc-o")
fi
if [ "$MODEDI" = "yes" ]
then
	MODE+=("di")
	CHAINRULE+=("fq-rej-i")
	LIST4+=("fq4-rej-i")
	LIST6+=("fq6-rej-i")
fi
if [ "$MODEDO" = "yes" ]
then
	MODE+=("do")
	CHAINRULE+=("fq-rej-o")
	LIST4+=("fq4-rej-o")
	LIST6+=("fq6-rej-o")
fi

MYTMPDIR="/dev/shm"
SRCDIR="$MYSRCDIR /etc/nftables.d/fqdn/isloc"

if [ `id -u` -ne 0 ]
then
	echo -e "The script $0 needs administrative privileges.\nAborting now."
	exit
fi

MYLITE=`echo $LITE | tr [:upper:] [:lower:]`
MYNOIP4=`echo $NOIP4 | tr [:upper:] [:lower:]`
MYNOIP6=`echo $NOIP6 | tr [:upper:] [:lower:]`
if [ "$MYNOIP4" = "yes" -a "$MYNOIP6" = "yes" ]
then
	echo no IP protocol active, exiting
	exit
fi

function installdig () {
	echo "Please install the package bind9-dnsutils"
	echo "Aborting ..."
	exit
}

dpkg -s bind9-dnsutils >/dev/null 2>&1 || installdig

[ -f $MYTMPDIR/fqdn.nft ] && rm -f $MYTMPDIR/fqdn.nft
[ -f $MYTMPDIR/setfqdn.nft ] && rm -f $MYTMPDIR/setfqdn.nft

function checknftstatus () {
	# I assume, nftables is managed by systemd
	MYSTATUS=`systemctl is-active nftables.service`
	if [ "$MYSTATUS" != "active" ]
	then
		exit
	fi
}

function prepare () {
	if [ "$MYNOIP4" != "yes" ]
	then
		for p4 in ${LIST4[*]}
		do
			nft list set $MYFAMILY $MYTABLENAME $p4 >/dev/null 2>&1
			if [ $? -ne 0 ]
			then
				echo add set $MYFAMILY $MYTABLENAME $p4 {type ipv4_addr . inet_proto . inet_service \; flags interval,timeout \; timeout 315s \; } >> $MYTMPDIR/setfqdn.nft
			fi
		done
	fi

	if [ "$MYNOIP6" != "yes" ]
	then
		for p6 in ${LIST6[*]}
		do
			nft list set $MYFAMILY $MYTABLENAME $p6 >/dev/null 2>&1
			if [ $? -ne 0 ]
			then
				echo add set $MYFAMILY $MYTABLENAME $p6 {type ipv6_addr . inet_proto . inet_service \; flags interval,timeout \; timeout 315s \; } >> $MYTMPDIR/setfqdn.nft
			fi
		done
	fi
}

function checktable () {
	nft list table $MYFAMILY $MYTABLENAME >/dev/null 2>&1
	if [ $? -ne 0 ]
	then
		[ -f $MYTMPDIR/setfqdn.nft ] && sed -i "1 i\ add table $MYFAMILY $MYTABLENAME" $MYTMPDIR/setfqdn.nft
	fi
}

function chainrule () {
	for fr in ${CHAINRULE[*]}
	do
		nft list chain $MYFAMILY $MYTABLENAME $fr >/dev/null 2>&1
		if [ $? -ne 0 ]
		then
			echo add chain $MYFAMILY $MYTABLENAME $fr >> $MYTMPDIR/fqdn.nft
		fi
	done
	### CHECK IF RULE EXISTS!!!!!=============================================================
	if [ "$MYFAMILY" = "inet" -a "$MYNOIP4" != "yes" ] || [ "$MYFAMILY" = "ip" ]
	then
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-acc-i 2>&1 | grep acc-i | grep fq4 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEAI" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-acc-i ip saddr . meta l4proto . th dport @fq4-acc-i ct state new counter accept >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-acc-o 2>&1 | grep acc-o | grep fq4 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEAO" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-acc-o ip daddr . meta l4proto . th dport @fq4-acc-o ct state new counter accept >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-rej-i 2>&1 | grep rej-i | grep fq4 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEDI" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-rej-i ip saddr . meta l4proto . th dport @fq4-rej-i ct state new counter drop >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-rej-o 2>&1 | grep rej-o | grep fq4 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEDO" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-rej-o ip daddr . meta l4proto . th dport @fq4-rej-o ct state new counter drop >> $MYTMPDIR/fqdn.nft
		fi
	fi
	if [ "$MYFAMILY" = "inet" -a "$MYNOIP6" != "yes" ] || [ "$MYFAMILY" = "ip6" ]
	then
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-acc-i 2>&1 | grep acc-i | grep fq6 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEAI" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-acc-i ip6 saddr . meta l4proto . th dport @fq6-acc-i ct state new counter accept >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-acc-o 2>&1 | grep acc-o | grep fq6 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEAO" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-acc-o ip6 daddr . meta l4proto . th dport @fq6-acc-o ct state new counter accept >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-rej-i 2>&1 | grep rej-i | grep fq6 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEDI" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-rej-i ip6 saddr . meta l4proto . th dport @fq6-rej-i ct state new counter drop >> $MYTMPDIR/fqdn.nft
		fi
		MYCNT=`nft list chain $MYFAMILY $MYTABLENAME fq-rej-o 2>&1 | grep rej-o | grep fq6 | wc -l`
		if [ $MYCNT -ne 1 -a "$MODEDO" = "yes" ]
		then
			echo add rule $MYFAMILY $MYTABLENAME fq-rej-o ip6 daddr . meta l4proto . th dport @fq6-rej-o ct state new counter drop >> $MYTMPDIR/fqdn.nft
		fi
	fi
}

function checkcon () {
	DIGCNT=1
	CONCNT=1
	while [ $CONCNT -ne 0 ]
	do
		dig -t soa . >&2>1 /dev/null
		CONCNT=$?
		sleep 1
		DIGCNT=$(($DIGCNT+1))
		if [ $DIGCNT -ge 120 ]
		then
			echo "$1 can not resolving over 2 Minutes, abort"
			exit 1
		fi
	done
}

function createset () {
	IPV4=`dig -t A +nocmd +noall +answer +short $FQDNCHAIN | grep -v '\.$' | sort -u -n -t . -k1,1 -k2,2 -k3,3 -k4,4`
	IPV6=`dig -t AAAA +nocmd +noall +answer +short $FQDNCHAIN | grep -v '\.$' | sort -u`
	if [ "$IPV4" != "" -o "$IPV6" != "" ]
	then
		TTLMIN=`dig +nocmd +noall +answer +ttlid $FQDNCHAIN -t A $FQDNCHAIN -t AAAA | awk '{print $2}' | sort -g | head -1`
		if [ $TTLMIN -lt 290 ]
		then
			TTL=$(($TTLMIN+20))
		else
			TTL=315
		fi
	fi
	#filter foreach, da mehrere IPs rauskommen koennen, format muss nicht geprueft werden.
	for myproto in $MYPROTO
	do
		if [ "$MYNOIP4" = "" ]
		then
			if [ "$MYFAMILY" = "inet" -o "$MYFAMILY" = "ip" ]
			then
#				echo Processing IPv4
				for myip4 in $IPV4
				do
					echo add set $MYFAMILY $MYTABLENAME fq4-$MYSETNORMNAME { type ipv4_addr . inet_proto . inet_service \; flags interval,timeout \; timeout 315s \; }
					if [ "$MYLITE" != "yes" ]
					then
						echo add element $MYFAMILY $MYTABLENAME fq4-$MYSETNORMNAME { $myip4 . $myproto . $EXTRACTPORT }
						echo delete element $MYFAMILY $MYTABLENAME fq4-$MYSETNORMNAME { $myip4 . $myproto . $EXTRACTPORT }
					fi
					echo add element $MYFAMILY $MYTABLENAME fq4-$MYSETNORMNAME { $myip4 . $myproto . $EXTRACTPORT timeout "$TTL"s }
				done >> $MYTMPDIR/fqdn.nft
			fi
		fi
		if [ "$MYNOIP6" = "" ]
		then
			if [ "$MYFAMILY" = "inet" -o "$MYFAMILY" = "ip6" ]
			then
#				echo Processing IPv6
				for myip6 in $IPV6
				do
					echo add set $MYFAMILY $MYTABLENAME fq6-$MYSETNORMNAME { type ipv6_addr . inet_proto . inet_service \; flags interval,timeout \; timeout 315s \; }
					if [ "$MYLITE" != "yes" ]
					then
						echo add element $MYFAMILY $MYTABLENAME fq6-$MYSETNORMNAME { $myip6 . $myproto . $EXTRACTPORT }
						echo delete element $MYFAMILY $MYTABLENAME fq6-$MYSETNORMNAME { $myip6 . $myproto . $EXTRACTPORT }
					fi
					echo add element $MYFAMILY $MYTABLENAME fq6-$MYSETNORMNAME { $myip6 . $myproto . $EXTRACTPORT timeout "$TTL"s }
				done
			fi
		fi
	done >> $MYTMPDIR/fqdn.nft
}

while true
do
	for r in ${MODE[*]}
	do
		case $r in
			ai)
				MYSETNORMNAME="acc-i"
				;;
			ao)
				MYSETNORMNAME="acc-o"
				;;
			di)
				MYSETNORMNAME="rej-i"
				;;
			do)
				MYSETNORMNAME="rej-o"
				;;
		esac

		for sd in $SRCDIR
		do
			#Check directory
			[ ! -d $sd/$r ] && mkdir -p $sd/$r

			#Form checking
			for c in `ls $sd/$r | grep -i -v -E -e ^both[[:digit:]]+$ -e ^tcp[[:digit:]]+$ -e ^udp[[:digit:]]+$ -e wrg`
			do
				mv $sd/$r/$c $sd/$r/WRG_$c
			done
			#We only accept lowercase
			#for d in `ls $sd/$r | grep -i -E -e ^both[[:digit:]]+$ -e ^tcp[[:digit:]]+$ -e ^udp[[:digit:]]+$ | grep -i -v wrg` # all case
			for d in `ls $sd/$r | grep -v [A-Z] | grep -i -E -e ^both[[:digit:]]+$ -e ^tcp[[:digit:]]+$ -e ^udp[[:digit:]]+$` # lowercase
			do
				#MYSETNAME=`echo $d | tr [:upper:] [:lower:]` #normalize all to lowercase
				MYSETNAME=`echo $d`
				EXTRACTPROTO=`echo $MYSETNAME | sed 's/[^a-z]*//g'`
				case $EXTRACTPROTO in
					both)
						MYPROTO="tcp udp"
						;;
					tcp|udp)
						MYPROTO="$EXTRACTPROTO"
						;;
				esac
				EXTRACTPORT=`echo $MYSETNAME | sed 's/[^0-9]*//g' | sed 's/^0*//g'`
				if [ $EXTRACTPORT -gt 65535 ]
				then
					RENAME="WRG_"$MYSETNAME
					mv $sd/$r/$MYSETNAME $sd/$r/$RENAME
				else
					FQDNENTRIES=`cat $sd/$r/$d`
					if [ "$FQDNENTRIES" != '' ]
					then
						for e in $FQDNENTRIES
						do
							FQDNCHAIN=$e
							# output should in one file, to get atomic update
							checkcon
							createset
						done
					fi
				fi
			done
		done
	done
	chainrule
	# upload output
	checktable
	prepare
	[ -f $MYTMPDIR/setfqdn.nft ] && nft -f $MYTMPDIR/setfqdn.nft
	[ -f $MYTMPDIR/fqdn.nft ] && nft -f $MYTMPDIR/fqdn.nft
#	echo done
	sleep 15
	checknftstatus
	# delete output
	[ -f $MYTMPDIR/setfqdn.nft ] && rm -f $MYTMPDIR/setfqdn.nft
	[ -f $MYTMPDIR/fqdn.nft ] && rm -f $MYTMPDIR/fqdn.nft
done

exit 0

