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