#!/bin/sh

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

[ -n "$QNUM" ] || QNUM=59780
[ -n "$NFQWS" ] || NFQWS="$ZAPRET_BASE/nfq/nfqws"
[ -n "$MDIG" ] || MDIG="$ZAPRET_BASE/mdig/mdig"
[ -n "$DESYNC_MARK" ] || DESYNC_MARK=0x40000000
DOMAIN=rutracker.org
CURL_MAX_TIME=5
MIN_TTL=1
MAX_TTL=12
HDRTEMP=/tmp/zapret-hdr.txt
ECHON="echo -n"

DNSCHECK_DNS="8.8.8.8 1.1.1.1 77.88.8.8"
DNSCHECK_DOM="pornhub.com putinhuylo.com rutracker.org nnmclub.to protonmail.com"
DNSCHECK_DIG1=/tmp/dig1.txt
DNSCHECK_DIG2=/tmp/dig2.txt
DNSCHECK_DIGS=/tmp/digs.txt


exists()
{
	which $1 >/dev/null 2>/dev/null
}
killwait()
{
	# $1 - signal (-9, -2, ...)
	# $2 - pid
	kill $1 $2
	# suppress job kill message
	wait $2 2>/dev/null
}

exitp()
{
	local A

	echo
	echo press enter to continue
	read A
	exit $1
}

read_yes_no()
{
	# $1 - default (Y/N)
	local A
	read A
	[ -z "$A" ] || ([ "$A" != "Y" ] && [ "$A" != "y" ] && [ "$A" != "N" ] && [ "$A" != "n" ]) && A=$1
	[ "$A" = "Y" ] || [ "$A" = "y" ] || [ "$A" = "1" ]
}
ask_yes_no()
{
	# $1 - default (Y/N or 0/1)
	# $2 - text
	local DEFAULT=$1
	[ "$1" = "1" ] && DEFAULT=Y
	[ "$1" = "0" ] && DEFAULT=N
	[ -z "$DEFAULT" ] && DEFAULT=N
	$ECHON "$2 (default : $DEFAULT) (Y/N) ? "
	read_yes_no $DEFAULT
}
ask_yes_no_var()
{
	# $1 - variable name for answer : 0/1
	# $2 - text
	local DEFAULT
	eval DEFAULT="\$$1"
	if ask_yes_no "$DEFAULT" "$2"; then
		eval $1=1
	else
		eval $1=0
	fi
}


require_root()
{
	echo \* checking privileges
	[ $(id -u) -ne "0" ] && {
		echo root is required
		exists sudo && exec sudo "$0"
		exists su && exec su -c "$0"
		echo su or sudo not found
		exitp 2
	}
}

IPT()
{
	$IPTABLES -C "$@" >/dev/null 2>/dev/null || $IPTABLES -I "$@"
}
IPT_DEL()
{
	$IPTABLES -C "$@" >/dev/null 2>/dev/null && $IPTABLES -D "$@"
}


check_system()
{
	echo \* checking system

	local UNAME=$(uname)
	[ "$UNAME" = "Linux" ] || {
		echo not supported
		exitp 5
	}
}

check_prerequisites()
{
	echo \* checking prerequisites

	[ -x "$NFQWS" ] && [ -x "$MDIG" ] || {
		echo $NFQWS or $MDIG is not available. run $ZAPRET_BASE/install_bin.sh
		exitp 6
	}

	for prog in iptables ip6tables curl; do
		exists $prog || {
			echo $prog does not exist. please install
			exitp 6
		}
	done
}


hdrfile_http_code()
{
	# $1 - hdr file
	sed -nre '1,1 s/^HTTP\/1\.[0,1] ([0-9]+) .*$/\1/p' "$1"
}
hdrfile_location()
{
	# $1 - hdr file

	# some DPIs return CRLF line ending
	tr -d '\015' <"$1" | sed -nre 's/^[L|l][O|o][C|c][A|a][T|t][I|i][O|o][N|n]:[ \t]*([^ \t]*)[ \t]*$/\1/p'
}
curl_test_http()
{
	# $1 - ip version : 4/6
	# $2 - domain name
	local code loc
	curl -${1}SsD "$HDRTEMP" --max-time $CURL_MAX_TIME $CURL_OPT "http://$2" -o /dev/null 2>&1 || {
		code=$?
		rm -f "$HDRTEMP"
		return $code
	}
	code=$(hdrfile_http_code "$HDRTEMP")
	[ "$code" = 301 -o "$code" = 302 -o "$code" = 307 -o "$code" = 308 ] && {
		loc=$(hdrfile_location "$HDRTEMP")
		[ "${loc#*$2}" = "$loc" ] && {
			echo suspicious redirection to : $loc
			rm -f "$HDRTEMP"
			return 254
		}
	}
	rm -f "$HDRTEMP"
	return 0
}
curl_test_https()
{
	# $1 - ip version : 4/6
	# $2 - domain name

	# prevent using QUIC if available in curl
	curl -${1}Ss --max-time $CURL_MAX_TIME $CURL_OPT --http1.1 "https://$2" -o /dev/null 2>&1 
}

