#!/bin/sh /etc/rc.common

START=95
STOP=15
SERVICE_DAEMONIZE=1
NAME=xclient
LOCK_FILE=/var/lock/$NAME.lock
LOG_FILE=/var/log/$NAME.log
XRAY_LOG_FILE=/usr/share/$NAME/$NAME.txt
REAL_LOG=$XRAY_LOG_FILE
TMP_PATH=/var/etc/$NAME
TMP_BIN_PATH=$TMP_PATH/bin
TMP_DNSMASQ_PATH=/tmp/dnsmasq.d/dnsmasq-$NAME.d
XBIN=/usr/bin/xray
tcp_config_file=
enable_socks=0
udp_config_file=
local_config_file=
ARG_UDP=
dns_port="5335"            #dns port
server_count=0
redir_tcp=0
redir_udp=0
local_enable=0
pdnsd_enable_flag=0
switch_server=$1
CRON_FILE=/etc/crontabs/root
EXTRA_COMMANDS='reset'
EXTRA_HELP="        reset   Reset to default settings"
#extra_command "reset" "Reset to default settings"
PS="/bin/busybox ps"

uci_get_by_name() {
	local ret=$(uci get $NAME.$1.$2 2>/dev/null)
	echo ${ret:=$3}
}

uci_get_by_type() {
	local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
	echo ${ret:=$3}
}

uci_set_by_name() {
	uci set $NAME.$1.$2=$3 2>/dev/null
	uci commit $NAME
}

uci_set_by_type() {
	uci set $NAME.@$1[0].$2=$3 2>/dev/null
	uci commit $NAME
}

uci_get_by_cfgid() {
	local ret=$(uci show $NAME.@$1[0].$2 | awk -F '.' '{print $2}' 2>/dev/null)
	echo ${ret:=$3}
}

