#!/bin/sh
# OpenVPN netifd proto handler for OpenWrt
# Copyright (C) 2026
# shellcheck disable=SC1091,2046,2091,3043,3060

[ -x /usr/sbin/openvpn ] || exit 0

[ -n "$INCLUDE_ONLY" ] || {
	. /lib/functions.sh
	. /usr/share/openvpn/openvpn.options # OPENVPN_* options
	. ../netifd-proto.sh
	. /usr/share/libubox/jshn.sh
	init_proto "$@"
}

# Helper to DRY up repeated option handling in init/setup
option_builder() {
	# option_builder <action:add|build> <LIST_VAR_NAME> <type>
	local action="$1"; shift
	local list_var="$1"; shift
	local opt_type="$1"; shift
	local f v

	for f in $(eval echo \$"$list_var")
	do
		if [ "$action" = "add" ]; then
			f=${f%%:*}
			case "$opt_type" in
				bool) proto_config_add_boolean "$f:bool" ;;
				protobool) proto_config_add_boolean "$f:protobool" ;;
				uinteger) proto_config_add_int "$f:uinteger" ;;
				integer) proto_config_add_int "$f:integer" ;;
				string) proto_config_add_string "$f:string" ;;
				protostring) proto_config_add_string "$f:protostring" ;;
				file) proto_config_add_string "$f:file" ;;
				list) proto_config_add_array "$f:list" ;;
			esac
		elif [ "$action" = "build" ]; then
			[ "${f#*:}" = "d" ] && [ "$ALLOW_DEPRECATED" = 0 ] && continue
			f=${f%%:*}
			case "$opt_type" in
				bool)
					json_get_var v "$f"
					[ "$v" = 1 ] && append exec_params "--${f//_/-}"
					;;
				uinteger|integer|string)
					json_get_var v "$f"
					case $f in
						push_remove)
							[ -n "$v" ] && append exec_params "--${f//_/-} '$v'"
						;;
						*)
							[ -n "$v" ] && append exec_params "--${f//_/-} $v"
						;;
					esac
					;;
				file)
					json_get_var v "$f"
					[ -f "$v" ] || continue
					[ -n "$v" ] && append exec_params "--${f//_/-} '$v'"
					;;
				list)
					local type
					json_get_type type "$f"
					case "$type" in
					object|array)
						local keys key
						json_select "$f"
						json_get_keys keys
						for key in $keys; do
							json_get_var val "$key"
							case $f in
								push)
									append exec_params "--${f//_/-} '$val'"
								;;
								*)
									append exec_params "--${f//_/-} $val"
								;;
							esac
						done
						json_select ..
						;;
					*)  ;;
					esac
					;;
			esac
		fi
	done
}


# Not real config params used by openvpn - only by our proto handler
PROTO_BOOLS='
allow_deprecated
ipv6
'

PROTO_STRINGS='
username
password
cert_password
ovpnproto
'

proto_openvpn_init_config() {
	available=1
	no_device=1
	lasterror=1
	renew_handler=1

	# There may be opvnvpn options which mean that a tap L2 device exists.
	# TODO: Set no_device to depend on tap device

	proto_add_dynamic_defaults

	# Add proto config options - netifd compares these for changes between interface events
	option_builder add PROTO_BOOLS protobool
	option_builder add PROTO_STRINGS string
	option_builder add OPENVPN_BOOLS bool
	option_builder add OPENVPN_UINTS uinteger
	option_builder add OPENVPN_INTS integer
	option_builder add OPENVPN_PARAMS_STRING string
	option_builder add OPENVPN_PARAMS_FILE file
	option_builder add OPENVPN_LIST list
}