nfqws_ipt_prepare()
{
	# $1 - port
	IPT POSTROUTING -t mangle -p tcp --dport $1 -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK -j NFQUEUE --queue-num $QNUM
}
nfqws_ipt_unprepare()
{
	# $1 - port
	IPT_DEL POSTROUTING -t mangle -p tcp --dport $1 -m mark ! --mark $DESYNC_MARK/$DESYNC_MARK -j NFQUEUE --queue-num $QNUM
}
nfqws_start()
{
	"$NFQWS" --dpi-desync-fwmark=$DESYNC_MARK --qnum=$QNUM "$@" >/dev/null &
}

curl_test()
{
	# $1 - test function
	# $2 - domain
	$1 $IPV $2 && {
		echo '!!!!! AVAILABLE !!!!!'
		return 0
	}
	local code=$?
	if [ $code = 254 ]; then
		echo UNAVAILABLE
	else
		echo UNAVAILABLE code=$code
	fi
	return $code
}
nfqws_curl_test()
{
	# $1 - test function
	# $2 - domain
	# $3,$4,$5, ... - nfqws params
	local code pid testf=$1 dom=$2
	shift
	shift
	echo - checking nfqws "$@"
	nfqws_start "$@"
	pid=$!
	curl_test $testf $dom
	code=$?
	killwait -9 $pid
	return $code
}
check_domain_bypass()
{
	# $1 - test function
	# $2 - domain

	local pid strategy tests='fake'

	if nfqws_curl_test $1 $2 --dpi-desync=split2; then
		strategy='--dpi-desync=split2'
	else
		tests="$tests split fake,split"
	fi
	if nfqws_curl_test $1 $2 --dpi-desync=disorder2; then
		[ -n "$strategy" ] || strategy='--dpi-desync=disorder2'
	else
		tests="$tests disorder fake,disorder"
	fi

	local ttls=$(seq -s ' ' $MIN_TTL $MAX_TTL)
	for desync in $tests; do
		for ttl in $ttls; do
			nfqws_curl_test $1 $2 --dpi-desync=$desync --dpi-desync-ttl=$ttl && {
				[ -n "$strategy" ] || strategy="--dpi-desync=$desync --dpi-desync-ttl=$ttl"
				break
			}
		done
	done

	echo
	if [ -n "$strategy" ]; then
		echo "!!!!! working strategy found : nfqws $strategy !!!!!"
		return 0
	else
		echo 'working strategy not found'
		return 1
	fi
}

check_domain()
{
	# $1 - test function
	# $2 - port
	# $3 - domain

	echo
	echo \* $1 $3

	# in case was interrupted before
	nfqws_ipt_unprepare $2
	killall nfqws 2>/dev/null

	echo "- checking without DPI bypass"
	curl_test $1 $3 && return 0

	echo preparing nfqws redirection
	nfqws_ipt_prepare $2

	check_domain_bypass $1 $3

	echo clearing nfqws redirection
	nfqws_ipt_unprepare $2
}
check_domain_http()
{
	# $1 - domain
	check_domain curl_test_http 80 $1
}
check_domain_https()
{
	# $1 - domain
	check_domain curl_test_https 443 $1
}

ask_params()
{
	echo
	echo NOTE ! this test should be run with zapret or any other bypass software disabled, without VPN
	echo NOTE ! this test will kill all nfqws processes. if you have already set up zapret you will need to restart it after test is complete.

	$ECHON "test this domain [ $DOMAIN ] : "
	local dom
	read dom
	[ -n "$dom" ] && DOMAIN=$dom

	$ECHON "ip protocol version [ 4 ] : "
	read IPV
	[ -n "$IPV" ] || IPV=4
	[ "$IPV" = 4 -o "$IPV" = 6 ] || {
		echo invalid ip version. should be 4 or 6.
		exitp 1
	}
	IPTABLES=iptables
	[ "$IPV" = 6 ] && IPTABLES=ip6tables

	ENABLE_HTTP=1
	ask_yes_no_var ENABLE_HTTP "check http"

	ENABLE_HTTPS=1
	ask_yes_no_var ENABLE_HTTPS "check https"

	IGNORE_CA=0
	CURL_OPT=
	[ "$ENABLE_HTTPS" = "1" ] && {
		echo on limited systems like openwrt CA certificates might not be installed to preserve space
		echo in such a case curl cannot verify server certificate and you should either install ca-bundle or disable verification
		echo however disabling verification will break https check if ISP does MitM attack and substitutes server certificate
		ask_yes_no_var IGNORE_CA "do not verify server certificate"
		[ "$IGNORE_CA" = 1 ] && CURL_OPT=-k
	}
}