get_host_ip() {
	local host=$(uci_get_by_name $1 server)
	local ip=$host
	if [ -z "$(echo $host | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}")" ]; then
		if [ "$host" == "${host#*:[0-9a-fA-F]}" ]; then
			ip=$(resolveip -4 -t 3 $host | awk 'NR==1{print}')
			[ -z "$ip" ] && ip=$(uclient-fetch -q -O- http://119.29.29.29/d?dn=$host | awk -F ';' '{print $1}')
		fi
	fi
	[ -z "$ip" ] || uci_set_by_name $1 ip $ip
	echo ${ip:="$(uci_get_by_name $1 ip "ERROR")"}
}

clean_log() {
	local logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
	[ "$logsnum" -gt 1000 ] && {
		echo "$(date "+%Y-%m-%d %H:%M:%S") The log file is too long, clear it！" >$LOG_FILE
	}
}

echolog() {
	local d="$(date "+%Y-%m-%d %H:%M:%S")"
	echo -e "$d: $*" >>$LOG_FILE
}

add_cron() {
	touch $CRON_FILE
	sed -i '/xclient.log/d' $CRON_FILE
	[ $(uci get xclient.config.auto_update_servers) > 0 ] && echo "0 */$(uci get xclient.config.auto_update_servers) * * * /usr/bin/lua /usr/share/xclient/subscribe.lua >$LOG_FILE" >>$CRON_FILE
	crontab $CRON_FILE
}

del_cron() {
	touch $CRON_FILE
	sed -i '/xclient.log/d' $CRON_FILE
	crontab $CRON_FILE
	clean_log
}

set_lock() {
	exec 1000>"$LOCK_FILE"
	flock -xn 1000
}

unset_lock() {
	flock -u 1000
	rm -rf "$LOCK_FILE"
}

unlock() {
	failcount=1
	while [ "$failcount" -le 10 ]; do
		if [ -f "$LOCK_FILE" ]; then
			let "failcount++"
			sleep 1s
			[ "$failcount" -ge 10 ] && unset_lock
		else
			break
		fi
	done
}

_exit() {
	local rc=$1
	unset_lock
	exit ${rc}
}

first_type() {
	type -t -p "/bin/${1}" -p "${TMP_BIN_PATH}/${1}" -p "${1}" "$@" | head -n1
}

ln_start_bin() {
	local file_func=${1}
	local ln_name=${2}
	shift 2
	if [ "${file_func%%/*}" != "${file_func}" ]; then
		[ ! -L "${file_func}" ] && {
			ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1
			file_func="${TMP_BIN_PATH}/${ln_name}"
		}
		[ -x "${file_func}" ] || echolog "$(readlink ${file_func}) no execute permission，Unable to start：${file_func} $*"
	fi
	#echo "${file_func} $*" >&2
	[ -x "${file_func}" ] || {
		echolog "Can't find ${file_func}，Unable to start..."
		echolog "-----------end------------"
		_exit 2
	}
	${file_func:-echolog "  - ${ln_name}"} "$@" >/dev/null 2>&1 &
}

start_dns() {
	local xclient_dns="$(uci_get_by_type global pdnsd_enable 0)"
	local dnsstr="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)"
	local dnsserver=$(echo "$dnsstr" | awk -F ':' '{print $1}')
	local dnsport=$(echo "$dnsstr" | awk -F ':' '{print $2}')
	start_pdnsd() {
		local usr_dns="$1"
		local usr_port="$2"
		if [ ! -f "$TMP_PATH/pdnsd/pdnsd.cache" ]; then
			mkdir -p $TMP_PATH/pdnsd
			touch $TMP_PATH/pdnsd/pdnsd.cache
			chown -R nobody:nogroup $TMP_PATH/pdnsd
		fi
		cat <<-EOF >$TMP_PATH/pdnsd.conf
			global{
			perm_cache=1024;
			cache_dir="$TMP_PATH/pdnsd";
			pid_file="/var/run/pdnsd.pid";
			run_as="nobody";
			server_ip=127.0.0.1;
			server_port=$dns_port;
			status_ctl=on;
			query_method=tcp_only;
			min_ttl=1h;
			max_ttl=1w;
			timeout=10;
			neg_domain_pol=on;
			proc_limit=2;
			procq_limit=8;
			par_queries=1;
			}
			server{
			label="xclient-usrdns";
			ip=$usr_dns;
			port=$usr_port;
			timeout=6;
			uptest=none;
			interval=10m;
			purge_cache=off;
			}
		EOF
		ln_start_bin $(first_type pdnsd) pdnsd -c $TMP_PATH/pdnsd.conf
	}
	if [ "$xclient_dns" != "0" ]; then
		local dns2socks_port=$(uci_get_by_type global dns2socks_port)
		ipset add xclient_spec_wan_ac $dnsserver nomatch 2>/dev/null 
		case "$xclient_dns" in
		1)
			start_pdnsd $dnsserver $dnsport
			pdnsd_enable_flag=1
			;;
		2)
			ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $dns2socks_port xclient-dns
			ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$dns2socks_port $dnsserver:$dnsport 127.0.0.1:$dns_port -q
			pdnsd_enable_flag=2
			;;
		esac
	fi
}

