Merge pull request #3790 from PolynomialDivision/feature/luci-app-dawn
[oweals/luci.git] / applications / luci-app-simple-adblock / luasrc / model / cbi / simple-adblock.lua
1 -- Copyright 2016-2018 Stan Grishin <stangri@melmac.net>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local readmeURL = "https://github.com/openwrt/packages/tree/master/net/simple-adblock/files/README.md"
5 -- local readmeURL = "https://github.com/stangri/openwrt_packages/tree/master/simple-adblock/files/README.md"
6
7 local packageName = "simple-adblock"
8 local uci = require "luci.model.uci".cursor()
9 local util = require "luci.util"
10 local sys = require "luci.sys"
11 local jsonc = require "luci.jsonc"
12 local fs = require "nixio.fs"
13 local nutil = require "nixio.util"
14 local http = require "luci.http"
15 local dispatcher = require "luci.dispatcher"
16 local enabledFlag = uci:get(packageName, "config", "enabled")
17 local command, outputFile, outputCache, outputGzip
18 local targetDNS = uci:get(packageName, "config", "dns")
19 local checkDnsmasq = sys.call("which dnsmasq >/dev/null 2>&1") == 0 and true
20 local checkUnbound = sys.call("which unbound >/dev/null 2>&1") == 0 and true
21 local checkDnsmasqIpset = sys.call("dnsmasq -v 2>/dev/null | grep -q 'no-ipset' || ! dnsmasq -v 2>/dev/null | grep -q -w 'ipset'") ~= 0
22    and sys.call("ipset help hash:net >/dev/null 2>&1") and true
23
24 if not targetDNS or targetDNS == "" then
25         targetDNS = "dnsmasq.servers"
26 end
27
28 if targetDNS ~= "dnsmasq.addnhosts" and targetDNS ~= "dnsmasq.conf" and 
29          targetDNS ~= "dnsmasq.ipset" and targetDNS ~= "dnsmasq.servers" and 
30          targetDNS ~= "unbound.adb_list" then
31         targetDNS = "dnsmasq.servers"
32 end
33
34 if targetDNS == "dnsmasq.addnhosts" then
35         outputFile="/var/run/" .. packageName .. ".addnhosts"
36         outputCache="/var/run/" .. packageName .. ".addnhosts.cache"
37         outputGzip="/etc/" .. packageName .. ".addnhosts.gz"
38 elseif targetDNS == "dnsmasq.conf" then
39         outputFile="/var/dnsmasq.d/" .. packageName .. ""
40         outputCache="/var/run/" .. packageName .. ".dnsmasq.cache"
41         outputGzip="/etc/" .. packageName .. ".dnsmasq.gz"
42 elseif targetDNS == "dnsmasq.servers" then
43         outputFile="/var/run/" .. packageName .. ".servers"
44         outputCache="/var/run/" .. packageName .. ".servers.cache"
45         outputGzip="/etc/" .. packageName .. ".servers.gz"
46 elseif targetDNS == "unbound.adb_list" then
47         outputFile="/var/lib/unbound/adb_list." .. packageName .. ""
48         outputCache="/var/run/" .. packageName .. ".unbound.cache"
49         outputGzip="/etc/" .. packageName .. ".unbound.gz"
50 end
51
52 local tmpfs
53 if fs.access("/var/run/" .. packageName .. ".json") then
54         tmpfs = jsonc.parse(util.trim(sys.exec("cat /var/run/" .. packageName .. ".json")))
55 end
56
57 local tmpfsVersion, tmpfsStatus = "", "Stopped"
58 local tmpfsMessage, tmpfsError, tmpfsStats
59 if tmpfs and tmpfs['data'] then
60         if tmpfs['data']['status'] and tmpfs['data']['status'] ~= "" then
61                 tmpfsStatus = tmpfs['data']['status']
62         end
63         if tmpfs['data']['message'] and tmpfs['data']['message'] ~= "" then
64                 tmpfsMessage = tmpfs['data']['message']
65         end
66         if tmpfs['data']['error'] and tmpfs['data']['error'] ~= "" then
67                 tmpfsError = tmpfs['data']['error']
68         end
69         if tmpfs['data']['stats'] and tmpfs['data']['stats'] ~= "" then
70                 tmpfsStats = tmpfs['data']['stats']
71         end
72         if tmpfs['data']['version'] and tmpfs['data']['version'] ~= "" then
73                 tmpfsVersion = packageName .. " " .. tmpfs['data']['version']
74         else 
75                 tmpfsVersion = packageName
76         end
77 end
78
79 local statusTable = {}
80 local errorTable = {}
81 statusTable["statusNoInstall"] = packageName .. translate("is not installed or not found")
82 statusTable["statusStopped"] = translate("Stopped")
83 statusTable["statusStarting"] = translate("Starting")
84 statusTable["statusRestarting"] = translate("Restarting")
85 statusTable["statusForceReloading"] = translate("Force Reloading")
86 statusTable["statusDownloading"] = translate("Downloading")
87 statusTable["statusError"] = translate("Error")
88 statusTable["statusWarning"] = translate("Warning")
89 statusTable["statusFail"] = translate("Fail")
90 statusTable["statusSuccess"] = translate("Success")
91 errorTable["errorOutputFileCreate"] = translate("failed to create") .. " '" .. outputFile .. "' " .. translate("file")
92 errorTable["errorFailDNSReload"] = translate("failed to restart/reload DNS resolver")
93 errorTable["errorSharedMemory"] = translate("failed to access shared memory")
94 errorTable["errorSorting"] = translate("failed to sort data file")
95 errorTable["errorOptimization"] = translate("failed to optimize data file")
96 errorTable["errorWhitelistProcessing"] = translate("failed to process whitelist")
97 errorTable["errorDataFileFormatting"] = translate("failed to format data file")
98 errorTable["errorMovingDataFile"] = translate("failed to move temporary data file to") .. " '" .. outputFile .. "'"
99 errorTable["errorCreatingCompressedCache"] = translate("failed to create compressed cache")
100 errorTable["errorRemovingTempFiles"] = translate("failed to remove temporary files")
101 errorTable["errorRestoreCompressedCache"] = translate("failed to unpack compressed cache")
102 errorTable["errorRestoreCache"] = translate("failed to move") .. " '" .. outputCache .. "' " .. translate("to") .. " '" .. outputFile .. "'"
103 errorTable["errorOhSnap"] = translate("failed to create blocklist or restart DNS resolver")
104 errorTable["errorStopping"] = translate("failed to stop") .. " " .. packageName
105 errorTable["errorDNSReload"] = translate("failed to reload/restart DNS resolver")
106 errorTable["errorDownloadingList"] = translate("failed to download")
107 errorTable["errorParsingList"] = translate("failed to parse")
108
109 m = Map("simple-adblock", translate("Simple AdBlock Settings"))
110 m.apply_on_parse = true
111 m.on_after_apply = function(self)
112         sys.call("/etc/init.d/simple-adblock restart")
113 end
114
115 h = m:section(NamedSection, "config", "simple-adblock", translate("Service Status") .. " [" .. tmpfsVersion .. "]")
116
117 if tmpfsStatus == "statusStarting" or
118          tmpfsStatus == "statusRestarting" or
119          tmpfsStatus == "statusForceReloading" or
120          tmpfsStatus == "statusDownloading" then
121         ss = h:option(DummyValue, "_dummy", translate("Service Status"))
122         ss.template = "simple-adblock/status"
123         ss.value = statusTable[tmpfsStatus] .. '...'
124         if tmpfsMessage then
125                 sm = h:option(DummyValue, "_dummy", translate("Task"))
126                 sm.template = "simple-adblock/status"
127                 sm.value = tmpfsMessage
128         end
129 else
130         if tmpfsStatus == "statusStopped" then
131                 ss = h:option(DummyValue, "_dummy", translate("Service Status"))
132                 ss.template = "simple-adblock/status"
133                 ss.value = statusTable[tmpfsStatus]
134                 if fs.access(outputCache) then
135                         sm = h:option(DummyValue, "_dummy", translate("Info"))
136                         sm.template = "simple-adblock/status"
137                         sm.value = translate("Cache file containing") .. " " .. util.trim(sys.exec("wc -l < " .. outputCache)) .. " " .. translate("domains found") .. "."
138                 elseif fs.access(outputGzip) then
139                         sm = h:option(DummyValue, "_dummy", translate("Info"))
140                         sm.template = "simple-adblock/status"
141                         sm.value = translate("Compressed cache file found") .. "."
142                 end
143         else
144                 ss = h:option(DummyValue, "_dummy", translate("Service Status"))
145                 ss.template = "simple-adblock/status"
146                 if tmpfsStatus == "statusSuccess" then
147 --                      ss.value = tmpfsStats
148                         ss.value = tmpfsVersion .. " " .. translate("is blocking") .. 
149                                 " " .. util.trim(sys.exec("wc -l < " .. outputFile)) .. 
150                                 " " .. translate("domains") .. " (" .. translate("with") .. 
151                                 " " .. targetDNS .. ")."
152                 else
153                         ss.value = statusTable[tmpfsStatus]
154                 end
155                 if tmpfsMessage then
156                         ms = h:option(DummyValue, "_dummy", translate("Message"))
157                         ms.template = "simple-adblock/status"
158                         ms.value = tmpfsMessage
159                 end
160                 if tmpfsError then
161                         es = h:option(DummyValue, "_dummy", translate("Collected Errors"))
162                         es.template = "simple-adblock/error"
163                         es.value = ""
164                         local err, e, url
165                         for err in tmpfsError:gmatch("[%p%w]+") do
166                                 if err:match("=") then
167                                         e,url = err:match("(.+)=(.+)")
168                                         es.value = es.value .. translate("Error") .. ": " .. errorTable[e] .. " " .. url .. ".\n"
169                                 else
170                                         es.value = es.value .. translate("Error") .. ": " .. errorTable[err] .. ".\n"
171                                 end
172                         end
173                 end
174         end
175         buttons = h:option(DummyValue, "_dummy")
176         buttons.template = "simple-adblock/buttons"
177 end
178
179 s = m:section(NamedSection, "config", "simple-adblock", translate("Configuration"))
180 -- General options
181 s:tab("basic", translate("Basic Configuration"))
182
183 o2 = s:taboption("basic", ListValue, "verbosity", translate("Output Verbosity Setting"), translate("Controls system log and console output verbosity."))
184 o2:value("0", translate("Suppress output"))
185 o2:value("1", translate("Some output"))
186 o2:value("2", translate("Verbose output"))
187 o2.default = 2
188
189 o3 = s:taboption("basic", ListValue, "force_dns", translate("Force Router DNS"), translate("Forces Router DNS use on local devices, also known as DNS Hijacking."))
190 o3:value("0", translate("Let local devices use their own DNS servers if set"))
191 o3:value("1", translate("Force Router DNS server to all local devices"))
192 o3.default = 1
193
194 local sysfs_path = "/sys/class/leds/"
195 local leds = {}
196 if fs.access(sysfs_path) then
197         leds = nutil.consume((fs.dir(sysfs_path)))
198 end
199 if #leds ~= 0 then
200         o4 = s:taboption("basic", Value, "led", translate("LED to indicate status"), translate("Pick the LED not already used in")
201                 .. [[ <a href="]] .. dispatcher.build_url("admin", "system", "leds") .. [[">]]
202                 .. translate("System LED Configuration") .. [[</a>]] .. ".")
203         o4.rmempty = false
204         o4:value("", translate("none"))
205         for k, v in ipairs(leds) do
206                 o4:value(v)
207         end
208 end
209
210 s:tab("advanced", translate("Advanced Configuration"))
211
212 local dns_descr = translate("Pick the DNS resolution option to create the adblock list for, see the") .. " "
213                 .. [[<a href="]] .. readmeURL .. [[#dns-resolution-option" target="_blank">]]
214                 .. translate("README") .. [[</a>]] .. " " .. translate("for details.")
215
216 if not checkDnsmasq then
217         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>dnsmasq.addnhosts</i> " .. translate("is not supported on this system.")
218         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>dnsmasq.conf</i> " .. translate("is not supported on this system.")
219         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>dnsmasq.ipset</i> " .. translate("is not supported on this system.")
220         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>dnsmasq.servers</i> " .. translate("is not supported on this system.")
221 elseif not checkDnsmasqIpset then 
222         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>dnsmasq.ipset</i> " .. translate("is not supported on this system.")
223 end
224 if not checkUnbound then 
225         dns_descr = dns_descr .. "<br />" .. translate("Please note that") .. " <i>unbound.adb_list</i> " .. translate("is not supported on this system.")
226 end
227
228 dns = s:taboption("advanced", ListValue, "dns", translate("DNS Service"), dns_descr)
229 if checkDnsmasq then
230         dns:value("dnsmasq.addnhosts", translate("DNSMASQ Additional Hosts"))
231         dns:value("dnsmasq.conf", translate("DNSMASQ Config"))
232         if checkDnsmasqIpset then
233                 dns:value("dnsmasq.ipset", translate("DNSMASQ IP Set"))
234         end
235         dns:value("dnsmasq.servers", translate("DNSMASQ Servers File"))
236 end
237 if checkUnbound then
238         dns:value("unbound.adb_list", translate("Unbound AdBlock List"))
239 end
240 dns.default = "dnsmasq.servers"
241
242 ipv6 = s:taboption("advanced", ListValue, "ipv6_enabled", translate("IPv6 Support"), translate("Add IPv6 entries to block-list."))
243 ipv6:value("", translate("Do not add IPv6 entries"))
244 ipv6:value("1", translate("Add IPv6 entries"))
245 ipv6:depends({dns="dnsmasq.addnhosts"}) 
246 ipv6.default = ""
247 ipv6.rmempty = true
248
249 o5 = s:taboption("advanced", Value, "boot_delay", translate("Delay (in seconds) for on-boot start"), translate("Run service after set delay on boot."))
250 o5.default = 120
251 o5.datatype = "range(1,600)"
252
253 o6 = s:taboption("advanced", Value, "download_timeout", translate("Download time-out (in seconds)"), translate("Stop the download if it is stalled for set number of seconds."))
254 o6.default = 10
255 o6.datatype = "range(1,60)"
256
257 o7 = s:taboption("advanced", Value, "curl_retry", translate("Curl download retry"), translate("If curl is installed and detected, it would retry download this many times on timeout/fail."))
258 o7.default = 3
259 o7.datatype = "range(0,30)"
260
261 o8 = s:taboption("advanced", ListValue, "parallel_downloads", translate("Simultaneous processing"), translate("Launch all lists downloads and processing simultaneously, reducing service start time."))
262 o8:value("0", translate("Do not use simultaneous processing"))
263 o8:value("1", translate("Use simultaneous processing"))
264 o8.default = 1
265
266 o10 = s:taboption("advanced", ListValue, "compressed_cache", translate("Store compressed cache file on router"), translate("Attempt to create a compressed cache of block-list in the persistent memory."))
267 o10:value("0", translate("Do not store compressed cache"))
268 o10:value("1", translate("Store compressed cache"))
269 o10.default = "0"
270
271 o11 = s:taboption("advanced", ListValue, "debug", translate("Enable Debugging"), translate("Enables debug output to /tmp/simple-adblock.log."))
272 o11:value("0", translate("Disable Debugging"))
273 o11:value("1", translate("Enable Debugging"))
274 o11.default = "0"
275
276
277 s2 = m:section(NamedSection, "config", "simple-adblock", translate("Whitelist and Blocklist Management"))
278 -- Whitelisted Domains
279 d1 = s2:option(DynamicList, "whitelist_domain", translate("Whitelisted Domains"), translate("Individual domains to be whitelisted."))
280 d1.addremove = false
281 d1.optional = false
282
283 -- Blacklisted Domains
284 d3 = s2:option(DynamicList, "blacklist_domain", translate("Blacklisted Domains"), translate("Individual domains to be blacklisted."))
285 d3.addremove = false
286 d3.optional = false
287
288 -- Whitelisted Domains URLs
289 d2 = s2:option(DynamicList, "whitelist_domains_url", translate("Whitelisted Domain URLs"), translate("URLs to lists of domains to be whitelisted."))
290 d2.addremove = false
291 d2.optional = false
292
293 -- Blacklisted Domains URLs
294 d4 = s2:option(DynamicList, "blacklist_domains_url", translate("Blacklisted Domain URLs"), translate("URLs to lists of domains to be blacklisted."))
295 d4.addremove = false
296 d4.optional = false
297
298 -- Blacklisted Hosts URLs
299 d5 = s2:option(DynamicList, "blacklist_hosts_url", translate("Blacklisted Hosts URLs"), translate("URLs to lists of hosts to be blacklisted."))
300 d5.addremove = false
301 d5.optional = false
302
303 return m