-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.

local fs = require("nixio.fs")

local knownParams = {
	--
	--Widget
	--	Name
	--	Default(s)
	--	Description
	--	Option(s)

	{ "Service", {
	-- initialisation and daemon options
		{ ListValue,
			"verb",
			{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 },
			translate("Set output verbosity") },
		{ Flag,
			"mlock",
			0,
			translate("Disable Paging") },
		{ Flag,
			"disable_occ",
			0,
			translate("Disable options consistency check") },
	--	{ Value,
	--		"user",
	--		"root",
	--		translate("Set UID to user") },
	--	{ Value,
	--		"group",
	--		"root",
	--		translate("Set GID to group") },
		{ Value,
			"cd",
			"/etc/openvpn",
			translate("Change to directory before initialization") },
		{ Value,
			"chroot",
			"/var/run",
			translate("Chroot to directory after initialization") },
	--	{ Value,
	--		"daemon",
	--		"Instance-Name",
	--		translate("Daemonize after initialization") },
	--	{ Value,
	--		"syslog",
	--		"Instance-Name",
	--		translate("Output to syslog and do not daemonize") },
		{ Flag,
			"passtos",
			0,
			translate("TOS passthrough (applies to IPv4 only)") },
	--	{ Value,
	--		"inetd",
	--		"nowait Instance-Name",
	--		translate("Run as an inetd or xinetd server") },
		{ Value,
			"log",
			"/var/log/openvpn.log",
			translate("Write log to file") },
		{ Value,
			"log_append",
			"/var/log/openvpn.log",
			translate("Append log to file") },
		{ Flag,
			"suppress_timestamps",
			0,
			translate("Don't log timestamps") },
	--	{ Value,
	--		"writepid",
	--		"/var/run/openvpn.pid",
	--		translate("Write process ID to file") },
		{ Value,
			"nice",
			0,
			translate("Change process priority") },
		{ Flag,
			"fast_io",
			0,
			translate("Optimize TUN/TAP/UDP writes") },
		{ Value,
			"echo",
			"some params echoed to log",
			translate("Echo parameters to log") },
		{ ListValue,
			"remap_usr1",
			{ "SIGHUP", "SIGTERM" },
			translate("Remap SIGUSR1 signals") },
		{ Value,
			"status",
			"/var/run/openvpn.status 5",
			translate("Write status to file every n seconds") },
		{ Value,
			"status_version",
			{ 1, 2 },
			translate("Status file format version") },	-- status
		{ Value,
			"mute",
			5,
			translate("Limit repeated log messages") },
		{ Value,
			"up",
			"/usr/bin/ovpn-up",
			translate("Shell cmd to execute after tun device open") },
		{ Value,
			"up_delay",
			5,
			translate("Delay tun/tap open and up script execution") },
		{ Value,
			"down",
			"/usr/bin/ovpn-down",
			translate("Shell cmd to run after tun device close") },
		{ Flag,
			"down_pre",
			0,
			translate("Call down cmd/script before TUN/TAP close") },
		{ Flag,
			"up_restart",
			0,
			translate("Run up/down scripts for all restarts") },
		{ Value,
			"route_up",
			"/usr/bin/ovpn-routeup",
			translate("Execute shell cmd after routes are added") },
		{ Value,
			"ipchange",
			"/usr/bin/ovpn-ipchange",
			translate("Execute shell command on remote IP change"),
			{ mode="p2p" } },
		{ DynamicList,
			"setenv",
			{ "VAR1 value1", "VAR2 value2" },
			translate("Pass environment variables to script") },
		{ Value,
			"tls_verify",
			"/usr/bin/ovpn-tlsverify",
			translate("Shell command to verify X509 name") },
		{ Value,
			"client_connect",
			"/usr/bin/ovpn-clientconnect",
			translate("Run script cmd on client connection") },
		{ Value,
			"client_disconnect",
			"/usr/bin/ovpn-clientdisconnect",
			translate("Run script cmd on client disconnection") },
		{ Value,
			"learn_address",
			"/usr/bin/ovpn-learnaddress",
			translate("Executed in server mode whenever an IPv4 address/route or MAC address is added to OpenVPN's internal routing table") },
		{ Value,
			"auth_user_pass_verify",
			"/usr/bin/ovpn-userpass via-env",
			translate("Executed in server mode on new client connections, when the client is still untrusted") },
		{ ListValue,
			"script_security",
			{ 0, 1, 2, 3 },
			translate("Policy level over usage of external programs and scripts") },
		{ ListValue,
			"compress",
			{ "lzo", "lz4" },
			translate("Enable a compression algorithm") },
	} },

	{ "Networking", {
	-- socket config
		{ ListValue,
			"mode",
			{ "p2p", "server" },
			translate("Major mode") },
		{ Value,
			"local",
			"0.0.0.0",
			translate("Local host name or IP address") },
		{ Value,
			"port",
			1194,
			translate("TCP/UDP port # for both local and remote") },
		{ Value,
			"lport",
			1194,
			translate("TCP/UDP port # for local (default=1194)") },
		{ Value,
			"rport",
			1194,
			translate("TCP/UDP port # for remote (default=1194)") },
		{ Flag,
			"float",
			0,
			translate("Allow remote to change its IP or port") },
		{ Flag,
			"nobind",
			0,
			translate("Do not bind to local address and port") },
		{ Value,
			"dev",
			"tun0",
			translate("tun/tap device") },
		{ ListValue,
			"dev_type",
			{ "tun", "tap" },
			translate("Type of used device") },
		{ Value,
			"dev_node",
			"/dev/net/tun",
			translate("Use tun/tap device node") },
		{ Value,
			"ifconfig",
			"10.200.200.3 10.200.200.1",
			translate("Set tun/tap adapter parameters") },
		{ Flag,
			"ifconfig_noexec",
			0,
			translate("Don't actually execute ifconfig") },
		{ Flag,
			"ifconfig_nowarn",
			0,
			translate("Don't warn on ifconfig inconsistencies") },
		{ DynamicList,
			"route",
			"10.123.0.0 255.255.0.0",
			translate("Add route after establishing connection") },
		{ Value,
			"route_gateway",
			"10.234.1.1",
			translate("Specify a default gateway for routes") },
		{ Value,
			"route_delay",
			0,
			translate("Delay n seconds after connection") },
		{ Flag,
			"route_noexec",
			0,
			translate("Don't add routes automatically") },
		{ Flag,
			"route_nopull",
			0,
			translate("Don't pull routes automatically") },
		{ Flag,
			"allow_recursive_routing",
			0,
			translate("Don't drop incoming tun packets with same destination as host") },
		{ ListValue,
			"mtu_disc",
			{ "yes", "maybe", "no" },
			translate("Enable Path MTU discovery") },
		{ Flag,
			"mtu_test",
			0,
			translate("Empirically measure MTU") },
		{ ListValue,
			"comp_lzo",
			{ "yes", "no", "adaptive" },
			translate("Use fast LZO compression") },
		{ Flag,
			"comp_noadapt",
			0,
			translate("Don't use adaptive lzo compression"),
			{ comp_lzo=1 } },
		{ Value,
			"link_mtu",
			1500,
			translate("Set TCP/UDP MTU") },
		{ Value,
			"tun_mtu",
			1500,
			translate("Set tun/tap device MTU") },
		{ Value,
			"tun_mtu_extra",
			1500,
			translate("Set tun/tap device overhead") },
		{ Value,
			"fragment",
			1500,
			translate("Enable internal datagram fragmentation"),
			{ proto="udp" } },
		{ Value,
			"mssfix",
			1500,
			translate("Set upper bound on TCP MSS"),
			{ proto="udp" } },
		{ Value,
			"sndbuf",
			65536,
			translate("Set the TCP/UDP send buffer size") },
		{ Value,
			"rcvbuf",
			65536,
			translate("Set the TCP/UDP receive buffer size") },
		{ Value,
			"txqueuelen",
			100,
			translate("Set tun/tap TX queue length") },
		{ Value,
			"shaper",
			10240,
			translate("Shaping for peer bandwidth") },
		{ Value,
			"inactive",
			240,
			translate("tun/tap inactivity timeout") },
		{ Value,
			"keepalive",
			"10 60",
			translate("Helper directive to simplify the expression of --ping and --ping-restart in server mode configurations") },
		{ Value,
			"ping",
			30,
			translate("Ping remote every n seconds over TCP/UDP port") },
		{ Value,
			"ping_exit",
			120,
			translate("Remote ping timeout") },
		{ Value,
			"ping_restart",
			60,
			translate("Restart after remote ping timeout") },
		{ Flag,
			"ping_timer_rem",
			0,
			translate("Only process ping timeouts if routes exist") },
		{ Flag,
			"persist_tun",
			0,
			translate("Keep tun/tap device open on restart") },
		{ Flag,
			"persist_key",
			0,
			translate("Don't re-read key on restart") },
		{ Flag,
			"persist_local_ip",
			0,
			translate("Keep local IP address on restart") },
		{ Flag,
			"persist_remote_ip",
			0,
			translate("Keep remote IP address on restart") },
	-- management channel
		{ Value,
			"management",
			"127.0.0.1 31194 /etc/openvpn/mngmt-pwds",
			translate("Enable management interface on <em>IP</em> <em>port</em>") },
	-- management
		{ Flag,
			"management_query_passwords",
			0,
			translate("Query management channel for private key") },
	-- management
		{ Flag,
			"management_hold",
			0,
			translate("Start OpenVPN in a hibernating state") },
	-- management
		{ Value,
			"management_log_cache",
			100,
			translate("Number of lines for log file history") },
		{ ListValue,
			"topology",
			{ "net30", "p2p", "subnet" },
			translate("'net30', 'p2p', or 'subnet'"),
			{dev_type="tun" } },
	} },

	{ "VPN", {
		{ Value,
			"server",
			"10.200.200.0 255.255.255.0",
			translate("Configure server mode"),
			{ client="0" }, { client="" } },
		{ Value,
			"server_bridge",
			"10.200.200.1 255.255.255.0 10.200.200.200 10.200.200.250",
			translate("Configure server bridge"),
			{ client="0" }, { client="" } },
		{ DynamicList,
			"push",
			{ "redirect-gateway", "comp-lzo" },
			translate("Push options to peer"),
			{ client="0" }, { client="" } },
		{ Flag,
			"push_reset",
			0,
			translate("Don't inherit global push options"),
			{ client="0" }, { client="" } },
		{ Flag,
			"disable",
			0,
			translate("Client is disabled"),
			{ client="0" }, { client="" } },
		{ Value,
			"ifconfig_pool",
			"10.200.200.100 10.200.200.150 255.255.255.0",
			translate("Set aside a pool of subnets"),
			{ client="0" }, { client="" } },
		{ Value,
			"ifconfig_pool_persist",
			"/etc/openvpn/ipp.txt 600",
			translate("Persist/unpersist ifconfig-pool"),
			{ client="0" }, { client="" } },
		{ Value,
			"ifconfig_push",
			"10.200.200.1 255.255.255.255",
			translate("Push an ifconfig option to remote"),
			{ client="0" }, { client="" } },
		{ Value,
			"iroute",
			"10.200.200.0 255.255.255.0",
			translate("Route subnet to client"),
			{ client="0" }, { client="" } },
		{ Flag,
			"client_to_client",
			0,
			translate("Allow client-to-client traffic"),
			{ client="0" }, { client="" } },
		{ Flag,
			"duplicate_cn",
			0,
			translate("Allow multiple clients with same certificate"),
			{ client="0" }, { client="" } },
		{ Value,
			"client_config_dir",
			"/etc/openvpn/ccd",
			translate("Directory for custom client config files"),
			{ client="0" }, { client="" } },
		{ Flag,
			"ccd_exclusive",
			0,
			translate("Refuse connection if no custom client config"),
			{ client="0" }, { client="" } },
		{ Value,
			"tmp_dir",
			"/var/run/openvpn",
			translate("Temporary directory for client-connect return file"),
			{ client="0" }, { client="" } },
		{ Value,
			"hash_size",
			"256 256",
			translate("Set size of real and virtual address hash tables"),
			{ client="0" }, { client="" } },
		{ Value,
			"bcast_buffers",
			256,
			translate("Number of allocated broadcast buffers"),
			{ client="0" }, { client="" } },
		{ Value,
			"tcp_queue_limit",
			64,
			translate("Maximum number of queued TCP output packets"),
			{ client="0" }, { client="" } },
		{ Value,
			"max_clients",
			10,
			translate("Allowed maximum of connected clients"),
			{ client="0" }, { client="" } },
		{ Value,
			"max_routes_per_client",
			256,
			translate("Allowed maximum of internal"),
			{ client="0" }, { client="" } },
		{ Value,
			"connect_freq",
			"3 10",
			translate("Allowed maximum of new connections"),
			{ client="0" }, { client="" } },
		{ Flag,
			"username_as_common_name",
			0,
			translate("Use username as common name"),
			{ client="0" }, { client="" } },
		{ Flag,
			"client",
			0,
			translate("Configure client mode") },
		{ Flag,
			"pull",
			0,
			translate("Accept options pushed from server"),
			{ client="1" } },
		{ FileUpload,
			"auth_user_pass",
			"/etc/openvpn/userpass.txt",
			translate("Authenticate using username/password"),
			{ client="1" } },
		{ ListValue,
			"auth_retry",
			{ "none", "nointeract", "interact" },
			translate("Handling of authentication failures"),
			{ client="1" } },
		{ Value,
			"explicit_exit_notify",
			1,
			translate("Send notification to peer on disconnect"),
			{ client="1" } },
		{ DynamicList,
			"remote",
			"1.2.3.4",
			translate("Remote host name or IP address") },
		{ Flag,
			"remote_random",
			0,
			translate("Randomly choose remote server"),
			{ client="1" } },
		{ ListValue,
			"proto",
			{ "udp", "tcp-client", "tcp-server" },
			translate("Use protocol"),
			{ client="1" } },
		{ Value,
			"connect_retry",
			5,
			translate("Connection retry interval"),
			{ proto="tcp-client" }, { client="1" } },
		{ Value,
			"http_proxy",
			"192.168.1.100 8080",
			translate("Connect to remote host through an HTTP proxy"),
			{ client="1" } },
		{ Flag,
			"http_proxy_retry",
			0,
			translate("Retry indefinitely on HTTP proxy errors"),
			{ client="1" } },
		{ Value,
			"http_proxy_timeout",
			5,
			translate("Proxy timeout in seconds"),
			{ client="1" } },
		{ DynamicList,
			"http_proxy_option",
			{ "VERSION 1.0", "AGENT OpenVPN/2.0.9" },
			translate("Set extended HTTP proxy options"),
			{ client="1" } },
		{ Value,
			"socks_proxy",
			"192.168.1.200 1080",
			translate("Connect through Socks5 proxy"),
			{ client="1" } },
	-- client && socks_proxy
		{ Value,
			"socks_proxy_retry",
			5,
			translate("Retry indefinitely on Socks proxy errors"),
			{ client="1" } },
		{ Value,
			"resolv_retry",
			"infinite",
			translate("If hostname resolve fails, retry"),
			{ client="1" } },
		{ ListValue,
			"redirect_gateway",
			{ "", "local", "def1", "local def1" },
			translate("Automatically redirect default route"),
			{ client="1" } },
		{ Value,
			"verify_client_cert",
			{  "none", "optional", "require" },
			translate("Specify whether the client is required to supply a valid certificate") },
	} },

	{ "Cryptography", {
		{ FileUpload,
			"secret",
			"/etc/openvpn/secret.key",
			translate("Enable Static Key encryption mode (non-TLS)") },
	-- parse
		{ Value,
			"auth",
			"SHA1",
			translate("HMAC authentication for packets") },
	-- parse
		{ Value,
			"cipher",
			{
				"AES-128-CBC",
				"AES-128-CFB",
				"AES-128-CFB1",
				"AES-128-CFB8",
				"AES-128-GCM",
				"AES-128-OFB",
				"AES-192-CBC",
				"AES-192-CFB",
				"AES-192-CFB1",
				"AES-192-CFB8",
				"AES-192-GCM",
				"AES-192-OFB",
				"AES-256-CBC",
				"AES-256-CFB",
				"AES-256-CFB1",
				"AES-256-CFB8",
				"AES-256-GCM",
				"AES-256-OFB",
				"BF-CBC",
				"BF-CFB",
				"BF-OFB",
				"CAST5-CBC",
				"CAST5-CFB",
				"CAST5-OFB",
				"DES-CBC",
				"DES-CFB",
				"DES-CFB1",
				"DES-CFB8",
				"DES-EDE-CBC",
				"DES-EDE-CFB",
				"DES-EDE-OFB",
				"DES-EDE3-CBC",
				"DES-EDE3-CFB",
				"DES-EDE3-CFB1",
				"DES-EDE3-CFB8",
				"DES-EDE3-OFB",
				"DES-OFB",
				"DESX-CBC",
				"RC2-40-CBC",
				"RC2-64-CBC",
				"RC2-CBC",
				"RC2-CFB",
				"RC2-OFB"
			},
			translate("Encryption cipher for packets") },
	-- parse
		{ Value,
			"keysize",
			1024,
			translate("Size of cipher key") },
	-- parse
		{ Value,
			"engine",
			"dynamic",
			translate("Enable OpenSSL hardware crypto engines") },
		{ Value,
			"replay_window",
			"64 15",
			translate("Replay protection sliding window size") },
		{ Flag,
			"mute_replay_warnings",
			0,
			translate("Silence the output of replay warnings") },
		{ Value,
			"replay_persist",
			"/var/run/openvpn-replay-state",
			translate("Persist replay-protection state") },
		{ Flag,
			"tls_server",
			0,
			translate("Enable TLS and assume server role"),
			{ tls_client="" }, { tls_client="0" } },
		{ Flag,
			"tls_client",
			0,
			translate("Enable TLS and assume client role"),
			{ tls_server="" }, { tls_server="0" } },
		{ FileUpload,
			"ca",
			"/etc/easy-rsa/keys/ca.crt",
			translate("Certificate authority") },
		{ FileUpload,
			"dh",
			"/etc/easy-rsa/keys/dh1024.pem",
			translate("Diffie-Hellman parameters") },
		{ FileUpload,
			"cert",
			"/etc/easy-rsa/keys/some-client.crt",
			translate("Local certificate") },
		{ FileUpload,
			"key",
			"/etc/easy-rsa/keys/some-client.key",
			translate("Local private key") },
		{ FileUpload,
			"pkcs12",
			"/etc/easy-rsa/keys/some-client.pk12",
			translate("PKCS#12 file containing keys") },
		{ ListValue,
			"key_method",
			{ 1, 2 },
			translate("Enable TLS and assume client role") },
		{ DynamicList,
			"tls_cipher",
			{
				"DHE-RSA-AES256-SHA",
				"DHE-DSS-AES256-SHA",
				"AES256-SHA",
				"EDH-RSA-DES-CBC3-SHA",
				"EDH-DSS-DES-CBC3-SHA",
				"DES-CBC3-SHA",
				"DHE-RSA-AES128-SHA",
				"DHE-DSS-AES128-SHA",
				"AES128-SHA",
				"RC4-SHA",
				"RC4-MD5",
				"EDH-RSA-DES-CBC-SHA",
				"EDH-DSS-DES-CBC-SHA",
				"DES-CBC-SHA",
				"EXP-EDH-RSA-DES-CBC-SHA",
				"EXP-EDH-DSS-DES-CBC-SHA",
				"EXP-DES-CBC-SHA",
				"EXP-RC2-CBC-MD5",
				"EXP-RC4-MD5"
			},
			translate("TLS cipher") },
		{ DynamicList,
			"tls_ciphersuites",
			{
				"TLS_AES_256_GCM_SHA384",
				"TLS_AES_128_GCM_SHA256",
				"TLS_CHACHA20_POLY1305_SHA256"
			},
			translate("TLS 1.3 or newer cipher") },
		{ Value,
			"tls_timeout",
			2,
			translate("Retransmit timeout on TLS control channel") },
		{ Value,
			"reneg_bytes",
			1024,
			translate("Renegotiate data chan. key after bytes") },
		{ Value,
			"reneg_pkts",
			100,
			translate("Renegotiate data chan. key after packets") },
		{ Value,
			"reneg_sec",
			3600,
			translate("Renegotiate data chan. key after seconds") },
		{ Value,
			"hand_window",
			60,
			translate("Timeframe for key exchange") },
		{ Value,
			"tran_window",
			3600,
			translate("Key transition window") },
		{ Flag,
			"single_session",
			0,
			translate("Allow only one session") },
		{ Flag,
			"tls_exit",
			0,
			translate("Exit on TLS negotiation failure") },
		{ Value,
			"tls_auth",
			"/etc/openvpn/tlsauth.key",
			translate("Additional authentication over TLS") },
		{ Value,
			"tls_crypt",
			"/etc/openvpn/tlscrypt.key",
			translate("Encrypt and authenticate all control channel packets with the key") },
	--	{ Value,
	--		"askpass",
	--		"[file]",
	--		translate("Get PEM password from controlling tty before we daemonize") },
		{ Flag,
			"auth_nocache",
			0,
			translate("Don't cache --askpass or --auth-user-pass passwords") },
		{ Value,
			"tls_remote",
			"remote_x509_name",
			translate("Only accept connections from given X509 name") },
		{ ListValue,
			"ns_cert_type",
			{ "client", "server" },
			translate("Require explicit designation on certificate") },
		{ ListValue,
			"remote_cert_tls",
			{ "client", "server" },
			translate("Require explicit key usage on certificate") },
		{ Value,
			"crl_verify",
			"/etc/easy-rsa/keys/crl.pem",
			translate("Check peer certificate against a CRL") },
		{ Value,
			"tls_version_min",
			"1.0",
			translate("The lowest supported TLS version") },
		{ Value,
			"tls_version_max",
			"1.2",
			translate("The highest supported TLS version") },
		{ ListValue,
			"key_direction",
			{ 0, 1 },
			translate("The key direction for 'tls-auth' and 'secret' options") },
		{ Flag,
			"ncp_disable",
			0,
			translate("This completely disables cipher negotiation") },
		{ Value,
			"ncp_ciphers",
			"AES-256-GCM:AES-128-GCM",
			translate("Restrict the allowed ciphers to be negotiated") },
	} }
}