gen_config_file() {
	case "$2" in
	1)
		local protocol="redir"
		config_file=$tcp_config_file
		;;
	2)
		local protocol="redir"
		config_file=$udp_config_file
		;;
	4)
		local protocol="socks"
		config_file=$local_config_file
		;;
	esac
	
	lua /usr/share/xclient/gen_config.lua $1 $mode $3 $4 $protocol $5 $s5>$config_file

	sed -i 's/\\//g' $TMP_PATH/*-xclient-*.json
}

start_udp() {
	redir_udp=1
    local type=$(uci_get_by_name $UDP_RELAY_SERVER protocol)
	local udp_port=$(uci_get_by_type global udp_port)
	case "$type" in
	shadowsocks-plugin)
		gen_config_file $UDP_RELAY_SERVER 2 $udp_port 0 $type
		ss_program="$(first_type sslocal ss-redir)"
		ln_start_bin $ss_program ss-redir -c $udp_config_file > $XRAY_LOG_FILE 2>&1 &
		echolog "UDP TPROXY Relay:$(get_name ss) Started!"
		;;
    shadowsocks|vless|vmess|trojan)
		gen_config_file $UDP_RELAY_SERVER 2 $udp_port 0 $type
		sed -i "2i\  \"stats\": {}," $udp_config_file 2>/dev/null
		$XBIN -config $udp_config_file > $XRAY_LOG_FILE 2>&1 &
		echolog "UDP TPROXY Relay:$($(first_type "xray" "xray") -version | head -1) Started!"
		;;
	esac
}

start_local() {
	[ "$LOCAL_SERVER" = "nil" ] && return 1
	local socks_port=$(uci_get_by_type global socks5_port)
	local type=$(uci_get_by_name $LOCAL_SERVER protocol)
	case "$type" in
	shadowsocks-plugin)
		gen_config_file $LOCAL_SERVER 4 $socks_port 0 $type
		ss_program="$(first_type sslocal ss-local)"
		ln_start_bin $ss_program ss-local -c $local_config_file > $XRAY_LOG_FILE 2>&1 &
		echolog "Global_Socks5:$(get_name ss) Started!"
		;;
    shadowsocks|vless|vmess|trojan)
		if [ "$_local" == "2" ]; then
			gen_config_file $LOCAL_SERVER 4 0 $socks_port 1 $type
			sed -i "2i\  \"stats\": {}," $local_config_file 2>/dev/null
			$XBIN -config $local_config_file > $XRAY_LOG_FILE 2>&1 &
			echolog "Global_Socks5:$($(first_type "xray" "xray") -version | head -1) Started!"
		fi
		;;
	esac
	local_enable=1
	return 0
}

Start_Run() {
	if [ "$_local" == "1" ]; then
		local socks_port=$(uci_get_by_type global socks5_port)
		tcp_config_file=$TMP_PATH/local-xclient-retcp.json
		[ "$mode" == "tcp,udp" ] && tcp_config_file=$TMP_PATH/local-udp-xclient-retcp.json
	fi
	local type=$(uci_get_by_name $GLOBAL_SERVER protocol)
	local tcp_port=$(uci_get_by_type global tcp_port)
	s5=""
	case "$type" in
	shadowsocks-plugin)
		gen_config_file $GLOBAL_SERVER 1 $tcp_port $socks_port 0 $type
		ss_program="$(first_type sslocal ss-redir)"
		ln_start_bin "$ss_program" ss-redir -c $tcp_config_file > $XRAY_LOG_FILE 2>&1 &
		echolog "Main node:$(get_name $type) $threads Threads Started!"
		;;
    shadowsocks|vless|vmess|trojan)
		gen_config_file $GLOBAL_SERVER 1 $tcp_port $socks_port 0 $type
		sed -i "2i\  \"stats\": {}," $tcp_config_file 2>/dev/null
		$XBIN -config $tcp_config_file > $XRAY_LOG_FILE 2>&1 &
		echolog "Main node:$($(first_type xray xray) -version | head -1) Started!"
		;;
	esac
	redir_tcp=1
	return 0
}

load_config() {
	if [ -z "$switch_server" ]; then
		GLOBAL_SERVER=$(uci_get_by_type global global_server nil)
	else
		GLOBAL_SERVER=$switch_server
	fi
	LOCAL_SERVER=$(uci_get_by_type global socks5_server nil)
	if [ "$GLOBAL_SERVER" == "nil" ]; then
		mode="tcp,udp"
		_local="2"
		local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
		start_local
		return 1
	fi
	UDP_RELAY_SERVER=$(uci_get_by_type global udp_relay_server nil)
	tcp_config_file=$TMP_PATH/tcp-only-xclient-retcp.json
	
	case "$UDP_RELAY_SERVER" in
	nil)
		mode="tcp"
		;;
	$GLOBAL_SERVER | same)
		mode="tcp,udp"
		tcp_config_file=$TMP_PATH/tcp-udp-xclient-retcp.json
		ARG_UDP="-u"
		UDP_RELAY_SERVER=$GLOBAL_SERVER
		;;
	*)
		mode="udp"
		udp_config_file=$TMP_PATH/udp-only-xclient-reudp.json
		ARG_UDP="-U"
		start_udp
		mode="tcp"
		;;
	esac
	case "$LOCAL_SERVER" in
	nil)
		_local="0"
		;;
	$GLOBAL_SERVER | same)
		_local="1"
		LOCAL_SERVER=$GLOBAL_SERVER
		local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
		start_local
		local_enable=0
		;;
	*)
		_local="2"
		s5="none"
		local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
		start_local
		;;
	esac
	return 0
}
 
check_server() {
	ENABLE_SERVER=$(uci_get_by_type global global_server nil)
	if [ "$ENABLE_SERVER" == "nil" ]; then
		return 1
	else
		local STYPE=$(uci_get_by_name $ENABLE_SERVER type nil)
		if [ "$STYPE" == "nil" ]; then
			local CFGID=$(uci_get_by_cfgid servers type nil)
			if [ "$CFGID" == "nil" ]; then
				uci_set_by_type global global_server 'nil'
			else
				uci_set_by_type global global_server $CFGID
			fi
			/etc/init.d/xclient restart
		fi
	fi
}



start_monitor() {
	if [ $(uci_get_by_type global monitor_enable 1) == "1" ]; then
		let total_count=redir_tcp+redir_udp+pdnsd_enable_flag
		if [ $total_count -gt 0 ]; then
			service_start /usr/share/xclient/monitor.sh $redir_tcp $redir_udp $pdnsd_enable_flag
		fi
	fi
}

start_rules() {
	local server=$(get_host_ip $GLOBAL_SERVER)
	local tcp_port=$(uci_get_by_type global tcp_port)
	if [ "$redir_udp" == "1" ]; then
		local udp_server=$(get_host_ip $UDP_RELAY_SERVER)
		local udp_port=udp_port=$(uci_get_by_type global udp_port)
	fi
	
	/usr/share/xclient/iptables.sh \
		-s "$server" \
		-l "$tcp_port" \
		-S "$udp_server" \
		-L "$udp_port" \
		-b "$(uci_get_by_type global wan_bp_ips)" \
		-B "$(uci_get_by_type global lan_bp_ips)" \
		-p "$(uci_get_by_type global lan_fp_ips)" \
		-m "$(uci_get_by_type global Interface)" \
		$ARG_UDP
	return $?
}

start() {
    echo "Starting Client" > $REAL_LOG
	set_lock
	echolog "----------starting------------"
	mkdir -p /var/run /var/lock /var/log /tmp/dnsmasq.d $TMP_BIN_PATH $TMP_DNSMASQ_PATH
	echo "conf-dir=${TMP_DNSMASQ_PATH}" >"/tmp/dnsmasq.d/dnsmasq-xclient.conf"
	if load_config; then
	    echo "Generating Client Config" > $REAL_LOG
		Start_Run
		echo "Setting up  iptables rules" > $REAL_LOG
		start_rules
		echo "Enabling Dns" > $REAL_LOG
		start_dns
		add_cron
	fi
	echo "Restarting Dnsmasq " > $REAL_LOG
	/etc/init.d/dnsmasq restart >/dev/null 2>&1
	check_server
	start_monitor
	clean_log
	echolog "-----------Running------------"
	unset_lock
	echo "XClient Started Successfully" >$REAL_LOG
	sleep 1
	echo "XClient is Running" >$REAL_LOG

}

boot() {
    ENABLE_=$(uci_get_by_type global enable 0)
	if [ "$ENABLE_" == "1" ]; then
		mkdir -p /var/run /var/lock /var/log /tmp/dnsmasq.d $TMP_BIN_PATH $TMP_DNSMASQ_PATH
		ulimit -n 65535
		start
	else	
		if pidof xclient >/dev/null; then
		   stop
		fi
	fi
}

stop() {
	echo "Stopping Client" > $REAL_LOG
	unlock
	set_lock
	/usr/share/xclient/iptables.sh -f
	$PS -w | grep -v "grep" | grep monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
	$PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
	killall -q -9 xray-plugin
	rm -f /var/lock/monitor.lock
	if [ -f "/tmp/dnsmasq.d/dnsmasq-xclient.conf" ]; then
		rm -rf /tmp/dnsmasq.d/dnsmasq-xclient.conf $TMP_DNSMASQ_PATH $TMP_PATH/*-xclient-*.json
		/etc/init.d/dnsmasq restart >/dev/null 2>&1
	fi
	del_cron
	unset_lock
	echo "0" > /usr/share/$NAME/logstatus_check >/dev/null 2>&1
	echo "" > $LOG_FILE >/dev/null 2>&1
	echo "XClient Stopped Successfully" > $REAL_LOG
}

reload(){
	stop >/dev/null 2>&1
	sleep 1
	boot >/dev/null 2>&1
}
