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

USE_PROCD=1
START=95
STOP=10

PROG=/usr/bin/fptn-client-cli

append_option() {
	local value
	config_get value config "$1"
	[ -n "$value" ] && procd_append_param command "--$2" "$value"
}

LIST_VALUES=""

collect_list_value() {
	LIST_VALUES="${LIST_VALUES:+$LIST_VALUES,}$1"
}

append_list() {
	LIST_VALUES=""
	config_list_foreach config "$1" collect_list_value
	[ -n "$LIST_VALUES" ] && procd_append_param command "--$2" "$LIST_VALUES"
}

append_bool() {
	local value
	config_get_bool value config "$1" "$3"
	if [ "$value" -eq 1 ]; then
		procd_append_param command "--$2" true
	else
		procd_append_param command "--$2" false
	fi
}

ACCESS_TOKENS=""

collect_token() {
	[ -n "$1" ] && ACCESS_TOKENS="${ACCESS_TOKENS:+$ACCESS_TOKENS }$1"
}

# There may be several tokens: one 'list access_token' each. A single
# 'option access_token', as older versions wrote it, is still understood.
collect_access_tokens() {
	ACCESS_TOKENS=""
	config_list_foreach config access_token collect_token
	if [ -z "$ACCESS_TOKENS" ]; then
		local single
		config_get single config access_token
		collect_token "$single"
	fi
}

GATEWAY_CACHE=/var/run/fptn.gateway

cached_gateway() {
	local saved
	[ -f "$GATEWAY_CACHE" ] || return 0
	saved=$(cat "$GATEWAY_CACHE")
	[ -n "$saved" ] || return 0
	ip route get "$saved" 2>/dev/null | grep -q ' dev ' && echo "$saved"
}

detect_gateway() {
	local result

	result=$(ip -4 route show default 2>/dev/null | grep -v ' dev tun' |
		sed -n 's/.* via \([0-9.]*\).*/\1/p' | head -1)
	[ -z "$result" ] && result=$(ip route 2>/dev/null | grep -v ' dev tun' |
		awk '/^default/{print $3; exit}')
	[ -z "$result" ] && result=$(route -n 2>/dev/null |
		awk '$1=="0.0.0.0" && $8 !~ /^tun/ {print $2; exit}')

	echo "$result"
}

wait_for_gateway() {
	local result waited=0

	result=$(detect_gateway)
	[ -z "$result" ] && result=$(cached_gateway)

	while [ -z "$result" ] && [ "$waited" -lt 10 ]; do
		logger -p daemon.err -t fptn "no default route, waiting for network"
		sleep 5
		waited=$((waited + 5))
		result=$(detect_gateway)
	done

	echo "$result"
}

detect_out_interface() {
	ip route get "$1" 2>/dev/null |
		sed -n 's/.* dev \([^ ]*\).*/\1/p' | head -1
}

VPN_DNS=172.20.0.1

# In coexistence mode the client owns neither routes nor the resolver: a
# transparent proxy in front of it does, and rewriting dnsmasq would fight it.
manages_system_dns() {
	local socks_listen disable_routing
	config_get socks_listen config socks_listen
	config_get_bool disable_routing config disable_routing 0
	[ -n "$socks_listen" ] && return 1
	[ "$disable_routing" -eq 1 ] && return 1
	return 0
}

apply_dns() {
	manages_system_dns || return 0

	local use_fptn_dns
	config_get_bool use_fptn_dns config use_fptn_dns 1
	[ "$use_fptn_dns" -eq 1 ] || return 0

	local dns
	config_get dns config dns

	uci -q revert dhcp
	uci -q delete dhcp.@dnsmasq[0].server
	[ -n "$dns" ] && uci add_list dhcp.@dnsmasq[0].server="$dns"
	uci add_list dhcp.@dnsmasq[0].server="$VPN_DNS"
	uci set dhcp.@dnsmasq[0].noresolv='1'
	/etc/init.d/dnsmasq reload
}

restore_dns() {
	manages_system_dns || return 0

	uci -q revert dhcp
	/etc/init.d/dnsmasq reload
}

