-- Copyright 2017-2018 Dirk Brenken (dev@brenken.org)
-- This is free software, licensed under the Apache License, Version 2.0

local fs        = require("nixio.fs")
local uci       = require("luci.model.uci").cursor()
local util      = require("luci.util")
local res_input = "/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv"
local res_dir   = fs.dirname(res_input)
local dump      = util.ubus("network.interface", "dump", {})
local plug_cnt  = tonumber(luci.sys.exec("env -i /usr/sbin/dnscrypt-proxy --version | grep 'Support for plugins: present' | wc -l"))
local res_list  = {}
local url       = "https://raw.githubusercontent.com/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv"

local _, date = pcall(require, "luci.http.date")
if not date then
	_, date = pcall(require, "luci.http.protocol.date")
end

if not fs.access(res_input) then
	if not fs.access("/lib/libustream-ssl.so") then
		m = SimpleForm("error", nil, translate("No default resolver list and no SSL support available.<br />")
			.. translate("Please install a resolver list to '/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv' to use this package."))
		m.submit = false
		m.reset = false
		return m
	else
		luci.sys.call("env -i /bin/uclient-fetch --no-check-certificate -O " .. res_input .. " " .. url .. " >/dev/null 2>&1")
	end
end

if not uci:get_first("dnscrypt-proxy", "global") then
	uci:add("dnscrypt-proxy", "global")
	uci:save("dnscrypt-proxy")
	uci:commit("dnscrypt-proxy")
end