local cts = { }
local params = { }

local m = Map("openvpn")
m.redirect = luci.dispatcher.build_url("admin", "vpn", "openvpn")
m.apply_on_parse = true

local p = m:section( SimpleSection )
p.template = "openvpn/pageswitch"
p.mode     = "advanced"
p.instance = arg[1]
p.category = arg[2] or "Service"

for _, c in ipairs(knownParams) do
	cts[#cts+1] = c[1]
	if c[1] == p.category then params = c[2] end
end

p.categories = cts


local s = m:section(
	NamedSection, arg[1], "openvpn",
	translate("%s" % arg[2])
)

s.title     = translate("%s" % arg[2])
s.addremove = false
s.anonymous = true


for _, option in ipairs(params) do
	local o = s:option(
		option[1], option[2],
		option[2], option[4]
	)

	o.optional = true

	if option[1] == DummyValue then
		o.value = option[3]
	elseif option[1] == FileUpload then

		o.initial_directory = "/etc/openvpn"

		function o.cfgvalue(self, section)
			local cfg_val = AbstractValue.cfgvalue(self, section)

			if cfg_val then
				return cfg_val
			end
		end

		function o.formvalue(self, section)
			local sel_val = AbstractValue.formvalue(self, section)
			local txt_val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")

			if sel_val and sel_val ~= "" then
				return sel_val
			end

			if txt_val and txt_val ~= "" then
				return txt_val
			end
		end

		function o.remove(self, section)
			local cfg_val = AbstractValue.cfgvalue(self, section)
			local txt_val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")

			if cfg_val and fs.access(cfg_val) and txt_val == "" then
				fs.unlink(cfg_val)
			end
			return AbstractValue.remove(self, section)
		end
	elseif option[1] == Flag then
		o.default = nil
	else
		if option[1] == DynamicList then
			function o.cfgvalue(...)
				local val = AbstractValue.cfgvalue(...)
				return ( val and type(val) ~= "table" ) and { val } or val
			end
		end

		if type(option[3]) == "table" then
			if o.optional then o:value("", "-- remove --") end
			for _, v in ipairs(option[3]) do
				v = tostring(v)
				o:value(v)
			end
			o.default = tostring(option[3][1])
		else
			o.default = tostring(option[3])
		end
	end

	for i=5,#option do
		if type(option[i]) == "table" then
			o:depends(option[i])
		end
	end
end

return m