start_service() {
	config_load fptn

	restore_dns

	local enabled
	config_get_bool enabled config enabled 0
	if [ "$enabled" -ne 1 ]; then
		logger -t fptn "disabled in /etc/config/fptn, not starting"
		return 0
	fi

	collect_access_tokens
	if [ -z "$ACCESS_TOKENS" ]; then
		logger -p daemon.err -t fptn "access_token is not set, not starting"
		return 1
	fi

	# Coexistence mode with a transparent proxy (ZeroBlock and friends):
	# routes are not ours, so a default route is not required.
	local socks_listen disable_routing
	config_get socks_listen config socks_listen
	config_get_bool disable_routing config disable_routing 0
	[ -n "$socks_listen" ] && disable_routing=1

	local gateway
	gateway=$(wait_for_gateway)
	if [ -z "$gateway" ] && [ "$disable_routing" -ne 1 ]; then
		logger -p daemon.err -t fptn "no default route, not starting"
		return 1
	fi
	[ -n "$gateway" ] && echo "$gateway" > "$GATEWAY_CACHE"

	# Named so that `ubus call service list` and LuCI can find it.
	procd_open_instance fptn
	procd_set_param command "$PROG"
	local token
	for token in $ACCESS_TOKENS; do
		procd_append_param command --access-token "$token"
	done
	[ -n "$gateway" ] && procd_append_param command --gateway-ip "$gateway"

	local iface
	config_get iface config out_network_interface
	[ -z "$iface" ] && [ -n "$gateway" ] && iface=$(detect_out_interface "$gateway")
	if [ -z "$iface" ] && [ "$disable_routing" -ne 1 ]; then
		logger -p daemon.err -t fptn "gateway $gateway is not reachable, not starting"
		return 1
	fi
	[ -n "$iface" ] && procd_append_param command --out-network-interface "$iface"

	# Integration with transparent proxies
	[ "$disable_routing" -eq 1 ] && procd_append_param command --disable-routing
	append_option routing_mark routing-mark
	append_option socks_listen socks-listen
	append_option socks_route_table socks-route-table

	append_option preferred_server preferred-server
	append_option exclude_servers exclude-servers
	append_option max_ping max-ping
	append_option tun_interface_name tun-interface-name
	append_option sni sni
	append_option bypass_method bypass-method
	append_option connection_strategy connection-strategy
	append_option mtu_size mtu-size
	append_option socks_max_sessions socks-max-sessions
	append_option status_listen status-listen
	append_option status_secret status-secret
	append_option probe_interval probe-interval

	append_bool enable_split_tunnel enable-split-tunnel 1
	append_option split_tunnel_mode split-tunnel-mode
	# The domain list comes from a file: two thousand UCI entries would be
	# read over ubus on every LuCI page load and glued into a 35 KB command
	# line. A list left in UCI still works and takes precedence.
	LIST_VALUES=""
	config_list_foreach config split_tunnel_domains collect_list_value
	if [ -z "$LIST_VALUES" ]; then
		local domains_file
		config_get domains_file config split_tunnel_domains_file \
			/etc/fptn/split_tunnel_domains.txt
		if [ -f "$domains_file" ]; then
			LIST_VALUES=$(grep -vE '^[[:space:]]*(#|$)' "$domains_file" \
				| tr '\n' ',' | sed 's/,$//')
		fi
	fi
	[ -n "$LIST_VALUES" ] && \
		procd_append_param command --split-tunnel-domains "$LIST_VALUES"
	append_list exclude_tunnel_networks exclude-tunnel-networks
	append_list include_tunnel_networks include-tunnel-networks
	append_list blacklist_domains blacklist-domains

	procd_set_param respawn 3600 5 0
	# Every proxied session costs two descriptors, and the default soft limit
	# of 1024 runs out within hours of office traffic.
	procd_set_param limits nofile="16384 16384"
	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_close_instance

	apply_dns
}

stop_service() {
	config_load fptn
	restore_dns
}

# Without this rc.common turns `reload` into `restart`, and every edit in
# LuCI would tear the tunnel down.
reload_service() {
	stop
	start
}

service_triggers() {
	procd_add_reload_trigger fptn
	# WAN only: a LAN interface flapping has nothing to do with the tunnel,
	# and restarting on it drops every session behind the router.
	procd_add_interface_trigger "interface.*.up" wan /etc/init.d/fptn restart
}