pingtest()
{
	ping -c 1 -W 1 $1 >/dev/null
}
dnstest()
{
	# $1 - dns server. empty for system resolver
	nslookup w3.org $1 >/dev/null 2>/dev/null
}
find_working_public_dns()
{
	for dns in $DNSCHECK_DNS; do
		pingtest $dns && dnstest $dns && {
			PUBDNS=$dns
			return 0
		}
	done
	return 1
}
check_dns_spoof()
{
	# $1 - domain
	# $2 - public DNS
	echo $1 | "$EXEDIR/mdig/mdig" --family=4 >"$DNSCHECK_DIG1"
	nslookup $1 $2 | sed -n '/Name:/,$p' | grep ^Address | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' >"$DNSCHECK_DIG2"
	# check whether system resolver returns anything other than public DNS
	grep -qvFf "$DNSCHECK_DIG2" "$DNSCHECK_DIG1"
}
check_dns_cleanup()
{
	rm -f "$DNSCHECK_DIG1" "$DNSCHECK_DIG2" "$DNSCHECK_DIGS" 2>/dev/null
}
check_dns()
{
	local C1 C2

	echo \* checking DNS

	[ -f "$DNSCHECK_DIGS" ] && rm -f "$DNSCHECK_DIGS"

	dnstest || {
		echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access.
		return 1
	}
	echo system DNS is working

	if find_working_public_dns ; then
		echo comparing system resolver to public DNS : $PUBDNS
		for dom in $DNSCHECK_DOM; do
			if check_dns_spoof $dom $PUBDNS ; then
				echo $dom : MISMATCH
				echo -- system resolver :
				cat "$DNSCHECK_DIG1"
				echo -- $PUBDNS :
				cat "$DNSCHECK_DIG2"
				check_dns_cleanup
				echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!!
				echo -- DNS CHANGE OR DNSCRYPT MAY BE REQUIRED
				return 1
			else
				echo $dom : OK
				cat "$DNSCHECK_DIG1" >>"$DNSCHECK_DIGS"
			fi
		done
	else
		echo no working public DNS was found. looks like public DNS blocked.
		for dom in $DNSCHECK_DOM; do echo $dom; done | "$EXEDIR/mdig/mdig" --threads=10 --family=4 >"$DNSCHECK_DIGS"
	fi

	echo checking resolved IP uniqueness for : $DNSCHECK_DOM
	echo censor\'s DNS can return equal result for multiple blocked domains.
	C1=$(wc -l <"$DNSCHECK_DIGS")
	C2=$(sort -u "$DNSCHECK_DIGS" | wc -l)
	[ "$C1" -eq 0 ] &&
	{
		echo -- DNS is not working. It's either misconfigured or blocked or you don't have inet access.
		check_dns_cleanup
		return 1
	}
	[ "$C1" = "$C2" ] ||
	{
		echo system dns resolver has returned equal IPs for some domains checked above \($C1 total, $C2 unique\)
		echo non-unique IPs :
		sort "$DNSCHECK_DIGS" | uniq -d
		echo -- POSSIBLE DNS HIJACK DETECTED. ZAPRET WILL NOT HELP YOU IN CASE DNS IS SPOOFED !!!
		echo -- DNSCRYPT MAY BE REQUIRED
		check_dns_cleanup
		return 1
	}
	echo all resolved IPs are unique
	echo -- DNS looks good
	echo -- NOTE this check is Russia targeted. In your country other domains may be blocked.
	check_dns_cleanup
	return 0
}


sigint()
{
	# make sure we are not in a middle state that impacts connectivity
	echo
	echo terminating...
	nfqws_ipt_unprepare 80
	nfqws_ipt_unprepare 443
	killall nfqws 2>/dev/null
	exit 1
}

trap 'sigint' 2

check_system
check_prerequisites
require_root
check_dns
ask_params

[ "$ENABLE_HTTP" = 1 ] && check_domain_http $DOMAIN
[ "$ENABLE_HTTPS" = 1 ] && check_domain_https $DOMAIN