#!/bin/sh

# Copyright 2015-2025 Rivoreo

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
# IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


PROGRAM_DIR="${0%/*}"
[ "$PROGRAM_DIR" = "$0" ] && PROGRAM_DIR="$PWD"

set -e

ports_list_file="`mktemp`"
trap 'rm -f "$ports_list_file"' EXIT
i=8192
while [ $i -lt 65535 ]; do printf %04X\\n $i; i=$((i+1)); done > "$ports_list_file"

print_free_udp_port() {
	sed -En -e 1d -e 's/^ *[0-9]+: [0-9A-F]{8}:([0-9A-F]{4}) .+/\1/p' /proc/net/udp | grep --fixed-strings --line-regexp --invert-match --file - "$ports_list_file" | sort --random-sort | { read n && echo $((0x$n)); }
}

parse_key_value() {
	case "$1" in
		*=*)
			key="${1%%=*}"
			key="${key%%[ 	]*}"
			value="${1#*=}"
			local trim_value
			while trim_value="${value#[ 	]}" && [ "$trim_value" != "$value" ]; do value="$trim_value"; done
			;;
		*\ *|*\	*)
			key="${1%%[ 	]*}"
			value="${1#*[ 	]}"
			;;
		*)
			false
			;;
	esac
}

print_peer_in_tunnel_address() {
	local ORIG_IFS="$IFS"
	IFS=.
	set -- $1
	IFS="$ORIG_IFS"
	[ $# = 4 ] || return
	printf "%s.%s.%s." $1 $2 $3
	echo $(($4+($4%2?-1:1)))
}

rand() {
	eval "$1=\"\`dd if=/dev/urandom count=1 bs=4 2> /dev/null | cksum\`\"" || return
	eval "[ -n \"\$$1\" ]" || return
	eval "$1=\${$1%% *}"
}

random_sleep() {
	local n
	rand n || return
	sleep $((n%($2-$1)+$1)) || true
}

set -f

while true; do
	if ! interfaces="`wg show interfaces`"; then
		sleep 240
		continue
	fi
	set -- $interfaces
	min_sleep=$((3600/$#))
	[ $min_sleep -lt 30 ] && min_sleep=30
	max_sleep=$((115200/$#))
	[ $max_sleep -lt 810 ] && max_sleep=810
	have_matched_interface=
	for i in $@; do
		[ -f "/sys/class/net/$i/flags" ] || continue
		[ -f "/etc/wireguard/$i.conf" ] || continue
		flags="`cat \"/sys/class/net/$i/flags\"`" || continue
		[ $((flags&1)) = 1 ] || continue
		section=
		my_private_key=
		port=
		dyn_port=
		remote_peer_name=
		in_tunnel_ip_addr=
		peer_ssh_port=22
		while read -r line; do case "$line" in
			\[[Ii][Nn][Tt][Ee][Rr][Ff][Aa][Cc][Ee]\]*)
				section=interface
				;;
			\[*\]*)
				section=
				;;
			?*=*)
				[ "$section" != interface ] && continue
				parse_key_value "$line"
				case "$key" in
					[Ll][Ii][Ss][Tt][Ee][Nn][Pp][Oo][Rr][Tt])
						port="$value"
						;;
					[Pp][Rr][Ii][Vv][Aa][Tt][Ee][Kk][Ee][Yy])
						my_private_key="$value"
						;;
					"#RNCN#DynamicBindPort")
						[ "$value" = true ] && dyn_port=1
						;;
					"#RNCN#RemotePeerName")
						remote_peer_name="$value"
						;;
					[Aa][Dd][Dd][Rr][Ee][Ss][Ss])
						addr_without_prefixlen="${value%/31}"
						[ "$addr_without_prefixlen" = "$value" ] && continue
						in_tunnel_ip_addr="`print_peer_in_tunnel_address \"$addr_without_prefixlen\"`"
						;;
					"#RNCN#PeerSSHPort")
						peer_ssh_port="$value"
						;;
				esac
				;;
		esac done < "/etc/wireguard/$i.conf"
		[ -n "$port" ] && [ -z "$dyn_port" ] && continue
		random_sleep 2 480
		if
			if [ -n "$dyn_port" ]; then
				[ -z "$my_private_key" ] && continue
				[ -z "$remote_peer_name" ] && continue
				[ -z "$in_tunnel_ip_addr" ] && continue
				my_public_key="`printf '%s\\n' \"$my_private_key\" | wg pubkey`"
				new_port="`print_free_udp_port`"
				ssh "$in_tunnel_ip_addr" -p "$peer_ssh_port" \
					-l wgcfg -i "$PROGRAM_DIR/../config/id_ecdsa_wgcfg" \
					"rncn set-remote-port $remote_peer_name $my_public_key $new_port" \
					< /dev/null || continue
				wg set "$i" listen-port "$new_port"
			else
				wg set "$i" listen-port 0
			fi
		then
			have_matched_interface=1
			random_sleep $min_sleep $max_sleep
		fi
	done
	[ -z "$have_matched_interface" ] && sleep 120
done
