131cbfdeae9254262a1ec4bec9a8645bd5ee46a2
[project/luci.git] / applications / luci-app-ddns / luasrc / model / cbi / ddns / detail.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
3 -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
4 -- Copyright 2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
5 -- Licensed to the public under the Apache License 2.0.
6
7 local NX   = require "nixio"
8 local NXFS = require "nixio.fs"
9 local SYS  = require "luci.sys"
10 local UTIL = require "luci.util"
11 local HTTP = require "luci.http"
12 local DISP = require "luci.dispatcher"
13 local WADM = require "luci.tools.webadmin"
14 local DTYP = require "luci.cbi.datatypes"
15 local CTRL = require "luci.controller.ddns"     -- this application's controller
16 local DDNS = require "luci.tools.ddns"          -- ddns multiused functions
17
18 -- takeover arguments -- #######################################################
19 local section = arg[1]
20
21 -- html constants -- ###########################################################
22 local font_red  = "<font color='red'>"
23 local font_off  = "</font>"
24 local bold_on   = "<strong>"
25 local bold_off  = "</strong>"
26
27 -- error text constants -- #####################################################
28 local err_ipv6_plain = translate("IPv6 not supported") .. " - " ..
29                 translate("please select 'IPv4' address version")
30 local err_ipv6_basic = bold_on ..
31                         font_red ..
32                                 translate("IPv6 not supported") ..
33                         font_off ..
34                         "<br />" .. translate("please select 'IPv4' address version") ..
35                  bold_off
36 local err_ipv6_other = bold_on ..
37                         font_red ..
38                                 translate("IPv6 not supported") ..
39                         font_off ..
40                         "<br />" .. translate("please select 'IPv4' address version in") .. " " ..
41                         [[<a href="]] ..
42                                         DISP.build_url("admin", "services", "ddns", "detail", section) ..
43                                         "?tab.dns." .. section .. "=basic" ..
44                                 [[">]] ..
45                                 translate("Basic Settings") ..
46                         [[</a>]] ..
47                  bold_off
48
49 function err_tab_basic(self)
50         return translate("Basic Settings") .. " - " .. self.title .. ": "
51 end
52 function err_tab_adv(self)
53         return translate("Advanced Settings") .. " - " .. self.title .. ": "
54 end
55 function err_tab_timer(self)
56         return translate("Timer Settings") .. " - " .. self.title .. ": "
57 end
58
59 -- read services/services_ipv6 files -- ########################################
60 local services4 = { }           -- IPv4 --
61 local fd4 = io.open("/usr/lib/ddns/services", "r")
62 if fd4 then
63         local ln, s, t
64         repeat
65                 ln = fd4:read("*l")
66                 s  = ln and ln:match('^%s*".*') -- only handle lines beginning with "
67                 s  = s  and  s:gsub('"','')     -- remove "
68                 t  = s  and UTIL.split(s,"(%s+)",nil,true)      -- split on whitespaces
69                 if t then services4[t[1]]=t[2] end
70         until not ln
71         fd4:close()
72 end
73
74 local services6 = { }           -- IPv6 --
75 local fd6 = io.open("/usr/lib/ddns/services_ipv6", "r")
76 if fd6 then
77         local ln, s, t
78         repeat
79                 ln = fd6:read("*l")
80                 s  = ln and ln:match('^%s*".*') -- only handle lines beginning with "
81                 s  = s  and  s:gsub('"','')     -- remove "
82                 t  = s  and UTIL.split(s,"(%s+)",nil,true)      -- split on whitespaces
83                 if t then services6[t[1]]=t[2] end
84         until not ln
85         fd6:close()
86 end
87
88 -- multi-used functions -- ####################################################
89 -- function to verify settings around ip_source
90 -- will use dynamic_dns_lucihelper to check if
91 -- local IP can be read
92 local function _verify_ip_source()
93         -- section is globally defined here be calling agrument (see above)
94         local _network   = "-"
95         local _url       = "-"
96         local _interface = "-"
97         local _script    = "-"
98         local _proxy     = ""
99
100         local _ipv6   = usev6:formvalue(section)
101         local _source = (_ipv6 == "1")
102                         and src6:formvalue(section)
103                         or  src4:formvalue(section)
104         if _source == "network" then
105                 _network = (_ipv6 == "1")
106                         and ipn6:formvalue(section)
107                         or  ipn4:formvalue(section)
108         elseif _source == "web" then
109                 _url = (_ipv6 == "1")
110                         and iurl6:formvalue(section)
111                         or  iurl4:formvalue(section)
112                 -- proxy only needed for checking url
113                 _proxy = (pxy) and pxy:formvalue(section) or ""
114         elseif _source == "interface" then
115                 _interface = ipi:formvalue(section)
116         elseif _source == "script" then
117                 _script = ips:formvalue(section)
118         end
119
120         local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh get_local_ip ]] ..
121                 _ipv6 .. [[ ]] .. _source .. [[ ]] .. _network .. [[ ]] ..
122                 _url .. [[ ]] .. _interface .. [[ ']] .. _script.. [[' ]] .. _proxy
123         return (SYS.call(command) == 0)
124 end
125
126 -- function to check if option is used inside url or script
127 -- return -1 on error, 0 NOT required, 1 required
128 local function _option_used(option, urlscript)
129         local surl      -- search string for url
130         local ssh       -- search string for script
131         local required  -- option used inside url or script
132
133         if     option == "domain"    then surl, ssh = '%[DOMAIN%]', '%$domain'
134         elseif option == "username"  then surl, ssh = '%[USERNAME%]', '%$username'
135         elseif option == "password"  then surl, ssh = '%[PASSWORD%]', '%$password'
136         elseif option == "param_enc" then surl, ssh = '%[PARAMENC%]', '%$param_enc'
137         elseif option == "param_opt" then surl, ssh = '%[PARAMOPT%]', '%$param_opt'
138         else
139                 error("undefined option")
140                 return -1       -- return on error
141         end
142
143         local required = false
144         -- handle url
145         if urlscript:find('http') then
146                 required = ( urlscript:find(surl) )
147         -- handle script
148         else
149                 if not urlscript:find("/") then
150                         -- might be inside ddns-scripts directory
151                         urlscript = "/usr/lib/ddns/" .. urlscript
152                 end
153                 -- problem with script exit here
154                 if not NXFS.access(urlscript) then return -1 end
155
156                 local f = io.input(urlscript)
157                 -- still problem with script exit here
158                 if not f then return -1 end
159                 for l in f:lines() do
160                         repeat
161                                 if l:find('^#') then break end  -- continue on comment lines
162                                 required = ( l:find(surl) or l:find(ssh) )
163                         until true
164                         if required then break end
165                 end
166                 f:close()
167         end
168         return (required and 1 or 0)
169 end
170
171 -- function to verify if option is valid
172 local function _option_validate(self, value)
173         -- section is globally defined here be calling agrument (see above)
174         local fusev6 = usev6:formvalue(section) or "0"
175         local fsvc4  = svc4:formvalue(section) or "-"
176         local fsvc6  = svc6:formvalue(section) or "-"
177         local urlsh, used
178
179         -- IP-Version dependent custom service selected
180         if (fusev6 == "0" and fsvc4 == "-") or
181            (fusev6 == "1" and fsvc6 == "-") then
182                 -- read custom url
183                 urlsh = uurl:formvalue(section) or ""
184                 -- no url then read custom script
185                 if (#urlsh == 0) then
186                         urlsh = ush:formvalue(section) or ""
187                 end
188         -- IPv4 read from services4 table
189         elseif (fusev6 == "0") then
190                 urlsh = services4[fsvc4] or ""
191         -- IPv6 read from services6 table
192         else
193                 urlsh = services6[fsvc6] or ""
194         end
195         -- problem with url or script exit here
196         -- error handled somewhere else
197         if (#urlsh == 0) then return "" end
198
199         used = _option_used(self.option, urlsh)
200         -- on error or not used return empty sting
201         if used < 1 then return "" end
202         -- needed but no data then return error
203         if not value or (#value == 0) then
204                 return nil, err_tab_basic(self) .. translate("missing / required")
205         end
206         return value
207 end
208
209 -- cbi-map definition -- #######################################################
210 local m         = Map("ddns")
211 m.title         = CTRL.app_title_back()
212 m.description   = CTRL.app_description()
213 m.redirect      = DISP.build_url("admin", "services", "ddns")
214
215 m.on_after_commit = function(self)
216         if self.changed then    -- changes ?
217                 local pid = DDNS.get_pid(section)
218                 if pid > 0 then -- running ?
219                         local tmp = NX.kill(pid, 1)     -- send SIGHUP
220                 end
221         end
222 end
223
224 -- provider switch was requested, save and reload page
225 if m:formvalue("cbid.ddns.%s._switch" % section) then   -- section == arg[1]
226         local fsvc
227         local fusev6 = m:formvalue("cbid.ddns.%s.use_ipv6" % section) or "0"
228         if fusev6 == "1" then
229                 fsvc = m:formvalue("cbid.ddns.%s.ipv6_service_name" % section) or ""
230         else
231                 fsvc = m:formvalue("cbid.ddns.%s.ipv4_service_name" % section) or ""
232         end
233
234         if fusev6 ~= (m:get(section, "use_ipv6") or "0") then   -- IPv6 was changed
235                 m:set(section, "use_ipv6", fusev6)              -- save it
236         end
237
238         if fsvc ~= "-" then                                     -- NOT "custom"
239                 m:set(section, "service_name", fsvc)            -- save it
240         else                                                    -- else
241                 m:del(section, "service_name")                  -- delete it
242         end
243         m.uci:save(m.config)
244
245         -- reload page
246         HTTP.redirect( DISP.build_url("admin", "services", "ddns", "detail", section) )
247         return
248 end
249
250 -- read application settings -- ################################################
251 -- date format; if not set use ISO format
252 local date_format = m.uci:get(m.config, "global", "date_format") or "%F %R"
253 -- log directory
254 local log_dir = m.uci:get(m.config, "global", "log_dir") or "/var/log/ddns"
255
256 -- cbi-section definition -- ###################################################
257 local ns = m:section( NamedSection, section, "service",
258         translate("Details for") .. ([[: <strong>%s</strong>]] % section),
259         translate("Configure here the details for selected Dynamic DNS service.") )
260 ns.instance = section   -- arg [1]
261 ns:tab("basic", translate("Basic Settings"), nil )
262 ns:tab("advanced", translate("Advanced Settings"), nil )
263 ns:tab("timer", translate("Timer Settings"), nil )
264 ns:tab("logview", translate("Log File Viewer"), nil )
265
266 -- TAB: Basic  #####################################################################################
267 -- enabled -- #################################################################
268 en = ns:taboption("basic", Flag, "enabled",
269         translate("Enabled"),
270         translate("If this service section is disabled it could not be started." .. "<br />" ..
271                 "Neither from LuCI interface nor from console") )
272 en.orientation = "horizontal"
273
274 -- IPv4/IPv6 - lookup_host -- #################################################
275 luh = ns:taboption("basic", Value, "lookup_host",
276                 translate("Lookup Hostname"),
277                 translate("Hostname/FQDN to validate, if IP update happen or necessary") )
278 luh.rmempty     = false
279 luh.placeholder = "myhost.example.com"
280 function luh.validate(self, value)
281         if not value
282         or not (#value > 0)
283         or not DTYP.hostname(value) then
284                 return nil, err_tab_basic(self) .. translate("invalid FQDN / required - Sample") .. ": 'myhost.example.com'"
285         else
286                 return UTIL.trim(value)
287         end
288 end
289 function luh.parse(self, section, novld)
290         DDNS.value_parse(self, section, novld)
291 end
292
293 -- use_ipv6 -- ################################################################
294 usev6 = ns:taboption("basic", ListValue, "use_ipv6",
295         translate("IP address version"),
296         translate("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider") )
297 usev6.widget  = "radio"
298 usev6.default = "0"
299 usev6:value("0", translate("IPv4-Address") )
300 function usev6.cfgvalue(self, section)
301         local value = AbstractValue.cfgvalue(self, section) or "0"
302         if DDNS.has_ipv6 or (value == "1" and not DDNS.has_ipv6) then
303                 self:value("1", translate("IPv6-Address") )
304         end
305         if value == "1" and not DDNS.has_ipv6 then
306                 self.description = err_ipv6_basic
307         end
308         return value
309 end
310 function usev6.validate(self, value)
311         if (value == "1" and DDNS.has_ipv6) or value == "0" then
312                 return value
313         end
314         return nil, err_tab_basic(self) .. err_ipv6_plain
315 end
316 function usev6.parse(self, section, novld)
317         DDNS.value_parse(self, section, novld)
318 end
319
320 -- IPv4 - service_name -- #####################################################
321 svc4 = ns:taboption("basic", ListValue, "ipv4_service_name",
322         translate("DDNS Service provider") .. " [IPv4]" )
323 svc4.default    = "-"
324 svc4:depends("use_ipv6", "0")   -- only show on IPv4
325 function svc4.cfgvalue(self, section)
326         local v =  DDNS.read_value(self, section, "service_name")
327         if v and (#v > 0) then
328                 for s, u in UTIL.kspairs(services4) do
329                         if v == s then return v end
330                 end
331         end
332         return "-"
333 end
334 function svc4.validate(self, value)
335         if usev6:formvalue(section) ~= "1" then -- do only on IPv4
336                 return value
337         else
338                 return ""       -- suppress validate error
339         end
340 end
341 function svc4.write(self, section, value)
342         if usev6:formvalue(section) ~= "1" then -- do only IPv4 here
343                 self.map:del(section, self.option)      -- to be shure
344                 if value ~= "-" then                    -- and write "service_name
345                         self.map:del(section, "update_url")     -- delete update_url
346                         self.map:del(section, "update_script")  -- delete update_script
347                         return self.map:set(section, "service_name", value)
348                 else
349                         return self.map:del(section, "service_name")
350                 end
351         end
352 end
353 function svc4.parse(self, section, novld)
354         DDNS.value_parse(self, section, novld)
355 end
356
357 -- IPv6 - service_name -- #####################################################
358 svc6 = ns:taboption("basic", ListValue, "ipv6_service_name",
359         translate("DDNS Service provider") .. " [IPv6]" )
360 svc6.default    = "-"
361 svc6:depends("use_ipv6", "1")   -- only show on IPv6
362 if not DDNS.has_ipv6 then
363         svc6.description = err_ipv6_basic
364 end
365 function svc6.cfgvalue(self, section)
366         local v =  DDNS.read_value(self, section, "service_name")
367         if v and (#v > 0) then
368                 for s, u in UTIL.kspairs(services4) do
369                         if v == s then return v end
370                 end
371         end
372         return "-"
373 end
374 function svc6.validate(self, value)
375         if usev6:formvalue(section) == "1" then -- do only on IPv6
376                 if DDNS.has_ipv6 then return value end
377                 return nil, err_tab_basic(self) .. err_ipv6_plain
378         else
379                 return ""       -- suppress validate error
380         end
381 end
382 function svc6.write(self, section, value)
383         if usev6:formvalue(section) == "1" then         -- do only when IPv6
384                 self.map:del(section, self.option)      -- delete "ipv6_service_name" helper
385                 if value ~= "-" then                    -- and write "service_name
386                         self.map:del(section, "update_url")     -- delete update_url
387                         self.map:del(section, "update_script")  -- delete update_script
388                         return self.map:set(section, "service_name", value)
389                 else
390                         return self.map:del(section, "service_name")
391                 end
392         end
393 end
394 function svc6.parse(self, section, novld)
395         DDNS.value_parse(self, section, novld)
396 end
397
398 -- IPv4/IPv6 - change Provider -- #############################################
399 svs             = ns:taboption("basic", Button, "_switch")
400 svs.title      = translate("Really change DDNS provider?")
401 svs.inputtitle = translate("Change provider")
402 svs.inputstyle = "apply"
403
404 -- IPv4/IPv6 - update_url -- ##################################################
405 uurl = ns:taboption("basic", Value, "update_url",
406         translate("Custom update-URL"),
407         translate("Update URL to be used for updating your DDNS Provider." .. "<br />" ..
408                 "Follow instructions you will find on their WEB page.") )
409 function uurl.validate(self, value)
410         local fush   = ush:formvalue(section)
411         local fusev6 = usev6:formvalue(section)
412
413         if (fusev6 ~= "1" and svc4:formvalue(section) ~= "-") or
414            (fusev6 == "1" and svc6:formvalue(section) ~= "-") then
415                 return ""       -- suppress validate error
416         elseif not value or (#value == 0) then
417                 if not fush or (#fush == 0) then
418                         return nil, err_tab_basic(self) .. translate("missing / required")
419                 else
420                         return ""       -- suppress validate error / update_script is given
421                 end
422         elseif (#fush > 0) then
423                 return nil, err_tab_basic(self) .. translate("either url or script could be set")
424         end
425
426         local url = DDNS.parse_url(value)
427         if not url.scheme == "http" then
428                 return nil, err_tab_basic(self) .. translate("must start with 'http://'")
429         elseif not url.query then
430                 return nil, err_tab_basic(self) .. "<QUERY> " .. translate("missing / required")
431         elseif not url.host then
432                 return nil, err_tab_basic(self) .. "<HOST> " .. translate("missing / required")
433         elseif SYS.call([[nslookup ]] .. url.host .. [[ >/dev/null 2>&1]]) ~= 0 then
434                 return nil, err_tab_basic(self) .. translate("can not resolve host: ") .. url.host
435         end
436
437         return value
438 end
439 function uurl.parse(self, section, novld)
440         DDNS.value_parse(self, section, novld)
441 end
442
443 -- IPv4/IPv6 - update_script -- ###############################################
444 ush = ns:taboption("basic", Value, "update_script",
445         translate("Custom update-script"),
446         translate("Custom update script to be used for updating your DDNS Provider.") )
447 function ush.validate(self, value)
448         local fuurl  = uurl:formvalue(section)
449         local fusev6 = usev6:formvalue(section)
450
451         if (fusev6 ~= "1" and svc4:formvalue(section) ~= "-") or
452            (fusev6 == "1" and svc6:formvalue(section) ~= "-") then
453                 return ""       -- suppress validate error
454         elseif not value or (#value == 0) then
455                 if not fuurl or (#fuurl == 0) then
456                         return nil, err_tab_basic(self) .. translate("missing / required")
457                 else
458                         return ""       -- suppress validate error / update_url is given
459                 end
460         elseif (#fuurl > 0) then
461                 return nil, err_tab_basic(self) .. translate("either url or script could be set")
462         elseif not NXFS.access(value) then
463                 return nil, err_tab_basic(self) .. translate("File not found")
464         end
465         return value
466 end
467 function ush.parse(self, section, novld)
468         DDNS.value_parse(self, section, novld)
469 end
470
471 -- IPv4/IPv6 - domain -- ######################################################
472 dom = ns:taboption("basic", Value, "domain",
473                 translate("Domain"),
474                 translate("Replaces [DOMAIN] in Update-URL") )
475 dom.placeholder = "myhost.example.com"
476 function dom.validate(self, value)
477         return _option_validate(self, value)
478 end
479 function dom.parse(self, section, novld)
480         DDNS.value_parse(self, section, novld)
481 end
482
483 -- IPv4/IPv6 - username -- ####################################################
484 user = ns:taboption("basic", Value, "username",
485                 translate("Username"),
486                 translate("Replaces [USERNAME] in Update-URL (URL-encoded)") )
487 function user.validate(self, value)
488         return _option_validate(self, value)
489 end
490 function user.parse(self, section, novld)
491         DDNS.value_parse(self, section, novld)
492 end
493
494 -- IPv4/IPv6 - password -- ####################################################
495 pw = ns:taboption("basic", Value, "password",
496                 translate("Password"),
497                 translate("Replaces [PASSWORD] in Update-URL (URL-encoded)") )
498 pw.password = true
499 function pw.validate(self, value)
500         return _option_validate(self, value)
501 end
502 function pw.parse(self, section, novld)
503         DDNS.value_parse(self, section, novld)
504 end
505
506 -- IPv4/IPv6 - param_enc -- ###################################################
507 pe = ns:taboption("basic", Value, "param_enc",
508                 translate("Optional Encoded Parameter"),
509                 translate("Optional: Replaces [PARAMENC] in Update-URL (URL-encoded)") )
510 function pe.validate(self, value)
511         return _option_validate(self, value)
512 end
513 function pe.parse(self, section, novld)
514         DDNS.value_parse(self, section, novld)
515 end
516
517 -- IPv4/IPv6 - param_enc -- ###################################################
518 po = ns:taboption("basic", Value, "param_opt",
519                 translate("Optional Parameter"),
520                 translate("Optional: Replaces [PARAMOPT] in Update-URL (NOT URL-encoded)") )
521 function po.validate(self, value)
522         return _option_validate(self, value)
523 end
524 function po.parse(self, section, novld)
525         DDNS.value_parse(self, section, novld)
526 end
527
528 -- handled service dependent show/display -- ##################################
529 -- IPv4 --
530 local cv4 = svc4:cfgvalue(section)
531 if cv4 ~= "-" then
532         svs:depends ("ipv4_service_name", "-" ) -- show only if "-"
533         ush:depends ("ipv4_service_name", "?")
534         uurl:depends("ipv4_service_name", "?")
535 else
536         uurl:depends("ipv4_service_name", "-")
537         ush:depends ("ipv4_service_name", "-")
538         dom:depends("ipv4_service_name", "-" )
539         user:depends("ipv4_service_name", "-" )
540         pw:depends("ipv4_service_name", "-" )
541         pe:depends("ipv4_service_name", "-" )
542         po:depends("ipv4_service_name", "-" )
543 end
544 for s, u in UTIL.kspairs(services4) do
545         svc4:value(s)   -- fill DropDown-List
546         if cv4 ~= s then
547                 svs:depends("ipv4_service_name", s )
548         else
549                 dom:depends ("ipv4_service_name", ((_option_used(dom.option, u) == 1) and s or "?") )
550                 user:depends("ipv4_service_name", ((_option_used(user.option, u) == 1) and s or "?") )
551                 pw:depends  ("ipv4_service_name", ((_option_used(pw.option, u) == 1) and s or "?") )
552                 pe:depends  ("ipv4_service_name", ((_option_used(pe.option, u) == 1) and s or "?") )
553                 po:depends  ("ipv4_service_name", ((_option_used(po.option, u) == 1) and s or "?") )
554         end
555 end
556 svc4:value("-", translate("-- custom --") )
557
558 -- IPv6 --
559 local cv6 = svc6:cfgvalue(section)
560 if cv6 ~= "-" then
561         svs:depends ("ipv6_service_name", "-" )
562         uurl:depends("ipv6_service_name", "?")
563         ush:depends ("ipv6_service_name", "?")
564 else
565         uurl:depends("ipv6_service_name", "-")
566         ush:depends ("ipv6_service_name", "-")
567         dom:depends("ipv6_service_name", "-" )
568         user:depends("ipv6_service_name", "-" )
569         pw:depends("ipv6_service_name", "-" )
570         pe:depends("ipv6_service_name", "-" )
571         po:depends("ipv6_service_name", "-" )
572 end
573 for s, u in UTIL.kspairs(services6) do
574         svc6:value(s)   -- fill DropDown-List
575         if cv6 ~= s then
576                 svs:depends("ipv6_service_name", s )
577         else
578                 dom:depends ("ipv6_service_name", ((_option_used(dom.option, u) == 1) and s or "?") )
579                 user:depends("ipv6_service_name", ((_option_used(user.option, u) == 1) and s or "?") )
580                 pw:depends  ("ipv6_service_name", ((_option_used(pw.option, u) == 1) and s or "?") )
581                 pe:depends  ("ipv6_service_name", ((_option_used(pe.option, u) == 1) and s or "?") )
582                 po:depends  ("ipv6_service_name", ((_option_used(po.option, u) == 1) and s or "?") )
583         end
584 end
585 svc6:value("-", translate("-- custom --") )
586
587 -- IPv4/IPv6 - use_https -- ###################################################
588 if DDNS.has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
589         https = ns:taboption("basic", Flag, "use_https",
590                 translate("Use HTTP Secure") )
591         https.orientation = "horizontal"
592         function https.cfgvalue(self, section)
593                 local value = AbstractValue.cfgvalue(self, section)
594                 if not DDNS.has_ssl and value == "1" then
595                         self.description = bold_on .. font_red ..
596                                 translate("HTTPS not supported") .. font_off .. "<br />" ..
597                                 translate("please disable") .. " !" .. bold_off
598                 else
599                         self.description = translate("Enable secure communication with DDNS provider")
600                 end
601                 return value
602         end
603         function https.validate(self, value)
604                 if (value == "1" and DDNS.has_ssl ) or value == "0" then return value end
605                 return nil, err_tab_basic(self) .. translate("HTTPS not supported") .. " !"
606         end
607         function https.write(self, section, value)
608                 if value == "1" then
609                         return self.map:set(section, self.option, value)
610                 else
611                         self.map:del(section, "cacert")
612                         return self.map:del(section, self.option)
613                 end
614         end
615 end
616
617 -- IPv4/IPv6 - cacert -- ######################################################
618 if DDNS.has_ssl then
619         cert = ns:taboption("basic", Value, "cacert",
620                 translate("Path to CA-Certificate"),
621                 translate("directory or path/file") .. "<br />" ..
622                 translate("or") .. bold_on .. " IGNORE " .. bold_off ..
623                 translate("to run HTTPS without verification of server certificates (insecure)") )
624         cert:depends("use_https", "1")
625         cert.placeholder = "/etc/ssl/certs"
626         cert.forcewrite = true
627         function cert.validate(self, value)
628                 if https:formvalue(section) ~= "1" then
629                         return ""       -- suppress validate error if NOT https
630                 end
631                 if value then   -- otherwise errors in datatype check
632                         if DTYP.directory(value)
633                         or DTYP.file(value)
634                         or (value == "IGNORE")
635                         or (#value == 0) then
636                                 return value
637                         end
638                 end
639                 return nil, err_tab_basic(self) ..
640                         translate("file or directory not found or not 'IGNORE'") .. " !"
641         end
642         function cert.parse(self, section, novld)
643                 DDNS.value_parse(self, section, novld)
644         end
645 end
646
647 -- TAB: Advanced  #################################################################################
648 -- IPv4 - ip_source -- ########################################################
649 src4 = ns:taboption("advanced", ListValue, "ipv4_source",
650         translate("IP address source") .. " [IPv4]",
651         translate("Defines the source to read systems IPv4-Address from, that will be send to the DDNS provider") )
652 src4:depends("use_ipv6", "0")   -- IPv4 selected
653 src4.default = "network"
654 src4:value("network", translate("Network"))
655 src4:value("web", translate("URL"))
656 src4:value("interface", translate("Interface"))
657 src4:value("script", translate("Script"))
658 function src4.cfgvalue(self, section)
659         return DDNS.read_value(self, section, "ip_source")
660 end
661 function src4.validate(self, value)
662         if usev6:formvalue(section) == "1" then
663                 return ""       -- ignore on IPv6 selected
664         elseif not _verify_ip_source() then
665                 return nil, err_tab_adv(self) ..
666                         translate("can not detect local IP. Please select a different Source combination")
667         else
668                 return value
669         end
670 end
671 function src4.write(self, section, value)
672         if usev6:formvalue(section) == "1" then
673                 return true     -- ignore on IPv6 selected
674         elseif value == "network" then
675                 self.map:del(section, "ip_url")         -- delete not need parameters
676                 self.map:del(section, "ip_interface")
677                 self.map:del(section, "ip_script")
678         elseif value == "web" then
679                 self.map:del(section, "ip_network")     -- delete not need parameters
680                 self.map:del(section, "ip_interface")
681                 self.map:del(section, "ip_script")
682         elseif value == "interface" then
683                 self.map:del(section, "ip_network")     -- delete not need parameters
684                 self.map:del(section, "ip_url")
685                 self.map:del(section, "ip_script")
686         elseif value == "script" then
687                 self.map:del(section, "ip_network")
688                 self.map:del(section, "ip_url")         -- delete not need parameters
689                 self.map:del(section, "ip_interface")
690         end
691         self.map:del(section, self.option)               -- delete "ipv4_source" helper
692         return self.map:set(section, "ip_source", value) -- and write "ip_source
693 end
694 function src4.parse(self, section, novld)
695         DDNS.value_parse(self, section, novld)
696 end
697
698 -- IPv6 - ip_source -- ########################################################
699 src6 = ns:taboption("advanced", ListValue, "ipv6_source",
700         translate("IP address source") .. " [IPv6]",
701         translate("Defines the source to read systems IPv6-Address from, that will be send to the DDNS provider") )
702 src6:depends("use_ipv6", 1)     -- IPv6 selected
703 src6.default = "network"
704 src6:value("network", translate("Network"))
705 src6:value("web", translate("URL"))
706 src6:value("interface", translate("Interface"))
707 src6:value("script", translate("Script"))
708 if not DDNS.has_ipv6 then
709         src6.description = err_ipv6_other
710 end
711 function src6.cfgvalue(self, section)
712         return DDNS.read_value(self, section, "ip_source")
713 end
714 function src6.validate(self, value)
715         if usev6:formvalue(section) ~= "1" then
716                 return ""       -- ignore on IPv4 selected
717         elseif not DDNS.has_ipv6 then
718                 return nil, err_tab_adv(self) .. err_ipv6_plain
719         elseif not _verify_ip_source() then
720                 return nil, err_tab_adv(self) ..
721                         translate("can not detect local IP. Please select a different Source combination")
722         else
723                 return value
724         end
725 end
726 function src6.write(self, section, value)
727         if usev6:formvalue(section) ~= "1" then
728                 return true     -- ignore on IPv4 selected
729         elseif value == "network" then
730                 self.map:del(section, "ip_url")         -- delete not need parameters
731                 self.map:del(section, "ip_interface")
732                 self.map:del(section, "ip_script")
733         elseif value == "web" then
734                 self.map:del(section, "ip_network")     -- delete not need parameters
735                 self.map:del(section, "ip_interface")
736                 self.map:del(section, "ip_script")
737         elseif value == "interface" then
738                 self.map:del(section, "ip_network")     -- delete not need parameters
739                 self.map:del(section, "ip_url")
740                 self.map:del(section, "ip_script")
741         elseif value == "script" then
742                 self.map:del(section, "ip_network")
743                 self.map:del(section, "ip_url")         -- delete not need parameters
744                 self.map:del(section, "ip_interface")
745         end
746         self.map:del(section, self.option)               -- delete "ipv4_source" helper
747         return self.map:set(section, "ip_source", value) -- and write "ip_source
748 end
749 function src6.parse(self, section, novld)
750         DDNS.value_parse(self, section, novld)
751 end
752
753 -- IPv4 - ip_network (default "wan") -- #######################################
754 ipn4 = ns:taboption("advanced", ListValue, "ipv4_network",
755         translate("Network") .. " [IPv4]",
756         translate("Defines the network to read systems IPv4-Address from") )
757 ipn4:depends("ipv4_source", "network")
758 ipn4.default = "wan"
759 WADM.cbi_add_networks(ipn4)
760 function ipn4.cfgvalue(self, section)
761         return DDNS.read_value(self, section, "ip_network")
762 end
763 function ipn4.validate(self, value)
764         if usev6:formvalue(section) == "1"
765          or src4:formvalue(section) ~= "network" then
766                 -- ignore if IPv6 selected OR
767                 -- ignore everything except "network"
768                 return ""
769         else
770                 return value
771         end
772 end
773 function ipn4.write(self, section, value)
774         if usev6:formvalue(section) == "1"
775          or src4:formvalue(section) ~= "network" then
776                 -- ignore if IPv6 selected OR
777                 -- ignore everything except "network"
778                 return true
779         else
780                 -- set also as "interface" for monitoring events changes/hot-plug
781                 self.map:set(section, "interface", value)
782                 self.map:del(section, self.option)                -- delete "ipv4_network" helper
783                 return self.map:set(section, "ip_network", value) -- and write "ip_network"
784         end
785 end
786 function ipn4.parse(self, section, novld)
787         DDNS.value_parse(self, section, novld)
788 end
789
790 -- IPv6 - ip_network (default "wan6") -- ######################################
791 ipn6 = ns:taboption("advanced", ListValue, "ipv6_network",
792         translate("Network") .. " [IPv6]" )
793 ipn6:depends("ipv6_source", "network")
794 ipn6.default = "wan6"
795 WADM.cbi_add_networks(ipn6)
796 if DDNS.has_ipv6 then
797         ipn6.description = translate("Defines the network to read systems IPv6-Address from")
798 else
799         ipn6.description = err_ipv6_other
800 end
801 function ipn6.cfgvalue(self, section)
802         return DDNS.read_value(self, section, "ip_network")
803 end
804 function ipn6.validate(self, value)
805         if usev6:formvalue(section) ~= "1"
806          or src6:formvalue(section) ~= "network" then
807                 -- ignore if IPv4 selected OR
808                 -- ignore everything except "network"
809                 return ""
810         elseif DDNS.has_ipv6 then
811                 return value
812         else
813                 return nil, err_tab_adv(self) .. err_ipv6_plain
814         end
815 end
816 function ipn6.write(self, section, value)
817         if usev6:formvalue(section) ~= "1"
818          or src6:formvalue(section) ~= "network" then
819                 -- ignore if IPv4 selected OR
820                 -- ignore everything except "network"
821                 return true
822         else
823                 -- set also as "interface" for monitoring events changes/hotplug
824                 self.map:set(section, "interface", value)
825                 self.map:del(section, self.option)                -- delete "ipv6_network" helper
826                 return self.map:set(section, "ip_network", value) -- and write "ip_network"
827         end
828 end
829 function ipn6.parse(self, section, novld)
830         DDNS.value_parse(self, section, novld)
831 end
832
833 -- IPv4 - ip_url (default "checkip.dyndns.com") -- ############################
834 iurl4 = ns:taboption("advanced", Value, "ipv4_url",
835         translate("URL to detect") .. " [IPv4]",
836         translate("Defines the Web page to read systems IPv4-Address from") )
837 iurl4:depends("ipv4_source", "web")
838 iurl4.default = "http://checkip.dyndns.com"
839 function iurl4.cfgvalue(self, section)
840         return DDNS.read_value(self, section, "ip_url")
841 end
842 function iurl4.validate(self, value)
843         if usev6:formvalue(section) == "1"
844          or src4:formvalue(section) ~= "web" then
845                 -- ignore if IPv6 selected OR
846                 -- ignore everything except "web"
847                 return ""
848         elseif not value or #value == 0 then
849                 return nil, err_tab_adv(self) .. translate("missing / required")
850         end
851
852         local url = DDNS.parse_url(value)
853         if not (url.scheme == "http" or url.scheme == "https") then
854                 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
855         elseif not url.host then
856                 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
857         elseif SYS.call([[nslookup ]] .. url.host .. [[>/dev/null 2>&1]]) ~= 0 then
858                 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
859         else
860                 return value
861         end
862 end
863 function iurl4.write(self, section, value)
864         if usev6:formvalue(section) == "1"
865          or src4:formvalue(section) ~= "web" then
866                 -- ignore if IPv6 selected OR
867                 -- ignore everything except "web"
868                 return true
869         else
870                 self.map:del(section, self.option)              -- delete "ipv4_url" helper
871                 return self.map:set(section, "ip_url", value)   -- and write "ip_url"
872         end
873 end
874 function iurl4.parse(self, section, novld)
875         DDNS.value_parse(self, section, novld)
876 end
877
878 -- IPv6 - ip_url (default "checkipv6.dyndns.com") -- ##########################
879 iurl6 = ns:taboption("advanced", Value, "ipv6_url",
880         translate("URL to detect") .. " [IPv6]" )
881 iurl6:depends("ipv6_source", "web")
882 iurl6.default = "http://checkipv6.dyndns.com"
883 if DDNS.has_ipv6 then
884         iurl6.description = translate("Defines the Web page to read systems IPv6-Address from")
885 else
886         iurl6.description = err_ipv6_other
887 end
888 function iurl6.cfgvalue(self, section)
889         return DDNS.read_value(self, section, "ip_url")
890 end
891 function iurl6.validate(self, value)
892         if usev6:formvalue(section) ~= "1"
893          or src6:formvalue(section) ~= "web" then
894                 -- ignore if IPv4 selected OR
895                 -- ignore everything except "web"
896                 return ""
897         elseif not DDNS.has_ipv6 then
898                 return nil, err_tab_adv(self) .. err_ipv6_plain
899         elseif not value or #value == 0 then
900                 return nil, err_tab_adv(self) .. translate("missing / required")
901         end
902
903         local url = DDNS.parse_url(value)
904         if not (url.scheme == "http" or url.scheme == "https") then
905                 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
906         elseif not url.host then
907                 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
908         elseif SYS.call([[nslookup ]] .. url.host .. [[>/dev/null 2>&1]]) ~= 0 then
909                 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
910         else
911                 return value
912         end
913 end
914 function iurl6.write(self, section, value)
915         if usev6:formvalue(section) ~= "1"
916          or src6:formvalue(section) ~= "web" then
917                 -- ignore if IPv4 selected OR
918                 -- ignore everything except "web"
919                 return true
920         else
921                 self.map:del(section, self.option)              -- delete "ipv6_url" helper
922                 return self.map:set(section, "ip_url", value)   -- and write "ip_url"
923         end
924 end
925 function iurl6.parse(self, section, novld)
926         DDNS.value_parse(self, section, novld)
927 end
928
929 -- IPv4 + IPv6 - ip_interface -- ##############################################
930 ipi = ns:taboption("advanced", ListValue, "ip_interface",
931         translate("Interface"),
932         translate("Defines the interface to read systems IP-Address from") )
933 ipi:depends("ipv4_source", "interface") -- IPv4
934 ipi:depends("ipv6_source", "interface") -- or IPv6
935 for _, v in pairs(SYS.net.devices()) do
936         -- show only interface set to a network
937         -- and ignore loopback
938         net = WADM.iface_get_network(v)
939         if net and net ~= "loopback" then
940                 ipi:value(v)
941         end
942 end
943 function ipi.validate(self, value)
944         local fusev6 = usev6:formvalue(section)
945         if (fusev6 ~= "1" and src4:formvalue(section) ~= "interface")
946         or (fusev6 == "1" and src6:formvalue(section) ~= "interface") then
947                 return ""
948         else
949                 return value
950         end
951 end
952 function ipi.write(self, section, value)
953         local fusev6 = usev6:formvalue(section)
954         if (fusev6 ~= "1" and src4:formvalue(section) ~= "interface")
955         or (fusev6 == "1" and src6:formvalue(section) ~= "interface") then
956                 return true
957         else
958                 -- get network from device to
959                 -- set also as "interface" for monitoring events changes/hotplug
960                 local net = WADM.iface_get_network(value)
961                 self.map:set(section, "interface", net)
962                 return self.map:set(section, self.option, value)
963         end
964 end
965 function ipi.parse(self, section, novld)
966         DDNS.value_parse(self, section, novld)
967 end
968
969 -- IPv4 + IPv6 - ip_script -- #################################################
970 ips = ns:taboption("advanced", Value, "ip_script",
971         translate("Script"),
972         translate("User defined script to read systems IP-Address") )
973 ips:depends("ipv4_source", "script")    -- IPv4
974 ips:depends("ipv6_source", "script")    -- or IPv6
975 ips.placeholder = "/path/to/script.sh"
976 function ips.validate(self, value)
977         local fusev6 = usev6:formvalue(section)
978         local split
979         if value then split = UTIL.split(value, " ") end
980
981         if (fusev6 ~= "1" and src4:formvalue(section) ~= "script")
982         or (fusev6 == "1" and src6:formvalue(section) ~= "script") then
983                 return ""
984         elseif not value or not (#value > 0) or not NXFS.access(split[1], "x") then
985                 return nil, err_tab_adv(self) ..
986                         translate("not found or not executable - Sample: '/path/to/script.sh'")
987         else
988                 return value
989         end
990 end
991 function ips.write(self, section, value)
992         local fusev6 = usev6:formvalue(section)
993         if (fusev6 ~= "1" and src4:formvalue(section) ~= "script")
994         or (fusev6 == "1" and src6:formvalue(section) ~= "script") then
995                 return true
996         else
997                 return self.map:set(section, self.option, value)
998         end
999 end
1000 function ips.parse(self, section, novld)
1001         DDNS.value_parse(self, section, novld)
1002 end
1003
1004 -- IPv4 - interface - default "wan" -- ########################################
1005 -- event network to monitor changes/hotplug/dynamic_dns_updater.sh
1006 -- only needs to be set if "ip_source"="web" or "script"
1007 -- if "ip_source"="network" or "interface" we use their network
1008 eif4 = ns:taboption("advanced", ListValue, "ipv4_interface",
1009         translate("Event Network") .. " [IPv4]",
1010         translate("Network on which the ddns-updater scripts will be started") )
1011 eif4:depends("ipv4_source", "web")
1012 eif4:depends("ipv4_source", "script")
1013 eif4.default = "wan"
1014 WADM.cbi_add_networks(eif4)
1015 function eif4.cfgvalue(self, section)
1016         return DDNS.read_value(self, section, "interface")
1017 end
1018 function eif4.validate(self, value)
1019         local fsrc4 = src4:formvalue(section) or ""
1020         if usev6:formvalue(section) == "1"
1021          or fsrc4 == "network"
1022          or fsrc4 == "interface" then
1023                 return ""       -- ignore IPv6, network, interface
1024         else
1025                 return value
1026         end
1027 end
1028 function eif4.write(self, section, value)
1029         local fsrc4 = src4:formvalue(section) or ""
1030         if usev6:formvalue(section) == "1"
1031          or fsrc4 == "network"
1032          or fsrc4 == "interface" then
1033                 return true     -- ignore IPv6, network, interface
1034         else
1035                 self.map:del(section, self.option)               -- delete "ipv4_interface" helper
1036                 return self.map:set(section, "interface", value) -- and write "interface"
1037         end
1038 end
1039 function eif4.parse(self, section, novld)
1040         DDNS.value_parse(self, section, novld)
1041 end
1042
1043 -- IPv6 - interface - default "wan6" -- #######################################
1044 -- event network to monitor changes/hotplug
1045 -- only needs to be set if "ip_source"="web" or "script"
1046 -- if "ip_source"="network" or "interface" we use their network
1047 eif6 = ns:taboption("advanced", ListValue, "ipv6_interface",
1048         translate("Event Network") .. " [IPv6]" )
1049 eif6:depends("ipv6_source", "web")
1050 eif6:depends("ipv6_source", "script")
1051 eif6.default = "wan6"
1052 WADM.cbi_add_networks(eif6)
1053 if not DDNS.has_ipv6 then
1054         eif6.description = err_ipv6_other
1055 else
1056         eif6.description = translate("Network on which the ddns-updater scripts will be started")
1057 end
1058 function eif6.cfgvalue(self, section)
1059         return DDNS.read_value(self, section, "interface")
1060 end
1061 function eif6.validate(self, value)
1062         local fsrc6 = src6:formvalue(section) or ""
1063         if usev6:formvalue(section) ~= "1"
1064          or fsrc6 == "network"
1065          or fsrc6 == "interface" then
1066                 return ""       -- ignore IPv4, network, interface
1067         elseif not DDNS.has_ipv6 then
1068                 return nil, err_tab_adv(self) .. err_ipv6_plain
1069         else
1070                 return value
1071         end
1072 end
1073 function eif6.write(self, section, value)
1074         local fsrc6 = src6:formvalue(section) or ""
1075         if usev6:formvalue(section) ~= "1"
1076          or fsrc6 == "network"
1077          or fsrc6 == "interface" then
1078                 return true     -- ignore IPv4, network, interface
1079         else
1080                 self.map:del(section, self.option)               -- delete "ipv6_interface" helper
1081                 return self.map:set(section, "interface", value) -- and write "interface"
1082         end
1083 end
1084 function eif6.parse(self, section, novld)
1085         DDNS.value_parse(self, section, novld)
1086 end
1087
1088 -- IPv4/IPv6 - bind_network -- ################################################
1089 if DDNS.has_bindnet or ( ( m:get(section, "bind_network") or "" ) ~= "" ) then
1090         bnet = ns:taboption("advanced", ListValue, "bind_network",
1091                 translate("Bind Network") )
1092         bnet:depends("ipv4_source", "web")
1093         bnet:depends("ipv6_source", "web")
1094         bnet.default = ""
1095         bnet:value("", translate("-- default --"))
1096         WADM.cbi_add_networks(bnet)
1097         function bnet.cfgvalue(self, section)
1098                 local value = AbstractValue.cfgvalue(self, section)
1099                 if not DDNS.has_bindnet and value ~= "" then
1100                         self.description = bold_on .. font_red ..
1101                                 translate("Binding to a specific network not supported") .. font_off .. "<br />" ..
1102                                 translate("please set to 'default'") .. " !" .. bold_off
1103                 else
1104                         self.description = translate("OPTIONAL: Network to use for communication") ..
1105                                 "<br />" .. translate("Casual users should not change this setting")
1106                 end
1107                 return value
1108         end
1109         function bnet.validate(self, value)
1110                 if ( (value ~= "") and DDNS.has_bindnet ) or (value == "") then return value end
1111                 return nil, err_tab_adv(self) .. translate("Binding to a specific network not supported") .. " !"
1112         end
1113         function bnet.parse(self, section, novld)
1114                 DDNS.value_parse(self, section, novld)
1115         end
1116 end
1117
1118 -- IPv4 + IPv6 - force_ipversion -- ###########################################
1119 -- optional to force wget/curl and host to use only selected IP version
1120 -- command parameter "-4" or "-6"
1121 if DDNS.has_forceip or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
1122         fipv = ns:taboption("advanced", Flag, "force_ipversion",
1123                 translate("Force IP Version") )
1124         fipv.orientation = "horizontal"
1125         function fipv.cfgvalue(self, section)
1126                 local value = AbstractValue.cfgvalue(self, section)
1127                 if not DDNS.has_forceip and value ~= "0" then
1128                         self.description = bold_on .. font_red ..
1129                                 translate("Force IP Version not supported") .. font_off .. "<br />" ..
1130                                 translate("please disable") .. " !" .. bold_off
1131                 else
1132                         self.description = translate("OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.")
1133                 end
1134                 return value
1135         end
1136         function fipv.validate(self, value)
1137                 if (value == "1" and DDNS.has_forceip) or value == "0" then return value end
1138                 return nil, err_tab_adv(self) .. translate("Force IP Version not supported")
1139         end
1140 end
1141
1142 -- IPv4 + IPv6 - dns_server -- ################################################
1143 -- optional DNS Server to use resolving my IP
1144 if DDNS.has_dnsserver or ( ( m:get(section, "dns_server") or "" ) ~= "" ) then
1145         dns = ns:taboption("advanced", Value, "dns_server",
1146                 translate("DNS-Server"),
1147                 translate("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") .. "<br />" ..
1148                 translate("Format: IP or FQDN"))
1149         dns.placeholder = "mydns.lan"
1150         function dns.validate(self, value)
1151                 -- if .datatype is set, then it is checked before calling this function
1152                 if not value or (#value == 0) then
1153                         return ""       -- ignore on empty
1154                 elseif not DDNS.has_dnsserver then
1155                         return nil, err_tab_adv(self) .. translate("Specifying a DNS-Server is not supported")
1156                 elseif not DTYP.host(value) then
1157                         return nil, err_tab_adv(self) .. translate("use hostname, FQDN, IPv4- or IPv6-Address")
1158                 else
1159                         local ipv6  = usev6:formvalue(section) or "0"
1160                         local force = fipv:formvalue(section)  or "0"
1161                         local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_dns ]] ..
1162                                 value .. [[ ]] .. ipv6 .. [[ ]] .. force
1163                         local ret = SYS.call(command)
1164                         if     ret == 0 then return value       -- everything OK
1165                         elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
1166                         elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
1167                         elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
1168                         else                 return nil, err_tab_adv(self) .. translate("unspecific error")
1169                         end
1170                 end
1171         end
1172         function dns.parse(self, section, novld)
1173                 DDNS.value_parse(self, section, novld)
1174         end
1175 end
1176
1177 -- IPv4 + IPv6 - force_dnstcp -- ##############################################
1178 if DDNS.has_bindhost or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
1179         tcp = ns:taboption("advanced", Flag, "force_dnstcp",
1180                 translate("Force TCP on DNS") )
1181         tcp.orientation = "horizontal"
1182         function tcp.cfgvalue(self, section)
1183                 local value = AbstractValue.cfgvalue(self, section)
1184                 if not DDNS.has_bindhost and value ~= "0" then
1185                         self.description = bold_on .. font_red ..
1186                                 translate("DNS requests via TCP not supported") .. font_off .. "<br />" ..
1187                                 translate("please disable") .. " !" .. bold_off
1188                 else
1189                         self.description = translate("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests.")
1190                 end
1191                 return value
1192         end
1193         function tcp.validate(self, value)
1194                 if (value == "1" and DDNS.has_bindhost ) or value == "0" then
1195                         return value
1196                 end
1197                 return nil, err_tab_adv(self) .. translate("DNS requests via TCP not supported")
1198         end
1199 end
1200
1201 -- IPv4 + IPv6 - proxy -- #####################################################
1202 -- optional Proxy to use for http/https requests  [user:password@]proxyhost[:port]
1203 if DDNS.has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
1204         pxy = ns:taboption("advanced", Value, "proxy",
1205                 translate("PROXY-Server") )
1206         pxy.placeholder="user:password@myproxy.lan:8080"
1207         function pxy.cfgvalue(self, section)
1208                 local value = AbstractValue.cfgvalue(self, section)
1209                 if not DDNS.has_proxy and value ~= "" then
1210                         self.description = bold_on .. font_red ..
1211                                 translate("PROXY-Server not supported") .. font_off .. "<br />" ..
1212                                 translate("please remove entry") .. "!" .. bold_off
1213                 else
1214                         self.description = translate("OPTIONAL: Proxy-Server for detection and updates.") .. "<br />" ..
1215                                 translate("Format") .. ": " .. bold_on .. "[user:password@]proxyhost:port" .. bold_off .. "<br />" ..
1216                                 translate("IPv6 address must be given in square brackets") .. ": " ..
1217                                 bold_on .. " [2001:db8::1]:8080" .. bold_off
1218                 end
1219                 return value
1220         end
1221         function pxy.validate(self, value)
1222                 -- if .datatype is set, then it is checked before calling this function
1223                 if not value or (#value == 0) then
1224                         return ""       -- ignore on empty
1225                 elseif DDNS.has_proxy then
1226                         local ipv6  = usev6:formvalue(section) or "0"
1227                         local force = fipv:formvalue(section) or "0"
1228                         local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_proxy ]] ..
1229                                 value .. [[ ]] .. ipv6 .. [[ ]] .. force
1230                         local ret = SYS.call(command)
1231                         if     ret == 0 then return value
1232                         elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
1233                         elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
1234                         elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
1235                         elseif ret == 5 then return nil, err_tab_adv(self) .. translate("proxy port missing")
1236                         else                 return nil, err_tab_adv(self) .. translate("unspecific error")
1237                         end
1238                 else
1239                         return nil, err_tab_adv(self) .. translate("PROXY-Server not supported")
1240                 end
1241         end
1242         function pxy.parse(self, section, novld)
1243                 DDNS.value_parse(self, section, novld)
1244         end
1245 end
1246
1247 -- use_syslog -- ##############################################################
1248 slog = ns:taboption("advanced", ListValue, "use_syslog",
1249         translate("Log to syslog"),
1250         translate("Writes log messages to syslog. Critical Errors will always be written to syslog.") )
1251 slog.default = "2"
1252 slog:value("0", translate("No logging"))
1253 slog:value("1", translate("Info"))
1254 slog:value("2", translate("Notice"))
1255 slog:value("3", translate("Warning"))
1256 slog:value("4", translate("Error"))
1257 function slog.parse(self, section, novld)
1258         DDNS.value_parse(self, section, novld)
1259 end
1260
1261 -- use_logfile -- #############################################################
1262 logf = ns:taboption("advanced", Flag, "use_logfile",
1263         translate("Log to file"),
1264         translate("Writes detailed messages to log file. File will be truncated automatically.") .. "<br />" ..
1265         translate("File") .. [[: "]] .. log_dir .. [[/]] .. section .. [[.log"]] )
1266 logf.orientation = "horizontal"
1267 logf.default     = "1"          -- if not defined write to log by default
1268
1269 -- TAB: Timer  ####################################################################################
1270 -- check_interval -- ##########################################################
1271 ci = ns:taboption("timer", Value, "check_interval",
1272         translate("Check Interval") )
1273 ci.template = "ddns/detail_value"
1274 ci.default  = "10"
1275 function ci.validate(self, value)
1276         if not DTYP.uinteger(value)
1277         or tonumber(value) < 1 then
1278                 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1279         end
1280
1281         local secs = DDNS.calc_seconds(value, cu:formvalue(section))
1282         if secs >= 300 then
1283                 return value
1284         else
1285                 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1286         end
1287 end
1288 function ci.write(self, section, value)
1289         -- remove when default
1290         local secs = DDNS.calc_seconds(value, cu:formvalue(section))
1291         if secs ~= 600 then     --default 10 minutes
1292                 return self.map:set(section, self.option, value)
1293         else
1294                 self.map:del(section, "check_unit")
1295                 return self.map:del(section, self.option)
1296         end
1297 end
1298 function ci.parse(self, section, novld)
1299         DDNS.value_parse(self, section, novld)
1300 end
1301
1302 -- check_unit -- ##############################################################
1303 cu = ns:taboption("timer", ListValue, "check_unit", "not displayed, but needed otherwise error",
1304         translate("Interval to check for changed IP" .. "<br />" ..
1305                 "Values below 5 minutes == 300 seconds are not supported") )
1306 cu.template = "ddns/detail_lvalue"
1307 cu.default  = "minutes"
1308 cu:value("seconds", translate("seconds"))
1309 cu:value("minutes", translate("minutes"))
1310 cu:value("hours", translate("hours"))
1311 --cu:value("days", translate("days"))
1312 function cu.write(self, section, value)
1313         -- remove when default
1314         local secs = DDNS.calc_seconds(ci:formvalue(section), value)
1315         if secs ~= 600 then     --default 10 minutes
1316                 return self.map:set(section, self.option, value)
1317         else
1318                 return true
1319         end
1320 end
1321 function cu.parse(self, section, novld)
1322         DDNS.value_parse(self, section, novld)
1323 end
1324
1325 -- force_interval (modified) -- ###############################################
1326 fi = ns:taboption("timer", Value, "force_interval",
1327         translate("Force Interval") )
1328 fi.template = "ddns/detail_value"
1329 fi.default  = "72"      -- see dynamic_dns_updater.sh script
1330 --fi.rmempty = false    -- validate ourselves for translatable error messages
1331 function fi.validate(self, value)
1332         if not DTYP.uinteger(value)
1333         or tonumber(value) < 0 then
1334                 return nil, err_tab_timer(self) .. translate("minimum value '0'")
1335         end
1336
1337         local force_s = DDNS.calc_seconds(value, fu:formvalue(section))
1338         if force_s == 0 then
1339                 return value
1340         end
1341
1342         local ci_value = ci:formvalue(section)
1343         if not DTYP.uinteger(ci_value) then
1344                 return ""       -- ignore because error in check_interval above
1345         end
1346
1347         local check_s = DDNS.calc_seconds(ci_value, cu:formvalue(section))
1348         if force_s >= check_s then
1349                 return value
1350         end
1351
1352         return nil, err_tab_timer(self) .. translate("must be greater or equal 'Check Interval'")
1353 end
1354 function fi.write(self, section, value)
1355         -- simulate rmempty=true remove default
1356         local secs = DDNS.calc_seconds(value, fu:formvalue(section))
1357         if secs ~= 259200 then  --default 72 hours == 3 days
1358                 return self.map:set(section, self.option, value)
1359         else
1360                 self.map:del(section, "force_unit")
1361                 return self.map:del(section, self.option)
1362         end
1363 end
1364 function fi.parse(self, section, novld)
1365         DDNS.value_parse(self, section, novld)
1366 end
1367
1368 -- force_unit -- ##############################################################
1369 fu = ns:taboption("timer", ListValue, "force_unit", "not displayed, but needed otherwise error",
1370         translate("Interval to force updates send to DDNS Provider" .. "<br />" ..
1371                 "Setting this parameter to 0 will force the script to only run once" .. "<br />" ..
1372                 "Values lower 'Check Interval' except '0' are not supported") )
1373 fu.template = "ddns/detail_lvalue"
1374 fu.default  = "hours"
1375 --fu.rmempty  = false   -- want to control write process
1376 --fu:value("seconds", translate("seconds"))
1377 fu:value("minutes", translate("minutes"))
1378 fu:value("hours", translate("hours"))
1379 fu:value("days", translate("days"))
1380 function fu.write(self, section, value)
1381         -- simulate rmempty=true remove default
1382         local secs = DDNS.calc_seconds(fi:formvalue(section), value)
1383         if secs ~= 259200 and secs ~= 0 then    --default 72 hours == 3 days
1384                 return self.map:set(section, self.option, value)
1385         else
1386                 return true
1387         end
1388 end
1389 function fu.parse(self, section, novld)
1390         DDNS.value_parse(self, section, novld)
1391 end
1392
1393 -- retry_count -- #############################################################
1394 rc = ns:taboption("timer", Value, "retry_count")
1395 rc.title        = translate("Error Retry Counter")
1396 rc.description  = translate("On Error the script will stop execution after given number of retrys")
1397                 .. "<br />"
1398                 .. translate("The default setting of '0' will retry infinite.")
1399 rc.default      = "0"
1400 function rc.validate(self, value)
1401         if not DTYP.uinteger(value) then
1402                 return nil, err_tab_timer(self) .. translate("minimum value '0'")
1403         else
1404                 return value
1405         end
1406 end
1407 function rc.parse(self, section, novld)
1408         DDNS.value_parse(self, section, novld)
1409 end
1410
1411 -- retry_interval -- ##########################################################
1412 ri = ns:taboption("timer", Value, "retry_interval",
1413         translate("Error Retry Interval") )
1414 ri.template = "ddns/detail_value"
1415 ri.default  = "60"
1416 function ri.validate(self, value)
1417         if not DTYP.uinteger(value)
1418         or tonumber(value) < 1 then
1419                 return nil, err_tab_timer(self) .. translate("minimum value '1'")
1420         else
1421                 return value
1422         end
1423 end
1424 function ri.write(self, section, value)
1425         -- simulate rmempty=true remove default
1426         local secs = DDNS.calc_seconds(value, ru:formvalue(section))
1427         if secs ~= 60 then      --default 60seconds
1428                 return self.map:set(section, self.option, value)
1429         else
1430                 self.map:del(section, "retry_unit")
1431                 return self.map:del(section, self.option)
1432         end
1433 end
1434 function ri.parse(self, section, novld)
1435         DDNS.value_parse(self, section, novld)
1436 end
1437
1438 -- retry_unit -- ##############################################################
1439 ru = ns:taboption("timer", ListValue, "retry_unit", "not displayed, but needed otherwise error",
1440         translate("On Error the script will retry the failed action after given time") )
1441 ru.template = "ddns/detail_lvalue"
1442 ru.default  = "seconds"
1443 --ru.rmempty  = false   -- want to control write process
1444 ru:value("seconds", translate("seconds"))
1445 ru:value("minutes", translate("minutes"))
1446 --ru:value("hours", translate("hours"))
1447 --ru:value("days", translate("days"))
1448 function ru.write(self, section, value)
1449         -- simulate rmempty=true remove default
1450         local secs = DDNS.calc_seconds(ri:formvalue(section), value)
1451         if secs ~= 60 then      --default 60seconds
1452                 return self.map:set(section, self.option, value)
1453         else
1454                 return true -- will be deleted by retry_interval
1455         end
1456 end
1457 function ru.parse(self, section, novld)
1458         DDNS.value_parse(self, section, novld)
1459 end
1460
1461 -- TAB: LogView  ##################################################################################
1462 lv = ns:taboption("logview", DummyValue, "_logview")
1463 lv.template = "ddns/detail_logview"
1464 lv.inputtitle = translate("Read / Reread log file")
1465 lv.rows = 50
1466 function lv.cfgvalue(self, section)
1467         local lfile=log_dir .. "/" .. section .. ".log"
1468         if NXFS.access(lfile) then
1469                 return lfile .. "\n" .. translate("Please press [Read] button")
1470         end
1471         return lfile .. "\n" .. translate("File not found or empty")
1472 end
1473
1474 return m