#!/bin/sh

# Copyright 2015-2024 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.


# May specify custom name servers to override resolv.conf(5)
NAME_SERVERS=
[ -f /etc/default/wireguard-peer-resolver ] && . /etc/default/wireguard-peer-resolver

is_inet4_address() {
	[ "${1#.}" = "$1" ] && [ "${1%.}" = "$1" ] || return
	local ORIG_IFS="$IFS"
	IFS=.
	set -- $1
	IFS="$ORIG_IFS"
	[ $# = 4 ] || return
	local i=1 v
	while [ $i -lt 5 ]; do
		eval "v=\"\${$i}\""
		[ "$v" -le 255 ] && [ "$v" -ge 0 ] || return
		[ "$v" -gt 0 ] && [ "${v#0}" != "$v" ] && return 1
		i=$((i+1))
	done
	[ $1 != 0 ] && return
	[ $2 = 0 ] || return
	[ $3 = 0 ] || return
	[ $4 = 0 ] || return
	true
}

is_inet6_address() {
	printf %s\\n "$1" | grep -Eq '^\[(([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|([0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}|([0-9a-f]{1,4})?::([0-9a-f]{1,4})?)\]$'
}

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
}

unset LC_ALL LC_MESSAGES
unset LANGUAGE

set -f

while true; do
	sleep 60
	#interfaces="`wg show interfaces`" || continue
	#for i in $interfaces; do
	echo
	wg show all latest-handshakes
done | while read -r line; do
	if [ -z "$line" ]; then
		t="`date +%s`"
		continue
	fi
	set -- $line
	[ $# != 3 ] && continue
	interface="$1"
	publickey="$2"
	handshake="$3"
	[ $((t-handshake)) -lt 240 ] && continue
	[ -f "/etc/wireguard/$interface.conf" ] || continue
	section=
	found_peer=
	while read -r line; do case "$line" in
		\[[Pp][Ee][Ee][Rr]\]*)
			[ -n "$found_peer" ] && break
			section=peer
			name=
			port=
			;;
		\[*\]*)
			[ -n "$found_peer" ] && break
			section=
			;;
		?*=*)
			[ "$section" != peer ] && continue
			parse_key_value "$line" || continue
			case "$key" in
				[Pp][Uu][Bb][Ll][Ii][Cc][Kk][Ee][Yy])
					[ "$value" != "$publickey" ] && continue
					found_peer=1
					[ -n "$name" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_domain" ] && break
					;;
				[Ee][Nn][Dd][Pp][Oo][Ii][Nn][Tt])
					name="${value%:*}"
					port="${value##*:}"
					[ -n "$found_peer" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_domain" ] && [ -n "$loop_gre_key" ] && break
					;;
				\#[Gg][Rr][Ee][Tt][Uu][Nn][Nn][Ee][Ll][Ii][Nn][Tt][Ee][Rr][Ff][Aa][Cc][Ee])
					loop_gre_interface=1
					gre_tunnel_interface="$value"
					[ -n "$name" ] && [ -n "$found_peer" ] && [ -n "$loop_gre_domain" ] && [ -n "$loop_gre_key" ] && break
					;;
				\#[Gg][Rr][Ee][Tt][Uu][Nn][Nn][Ee][Ll][Dd][Oo][Mm][Aa][Ii][Nn])
					loop_gre_domain=1
					gre_tunnel_domain="$value"
					[ -n "$found_peer" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_key" ] && break
					;;
				\#[Gg][Rr][Ee][Tt][Uu][Nn][Nn][Ee][Ll][Kk][Ee][Yy])
					loop_gre_key=1
					gre_tunnel_key="$value"
					[ -n "$found_peer" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_interface" ] && [ -n "$loop_gre_domain" ] && break
					;;
			esac
			;;
	esac done < "/etc/wireguard/$interface.conf"
	if [ -n "$found_peer" ] && [ -n "$name" ] && ! is_inet4_address "$name" && ! is_inet6_address "$name" &&
		if [ -z "${name#\`*\`}" ]; then
			command="${name%\`}"
			command="${command#\`}"
			address="`sh -c \"$command\"`"
		elif address="`LANG=C host -4 -t A -- \"$name\" $NAME_SERVERS`" && [ "${address%%* has no A record}" = "$address" ]; then
			address="${address##*
}"
			address="${address##* has address }"
		elif address="`LANG=C host -4 -t AAAA -- \"$name\" $NAME_SERVERS`" && [ "${address%%* has no AAAA record}" = "$address" ]; then
			address="${address##*
}"
			address="${address##* has IPv6 address }"
		else
			false
		fi
	then
		wg set "$interface" peer "$publickey" endpoint "$address:$port"
	fi

	if [ -n "$gre_tunnel_interface" ] && [ -n "$gre_tunnel_domain" ] && ! is_inet4_address "$name" && ! is_inet6_address "$name" &&
		if address="`LANG=C host -4 -t A -- \"$gre_tunnel_domain\" $NAME_SERVERS`" && [ "${address%%* has no A record}" = "$address" ]; then
			address="${address##*
}"
			address="${address##* has address }"
		elif address="`LANG=C host -4 -t AAAA -- \"$gre_tunnel_domain\" $NAME_SERVERS`" && [ "${address%%* has no AAAA record}" = "$address" ]; then
			address="${address##*
}"
			address="${address##* has IPv6 address }"
		else
			false
		fi
	then
		if [ -n "$gre_tunnel_key" ]; then
			ip tunnel change $gre_tunnel_interface mode gre remote $address ttl inherit key $gre_tunnel_key
		else
			ip tunnel change $gre_tunnel_interface mode gre remote $address ttl inherit
		fi
	fi
done