proto_openvpn_setup() {
	local config="$1"
	local conf_file="/var/run/openvpn.$config.conf"
	local exec_params cd_dir

	exec_params=

	json_get_var dev_type dev_type
	[ -z "$dev_type" ] && append exec_params "--dev-type tun"
	json_get_var ovpnproto ovpnproto
	[ -n "$ovpnproto" ] && append exec_params "--proto $ovpnproto"

	json_get_var config_file config
	# shellcheck disable=SC2154
	cd_dir="${config_file%/*}"
	[ "$cd_dir" = "$config_file" ] && cd_dir="/"
	append exec_params "--cd $cd_dir"
	append exec_params "--status /var/run/openvpn.$config.status"
	append exec_params "--syslog openvpn_$config"
	append exec_params "--tmp-dir /tmp"

	json_get_var ALLOW_DEPRECATED allow_deprecated
	[ -z "$ALLOW_DEPRECATED" ] && ALLOW_DEPRECATED=0

	# Build exec params from configured options we get from ubus values stored during init_config
	option_builder build OPENVPN_BOOLS bool
	option_builder build OPENVPN_UINTS uinteger
	option_builder build OPENVPN_INTS integer
	option_builder build OPENVPN_PARAMS_STRING string
	option_builder build OPENVPN_PARAMS_FILE file
	option_builder build OPENVPN_LIST list

	proto_add_dynamic_defaults

	json_get_vars auth_user_pass askpass username password cert_password

	mkdir -p /var/run
	# combine into --askpass:
	if [ -n "$cert_password" ]; then
		cp_file="/var/run/openvpn.$config.pass"
		umask 077
		printf '%s\n' "${cert_password:-}" > "$cp_file"
		umask 022
		append exec_params "--askpass $cp_file"
	elif [ -n "$askpass" ]; then
		append exec_params "--askpass $askpass"
	fi

	# combine into --auth-user-pass:
	if [ -n "$username" ] || [ -n "$password" ]; then
		auth_file="/var/run/openvpn.$config.auth"
		umask 077
		printf '%s\n' "${username:-}" "${password:-}" > "$auth_file"
		umask 022
		append exec_params "--auth-user-pass $auth_file"
	elif [ -n "$auth_user_pass" ]; then
		append exec_params "--auth-user-pass $auth_user_pass"
	fi

	# Testing option
	# ${tls_exit:+--tls-exit} \

	# Check 'script_security' option
	json_get_var script_security script_security
	[ -z "$script_security" ] && script_security=3

	# Add default hotplug handling if 'script_security' option is equal '3'
	if [ "$script_security" -eq '3' ]; then
		local ipv6
		local up down route_up route_pre_down
		local client tls_client tls_server
		local tls_crypt_v2_verify mode learn_address client_connect
		local client_crresponse client_disconnect auth_user_pass_verify

		logger -t "openvpn(proto)" \
			-p daemon.info "Enabled default hotplug processing, as the openvpn configuration 'script_security' is '3'"

		append exec_params "--setenv INTERFACE $config"
		append exec_params "--script-security 3"

		json_get_vars up down route_up route_pre_down
		json_get_vars tls_crypt_v2_verify mode learn_address client_connect
		json_get_vars client_crresponse client_disconnect auth_user_pass_verify

		json_get_vars ipv6
		#default ipv6 is enabled
		[ -n "$ipv6" ] || ipv6=1
		append exec_params "--setenv IPV6 '$ipv6'"

		json_get_vars ifconfig_noexec route_noexec
		[ -z "$ifconfig_noexec" ] && append exec_params "--ifconfig-noexec"
		[ -z "$route_noexec" ] && append exec_params "--route-noexec"

		append exec_params "--up '/usr/libexec/openvpn-hotplug'"
		[ -n "$up" ] && append exec_params "--setenv user_up '$up'"

		append exec_params "--down '/usr/libexec/openvpn-hotplug'"
		[ -n "$down" ] && append exec_params "--setenv user_down '$down'"

		append exec_params "--route-up '/usr/libexec/openvpn-hotplug'"
		[ -n "$route_up" ] && append exec_params "--setenv user_route_up '$route_up'"

		append exec_params "--route-pre-down '/usr/libexec/openvpn-hotplug'"
		[ -n "$route_pre_down" ] && append exec_params "--setenv user_route_pre_down '$route_pre_down'"

		append exec_params "--tls-crypt-v2-verify '/usr/libexec/openvpn-hotplug'"
		[ -n "$tls_crypt_v2_verify" ] && append exec_params "--setenv user_tls_crypt_v2_verify '$tls_crypt_v2_verify'"

		[ "$mode" = 'server' ] && {
			append exec_params "--learn-address '/usr/libexec/openvpn-hotplug'"
			[ -n "$learn_address" ] && append exec_params "--setenv user_learn_address '$learn_address'"
			append exec_params "--client-connect '/usr/libexec/openvpn-hotplug'"
			[ -n "$client_connect" ] && append exec_params "--setenv user_client_connect '$client_connect'"
			append exec_params "--client-crresponse '/usr/libexec/openvpn-hotplug'"
			[ -n "$client_crresponse" ] && append exec_params "--setenv user_client_crresponse '$client_crresponse'"
			append exec_params "--client-disconnect '/usr/libexec/openvpn-hotplug'"
			[ -n "$client_disconnect" ] && append exec_params "--setenv user_client_disconnect '$client_disconnect'"

			[ -n "$auth_user_pass_verify" ] && {
				append exec_params "--auth-user-pass-verify '/usr/libexec/openvpn-hotplug' via-file"
				append exec_params "--setenv user_auth_user_pass_verify '$auth_user_pass_verify'"
			}
		}

		json_get_vars client tls_client tls_server
		if [ "$client" = 1 ] || [ "$tls_client" = 1 ]; then
			append exec_params "--ipchange '/usr/libexec/openvpn-hotplug'"
			json_get_var ipchange ipchange
			[ -n "$ipchange" ] && append exec_params "--setenv user_ipchange '$ipchange'"
		fi

		if [ "$tls_client" = 1 ] || [ "$tls_server" = 1 ]; then
			append exec_params "--tls-verify '/usr/libexec/openvpn-hotplug'"
			json_get_var tls_verify tls_verify
			[ -n "$tls_verify" ] && append exec_params "--setenv user_tls_verify '$tls_verify'"
		fi
	else
		logger -t "openvpn(proto)" \
			-p daemon.warn "Default hotplug processing disabled, as the openvpn configuration 'script_security' is less than '3'"
	fi

	eval "set -- $exec_params"
	umask 077
	printf "%b\n" "${exec_params//--/\\n}" > "$conf_file"
	umask 022
	proto_run_command "$config" openvpn --config "$conf_file"

	# last param wins; user provided status or syslog supersedes.
}

proto_openvpn_renew() {
	config="$1"
	local sigusr1

	sigusr1="$(kill -l SIGUSR1)"
	[ -n "$sigusr1" ] && proto_kill_command "$config" "$sigusr1"
}

proto_openvpn_teardown() {
	local iface="$1"
	rm -f \
		"/var/run/openvpn.$iface.conf" \
		"/var/run/openvpn.$iface.pass" \
		"/var/run/openvpn.$iface.auth" \
		"/var/run/openvpn.$iface.status" 
	proto_kill_command "$iface"
}

[ -n "$INCLUDE_ONLY" ] || {
	add_protocol openvpn
}