if fs.access(res_input) then
	for line in io.lines(res_input) or {} do
		local name,
		location,
		dnssec,
		nolog = line:match("^([^,]+),.-,\".-\",\"*(.-)\"*,.-,[0-9],\"*([yesno]+)\"*,\"*([yesno]+)\"*,.*")
		if name ~= "" and name ~= "Name" then
			if location == "" then
				location = "-"
			end
			if dnssec == "" then
				dnssec = "-"
			end
			if nolog == "" then
				nolog = "-"
			end
			res_list[#res_list + 1] = { name = name, location = location, dnssec = dnssec, nolog = nolog }
		end
	end
end

m = Map("dnscrypt-proxy", translate("DNSCrypt-Proxy"),
	translate("Configuration of the DNSCrypt-Proxy package. ")
	.. translatef("For further information "
	.. "<a href=\"%s\" target=\"_blank\">"
	.. "see the wiki online</a>", "https://wiki.openwrt.org/inbox/dnscrypt"))
m:chain("dhcp")

function m.on_after_commit(self)
	function d1.validate(self, value, s1)
		if value == "1" then
			uci:commit("dnscrypt-proxy")
			uci:set("dhcp", s1, "noresolv", 1)
			if not fs.access("/etc/resolv-crypt.conf") or fs.stat("/etc/resolv-crypt.conf").size == 0 then
				uci:set("dhcp", s1, "resolvfile", "/tmp/resolv.conf.auto")
			else
				uci:set("dhcp", s1, "resolvfile", "/etc/resolv-crypt.conf")
			end
			local server_list = {}
			local cnt = 1
			uci:foreach("dnscrypt-proxy", "dnscrypt-proxy", function(s)
				server_list[cnt] = s['address'] .. "#" .. s['port']
				cnt = cnt + 1
			end)
			server_list[cnt] = "/pool.ntp.org/8.8.8.8"
			uci:set_list("dhcp", s1, "server", server_list)
			if cnt > 2 then
				uci:set("dhcp", s1, "allservers", 1)
			else
				uci:set("dhcp", s1, "allservers", 0)
			end
			uci:save("dhcp")
			uci:commit("dhcp")
		end
		return value
	end
	luci.sys.call("env -i /etc/init.d/dnscrypt-proxy restart >/dev/null 2>&1")
	luci.sys.call("env -i /etc/init.d/dnsmasq restart >/dev/null 2>&1")
end

s = m:section(TypedSection, "global", translate("General Options"))
s.anonymous = true

-- Main dnscrypt-proxy resource list

o1 = s:option(DummyValue, "", translate("Default Resolver List"))
o1.template = "dnscrypt-proxy/res_options"
o1.value = res_input

o2 = s:option(DummyValue, "", translate("File Date"))
o2.template = "dnscrypt-proxy/res_options"
if fs.access(res_input) then
	o2.value = date.to_http(fs.stat(res_input).mtime)
else
	o2.value = "-"
end

o3 = s:option(DummyValue, "", translate("File Checksum"))
o3.template = "dnscrypt-proxy/res_options"
if fs.access(res_input) then
	o3.value = luci.sys.exec("sha256sum " .. res_input .. " | awk '{print $1}'")
else
	o3.value = "-"
end

if fs.access("/lib/libustream-ssl.so") then
	btn1 = s:option(Button, "", translate("Refresh Resolver List"),
		translate("Download the current resolver list from 'github.com/dyne/dnscrypt-proxy'."))
	btn1.inputtitle = translate("Refresh List")
	btn1.inputstyle = "apply"
	btn1.disabled = false
	function btn1.write()
		if not fs.access(res_dir) then
			fs.mkdir(res_dir)
		end
		luci.sys.call("env -i /bin/uclient-fetch --no-check-certificate -O " .. res_input .. " " .. url .. " >/dev/null 2>&1")
		luci.http.redirect(luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy"))
	end
else
	btn1 = s:option(Button, "", translate("Refresh Resolver List"),
		translate("No SSL support available.<br />")
		.. translate("Please install a 'libustream-ssl' library to download the current resolver list from 'github.com/dyne/dnscrypt-proxy'."))
	btn1.inputtitle = translate("-------")
	btn1.inputstyle = "button"
	btn1.disabled = true
end

if not fs.access("/etc/resolv-crypt.conf") or fs.stat("/etc/resolv-crypt.conf").size == 0 then
	btn2 = s:option(Button, "", translate("Create Custom Config File"),
		translate("Create '/etc/resolv-crypt.conf' with 'options timeout:1' to reduce DNS upstream timeouts with multiple DNSCrypt instances.<br />")
		.. translatef("For further information "
		.. "<a href=\"%s\" target=\"_blank\">"
		.. "see the wiki online</a>", "https://wiki.openwrt.org/inbox/dnscrypt"))
	btn2.inputtitle = translate("Create Config File")
	btn2.inputstyle = "apply"
	btn2.disabled = false
	function btn2.write()
		luci.sys.call("env -i echo 'options timeout:1' > '/etc/resolv-crypt.conf'")
		luci.http.redirect(luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy"))
	end
else
	btn2 = s:option(Button, "", translate("Create Custom Config File"),
		translate("The config file '/etc/resolv-crypt.conf' already exist.<br />")
		.. translate("Please edit the file manually in the 'Advanced' section."))
	btn2.inputtitle = translate("-------")
	btn2.inputstyle = "button"
	btn2.disabled = true
end

-- Trigger settings

t = s:option(ListValue, "procd_trigger", translate("Startup Trigger"),
	translate("By default the DNSCrypt-Proxy startup will be triggered by ifup events of 'All' available network interfaces.<br />")
	.. translate("To restrict the trigger, select only the relevant network interface. Usually the 'wan' interface should work for most users."))
t:value("", "All")
if dump then
	local i, v
	for i, v in ipairs(dump.interface) do
		if v.interface ~= "loopback" then
			t:value(v.interface)
		end
	end
end
t.default = procd_trigger or "All"
t.rmempty = true

-- Mandatory options per instance

s = m:section(TypedSection, "dnscrypt-proxy", translate("Instance Options"))
s.anonymous = true
s.addremove = true

i1 = s:option(Value, "address", translate("IP Address"),
	translate("The local IPv4 or IPv6 address. The latter one should be specified within brackets, e.g. '[::1]'."))
i1.default = address or "127.0.0.1"
i1.rmempty = false

i2 = s:option(Value, "port", translate("Port"),
	translate("The listening port for DNS queries."))
i2.datatype = "port"
i2.default = port
i2.rmempty = false

i3 = s:option(ListValue, "resolver", translate("Resolver (LOC/SEC/NOLOG)"),
	translate("Name of the remote DNS service for resolving queries incl. Location, DNSSEC- and NOLOG-Flag."))
i3.datatype = "hostname"
i3.widget = "select"
local i, v
for i, v in ipairs(res_list) do
	if v.name then
		i3:value(v.name, v.name .. " (" .. v.location .. "/" .. v.dnssec .. "/" .. v.nolog .. ")")
	end
end
i3.default = resolver
i3.rmempty = false

-- Extra options per instance

e1 = s:option(Value, "resolvers_list", translate("Alternate Resolver List"),
	translate("Specify a non-default Resolver List."))
e1.datatype = "file"
e1.optional = true

e2 = s:option(Value, "ephemeral_keys", translate("Ephemeral Keys"),
	translate("Improve privacy by using an ephemeral public key for each query. ")
	.. translate("This option requires extra CPU cycles and is useless with most DNSCrypt server."))
e2.datatype = "bool"
e2.value = 1
e2.optional = true

if plug_cnt > 0 then
	e3 = s:option(DynamicList, "blacklist", translate("Blacklist"),
		translate("Local blacklists allow you to block abuse sites by domains or ip addresses. ")
		.. translate("The value for this property is the blocklist type and path to the file, e.g.'domains:/path/to/dbl.txt' or 'ips:/path/to/ipbl.txt'."))
	e3.optional = true

	e4 = s:option(Value, "block_ipv6", translate("Block IPv6"),
		translate("Disable IPv6 to speed up DNSCrypt-Proxy."))
	e4.datatype = "bool"
	e4.value = 1
	e4.optional = true

	e5 = s:option(Value, "local_cache", translate("Local Cache"),
		translate("Enable Caching to speed up DNSCcrypt-Proxy."))
	e5.datatype = "bool"
	e5.value = 1
	e5.optional = true
	
	e6 = s:option(Value, "query_log_file", translate("DNS Query Logfile"),
	translate("Log the received DNS queries to a file, so you can watch in real-time what is happening on the network."))
	e6.optional = true
end

-- Dnsmasq options

m1 = Map("dhcp")

s1 = m1:section(TypedSection, "dnsmasq", translate("Dnsmasq Options"))
s1.anonymous = true

d1 = s1:option(Flag, "", translate("Transfer Options To Dnsmasq"),
	translate("Apply DNSCrypt-Proxy specific settings to the Dnsmasq configuration.<br />")
	.. translate("Please note: This may change the values for 'noresolv', 'resolvfile', 'allservers' and the list 'server' settings."))
d1.default = d1.enabled
d1.rmempty = false

return m, m1