2 LuCI - Lua Configuration Interface
4 A lot of code taken from original ddns.lua cbi-model made by
5 Copyright 2008 Steven Barth <steven@midlink.org>
6 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
7 Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
9 modified to use as detail page together with new overview page and
10 extensions for IPv6, HTTPS settings, syslog and log settings,
11 optional Proxy-Support, optional DNS-Server, optional use of TCP requests to DNS server,
12 optional force of IP protocol version usage
13 Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
15 Licensed under the Apache License, Version 2.0 (the "License");
16 you may not use this file except in compliance with the License.
17 You may obtain a copy of the License at
19 http://www.apache.org/licenses/LICENSE-2.0
24 require "luci.dispatcher"
27 require "luci.tools.webadmin"
28 require "luci.cbi.datatypes"
29 require "luci.tools.ddns" -- ddns multiused functions
34 -- check supported options
35 -- saved to local vars here because doing multiple os calls slow down the system
36 has_ipv6 = luci.tools.ddns.check_ipv6() -- IPv6 support
37 has_ssl = luci.tools.ddns.check_ssl() -- HTTPS support
38 has_proxy = luci.tools.ddns.check_proxy() -- Proxy support
39 has_dnstcp = luci.tools.ddns.check_bind_host() -- DNS TCP support
40 has_force = has_ssl and has_dnstcp -- Force IP Protocoll
43 font_red = "<font color='red'>"
46 bold_off = "</strong>"
48 -- error text constants
49 err_ipv6_plain = translate("IPv6 not supported") .. " - " ..
50 translate("please select 'IPv4' address version")
51 err_ipv6_basic = bold_on ..
53 translate("IPv6 not supported") ..
55 "<br />" .. translate("please select 'IPv4' address version") ..
57 err_ipv6_other = bold_on ..
59 translate("IPv6 not supported") ..
61 "<br />" .. translate("please select 'IPv4' address version in") .. " " ..
63 luci.dispatcher.build_url("admin", "services", "ddns", "detail", section) ..
64 "?tab.dns." .. section .. "=basic" ..
66 translate("Basic Settings") ..
70 function err_tab_basic(self)
71 return translate("Basic Settings") .. " - " .. self.title .. ": "
73 function err_tab_adv(self)
74 return translate("Advanced Settings") .. " - " .. self.title .. ": "
76 function err_tab_timer(self)
77 return translate("Timer Settings") .. " - " .. self.title .. ": "
80 -- function to verify settings around ip_source
81 -- will use dynamic_dns_lucihelper to check if
82 -- local IP can be read
83 local function _verify_ip_source()
84 -- section is globally defined here be calling agrument (see above)
87 local _interface = "-"
91 local _ipv6 = usev6:formvalue(section)
92 local _source = (_ipv6 == "1")
93 and src6:formvalue(section)
94 or src4:formvalue(section)
95 if _source == "network" then
96 _network = (_ipv6 == "1")
97 and ipn6:formvalue(section)
98 or ipn4:formvalue(section)
99 elseif _source == "web" then
100 _url = (_ipv6 == "1")
101 and iurl6:formvalue(section)
102 or iurl4:formvalue(section)
103 -- proxy only needed for checking url
104 _proxy = (pxy) and pxy:formvalue(section) or ""
105 elseif _source == "interface" then
106 _interface = ipi:formvalue(section)
107 elseif _source == "script" then
108 _script = ips:formvalue(section)
111 local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh get_local_ip ]] ..
112 _ipv6 .. [[ ]] .. _source .. [[ ]] .. _network .. [[ ]] ..
113 _url .. [[ ]] .. _interface .. [[ ]] .. _script.. [[ ]] .. _proxy
114 local ret = luci.sys.call(command)
119 return nil -- invalid
123 -- cbi-map definition
126 m.title = [[<a href="]] .. luci.dispatcher.build_url("admin", "services", "ddns") .. [[">]] ..
127 translate("Dynamic DNS") .. [[</a>]]
129 m.description = translate("Dynamic DNS allows that your router can be reached with " ..
130 "a fixed hostname while having a dynamically changing " ..
133 m.redirect = luci.dispatcher.build_url("admin", "services", "ddns")
135 -- read application settings
136 -- date format; if not set use ISO format
137 date_format = m.uci:get(m.config, "global", "date_format") or "%F %R"
139 log_dir = m.uci:get(m.config, "global", "log_dir") or "/var/log/ddns"
141 -- cbi-section definition
142 ns = m:section( NamedSection, section, "service",
143 translate("Details for") .. ([[: <strong>%s</strong>]] % section),
144 translate("Configure here the details for selected Dynamic DNS service") )
145 ns.instance = section -- arg [1]
146 ns:tab("basic", translate("Basic Settings"), nil )
147 ns:tab("advanced", translate("Advanced Settings"), nil )
148 ns:tab("timer", translate("Timer Settings"), nil )
149 ns:tab("logview", translate("Log File Viewer"), nil )
151 -- TAB: Basic ##################################################################
153 en = ns:taboption("basic", Flag, "enabled",
154 translate("Enabled"),
155 translate("If this service section is disabled it could not be started." .. "<br />" ..
156 "Neither from LuCI interface nor from console") )
157 en.orientation = "horizontal"
160 usev6 = ns:taboption("basic", ListValue, "use_ipv6",
161 translate("IP address version"),
162 translate("Defines which IP address 'IPv4/IPv6' is send to the DDNS provider") )
163 usev6.widget = "radio"
165 usev6:value("0", translate("IPv4-Address") )
166 function usev6.cfgvalue(self, section)
167 local value = AbstractValue.cfgvalue(self, section)
168 if has_ipv6 or (value == "1" and not has_ipv6) then
169 self:value("1", translate("IPv6-Address") )
171 if value == "1" and not has_ipv6 then
172 self.description = err_ipv6_basic
176 function usev6.validate(self, value)
177 if (value == "1" and has_ipv6) or value == "0" then
180 return nil, err_tab_basic(self) .. err_ipv6_plain
182 function usev6.write(self, section, value)
183 if value == "0" then -- force rmempty
184 return self.map:del(section, self.option)
186 return self.map:set(section, self.option, value)
190 -- IPv4 - service_name
191 svc4 = ns:taboption("basic", ListValue, "ipv4_service_name",
192 translate("DDNS Service provider") .. " [IPv4]" )
194 svc4:depends("use_ipv6", "0") -- only show on IPv4
196 local services4 = { }
197 local fd4 = io.open("/usr/lib/ddns/services", "r")
203 local s = ln and ln:match('^%s*"([^"]+)"')
204 if s then services4[#services4+1] = s end
209 for _, v in luci.util.vspairs(services4) do svc4:value(v) end
210 svc4:value("-", translate("-- custom --") )
212 function svc4.cfgvalue(self, section)
213 local v = luci.tools.ddns.read_value(self, section, "service_name")
214 if not v or #v == 0 then
220 function svc4.validate(self, value)
221 if usev6:formvalue(section) == "0" then -- do only on IPv4
224 return "" -- supress validate error
227 function svc4.write(self, section, value)
228 if usev6:formvalue(section) == "0" then -- do only IPv4 here
229 self.map:del(section, self.option) -- to be shure
230 if value ~= "-" then -- and write "service_name
231 self.map:del(section, "update_url") -- delete update_url
232 return self.map:set(section, "service_name", value)
234 return self.map:del(section, "service_name")
239 -- IPv6 - service_name
240 svc6 = ns:taboption("basic", ListValue, "ipv6_service_name",
241 translate("DDNS Service provider") .. " [IPv6]" )
243 svc6:depends("use_ipv6", "1") -- only show on IPv6
245 svc6.description = err_ipv6_basic
248 local services6 = { }
249 local fd6 = io.open("/usr/lib/ddns/services_ipv6", "r")
255 local s = ln and ln:match('^%s*"([^"]+)"')
256 if s then services6[#services6+1] = s end
261 for _, v in luci.util.vspairs(services6) do svc6:value(v) end
262 svc6:value("-", translate("-- custom --") )
264 function svc6.cfgvalue(self, section)
265 local v = luci.tools.ddns.read_value(self, section, "service_name")
266 if not v or #v == 0 then
272 function svc6.validate(self, value)
273 if usev6:formvalue(section) == "1" then -- do only on IPv6
274 if has_ipv6 then return value end
275 return nil, err_tab_basic(self) .. err_ipv6_plain
277 return "" -- supress validate error
280 function svc6.write(self, section, value)
281 if usev6:formvalue(section) == "1" then -- do only when IPv6
282 self.map:del(section, self.option) -- delete "ipv6_service_name" helper
283 if value ~= "-" then -- and write "service_name
284 self.map:del(section, "update_url") -- delete update_url
285 return self.map:set(section, "service_name", value)
287 return self.map:del(section, "service_name")
292 -- IPv4/IPv6 - update_url
293 uurl = ns:taboption("basic", Value, "update_url",
294 translate("Custom update-URL"),
295 translate("Update URL to be used for updating your DDNS Provider." .. "<br />" ..
296 "Follow instructions you will find on their WEB page.") )
297 uurl:depends("ipv4_service_name", "-")
298 uurl:depends("ipv6_service_name", "-")
299 function uurl.validate(self, value)
300 local script = ush:formvalue(section)
302 if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
303 (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
304 return "" -- suppress validate error
305 elseif not value then
306 if not script or not (#script > 0) then
307 return nil, err_tab_basic(self) .. translate("missing / required")
309 return "" -- suppress validate error / update_script is given
311 elseif (#script > 0) then
312 return nil, err_tab_basic(self) .. translate("either url or script could be set")
315 local url = luci.tools.ddns.parse_url(value)
316 if not url.scheme == "http" then
317 return nil, err_tab_basic(self) .. translate("must start with 'http://'")
318 elseif not url.query then
319 return nil, err_tab_basic(self) .. "<QUERY> " .. translate("missing / required")
320 elseif not url.host then
321 return nil, err_tab_basic(self) .. "<HOST> " .. translate("missing / required")
322 elseif luci.sys.call([[nslookup ]] .. url.host .. [[ >/dev/null 2>&1]]) ~= 0 then
323 return nil, err_tab_basic(self) .. translate("can not resolve host: ") .. url.host
329 -- IPv4/IPv6 - update_script
330 ush = ns:taboption("basic", Value, "update_script",
331 translate("Custom update-script"),
332 translate("Custom update script to be used for updating your DDNS Provider.") )
333 ush:depends("ipv4_service_name", "-")
334 ush:depends("ipv6_service_name", "-")
335 function ush.validate(self, value)
336 local url = uurl:formvalue(section)
338 if (usev6:formvalue(section) == "0" and svc4:formvalue(section) ~= "-") or
339 (usev6:formvalue(section) == "1" and svc6:formvalue(section) ~= "-") then
340 return "" -- suppress validate error
341 elseif not value then
342 if not url or not (#url > 0) then
343 return nil, err_tab_basic(self) .. translate("missing / required")
345 return "" -- suppress validate error / update_url is given
347 elseif (#url > 0) then
348 return nil, err_tab_basic(self) .. translate("either url or script could be set")
349 elseif not nixio.fs.access(value) then
350 return nil, err_tab_basic(self) .. translate("File not found")
355 -- IPv4/IPv6 - domain
356 dom = ns:taboption("basic", Value, "domain",
357 translate("Hostname/Domain"),
358 translate("Replaces [DOMAIN] in Update-URL") )
360 dom.placeholder = "mypersonaldomain.dyndns.org"
361 function dom.validate(self, value)
364 or not luci.cbi.datatypes.hostname(value) then
365 return nil, err_tab_basic(self) .. translate("invalid - Sample") .. ": 'mypersonaldomain.dyndns.org'"
371 -- IPv4/IPv6 - username
372 user = ns:taboption("basic", Value, "username",
373 translate("Username"),
374 translate("Replaces [USERNAME] in Update-URL") )
376 function user.validate(self, value)
378 return nil, err_tab_basic(self) .. translate("missing / required")
383 -- IPv4/IPv6 - password
384 pw = ns:taboption("basic", Value, "password",
385 translate("Password"),
386 translate("Replaces [PASSWORD] in Update-URL") )
389 function pw.validate(self, value)
391 return nil, err_tab_basic(self) .. translate("missing / required")
396 -- IPv4/IPv6 - use_https (NEW)
397 if has_ssl or ( ( m:get(section, "use_https") or "0" ) == "1" ) then
398 https = ns:taboption("basic", Flag, "use_https",
399 translate("Use HTTP Secure") )
400 https.orientation = "horizontal"
401 https.rmempty = false -- force validate function
402 function https.cfgvalue(self, section)
403 local value = AbstractValue.cfgvalue(self, section)
404 if not has_ssl and value == "1" then
405 self.description = bold_on .. font_red ..
406 translate("HTTPS not supported") .. font_off .. "<br />" ..
407 translate("please disable") .. " !" .. bold_off
409 self.description = translate("Enable secure communication with DDNS provider")
413 function https.validate(self, value)
414 if (value == "1" and has_ssl ) or value == "0" then return value end
415 return nil, err_tab_basic(self) .. translate("HTTPS not supported") .. " !"
417 function https.write(self, section, value)
419 return self.map:set(section, self.option, value)
421 self.map:del(section, "cacert")
422 return self.map:del(section, self.option)
427 -- IPv4/IPv6 - cacert (NEW)
429 cert = ns:taboption("basic", Value, "cacert",
430 translate("Path to CA-Certificate"),
431 translate("directory or path/file") .. "<br />" ..
432 translate("or") .. bold_on .. " IGNORE " .. bold_off ..
433 translate("to run HTTPS without verification of server certificates (insecure)") )
434 cert:depends("use_https", "1")
435 cert.rmempty = false -- force validate function
436 cert.default = "/etc/ssl/certs"
437 function cert.validate(self, value)
438 if https:formvalue(section) == "0" then
439 return "" -- supress validate error if NOT https
441 if value then -- otherwise errors in datatype check
442 if luci.cbi.datatypes.directory(value)
443 or luci.cbi.datatypes.file(value)
444 or value == "IGNORE" then
448 return nil, err_tab_basic(self) ..
449 translate("file or directory not found or not 'IGNORE'") .. " !"
454 slog = ns:taboption("basic", ListValue, "use_syslog",
455 translate("Log to syslog"),
456 translate("Writes log messages to syslog. Critical Errors will always be written to syslog.") )
458 slog:value("0", translate("No logging"))
459 slog:value("1", translate("Info"))
460 slog:value("2", translate("Notice"))
461 slog:value("3", translate("Warning"))
462 slog:value("4", translate("Error"))
465 logf = ns:taboption("basic", Flag, "use_logfile",
466 translate("Log to file"),
467 translate("Writes detailed messages to log file. File will be truncated automatically.") .. "<br />" ..
468 translate("File") .. [[: "]] .. log_dir .. [[/]] .. section .. [[.log"]] )
469 logf.orientation = "horizontal"
470 logf.rmempty = false -- we want to save in /etc/config/ddns file on "0" because
471 logf.default = "1" -- if not defined write to log by default
473 -- TAB: Advanced ##############################################################
475 src4 = ns:taboption("advanced", ListValue, "ipv4_source",
476 translate("IP address source") .. " [IPv4]",
477 translate("Defines the source to read systems IPv4-Address from, that will be send to the DDNS provider") )
478 src4:depends("use_ipv6", "0") -- IPv4 selected
479 src4.default = "network"
480 src4:value("network", translate("Network"))
481 src4:value("web", translate("URL"))
482 src4:value("interface", translate("Interface"))
483 src4:value("script", translate("Script"))
484 function src4.cfgvalue(self, section)
485 return luci.tools.ddns.read_value(self, section, "ip_source")
487 function src4.validate(self, value)
488 if usev6:formvalue(section) == "1" then
489 return "" -- ignore on IPv6 selected
490 elseif not _verify_ip_source() then
491 return nil, err_tab_adv(self) ..
492 translate("can not detect local IP. Please select a different Source combination")
497 function src4.write(self, section, value)
498 if usev6:formvalue(section) == "1" then
499 return true -- ignore on IPv6 selected
500 elseif value == "network" then
501 self.map:del(section, "ip_url") -- delete not need parameters
502 self.map:del(section, "ip_interface")
503 self.map:del(section, "ip_script")
504 elseif value == "web" then
505 self.map:del(section, "ip_network") -- delete not need parameters
506 self.map:del(section, "ip_interface")
507 self.map:del(section, "ip_script")
508 elseif value == "interface" then
509 self.map:del(section, "ip_network") -- delete not need parameters
510 self.map:del(section, "ip_url")
511 self.map:del(section, "ip_script")
512 elseif value == "script" then
513 self.map:del(section, "ip_network")
514 self.map:del(section, "ip_url") -- delete not need parameters
515 self.map:del(section, "ip_interface")
517 self.map:del(section, self.option) -- delete "ipv4_source" helper
518 return self.map:set(section, "ip_source", value) -- and write "ip_source
522 src6 = ns:taboption("advanced", ListValue, "ipv6_source",
523 translate("IP address source") .. " [IPv6]",
524 translate("Defines the source to read systems IPv6-Address from, that will be send to the DDNS provider") )
525 src6:depends("use_ipv6", 1) -- IPv6 selected
526 src6.default = "network"
527 src6:value("network", translate("Network"))
528 src6:value("web", translate("URL"))
529 src6:value("interface", translate("Interface"))
530 src6:value("script", translate("Script"))
532 src6.description = err_ipv6_other
534 function src6.cfgvalue(self, section)
535 return luci.tools.ddns.read_value(self, section, "ip_source")
537 function src6.validate(self, value)
538 if usev6:formvalue(section) == "0" then
539 return "" -- ignore on IPv4 selected
540 elseif not has_ipv6 then
541 return nil, err_tab_adv(self) .. err_ipv6_plain
542 elseif not _verify_ip_source() then
543 return nil, err_tab_adv(self) ..
544 translate("can not detect local IP. Please select a different Source combination")
549 function src6.write(self, section, value)
550 if usev6:formvalue(section) == "0" then
551 return true -- ignore on IPv4 selected
552 elseif value == "network" then
553 self.map:del(section, "ip_url") -- delete not need parameters
554 self.map:del(section, "ip_interface")
555 self.map:del(section, "ip_script")
556 elseif value == "web" then
557 self.map:del(section, "ip_network") -- delete not need parameters
558 self.map:del(section, "ip_interface")
559 self.map:del(section, "ip_script")
560 elseif value == "interface" then
561 self.map:del(section, "ip_network") -- delete not need parameters
562 self.map:del(section, "ip_url")
563 self.map:del(section, "ip_script")
564 elseif value == "script" then
565 self.map:del(section, "ip_network")
566 self.map:del(section, "ip_url") -- delete not need parameters
567 self.map:del(section, "ip_interface")
569 self.map:del(section, self.option) -- delete "ipv4_source" helper
570 return self.map:set(section, "ip_source", value) -- and write "ip_source
573 -- IPv4 - ip_network (default "wan")
574 ipn4 = ns:taboption("advanced", ListValue, "ipv4_network",
575 translate("Network") .. " [IPv4]",
576 translate("Defines the network to read systems IPv4-Address from") )
577 ipn4:depends("ipv4_source", "network")
579 luci.tools.webadmin.cbi_add_networks(ipn4)
580 function ipn4.cfgvalue(self, section)
581 return luci.tools.ddns.read_value(self, section, "ip_network")
583 function ipn4.validate(self, value)
584 if usev6:formvalue(section) == "1"
585 or src4:formvalue(section) ~= "network" then
586 -- ignore if IPv6 selected OR
587 -- ignore everything except "network"
593 function ipn4.write(self, section, value)
594 if usev6:formvalue(section) == "1"
595 or src4:formvalue(section) ~= "network" then
596 -- ignore if IPv6 selected OR
597 -- ignore everything except "network"
600 -- set also as "interface" for monitoring events changes/hot-plug
601 self.map:set(section, "interface", value)
602 self.map:del(section, self.option) -- delete "ipv4_network" helper
603 return self.map:set(section, "ip_network", value) -- and write "ip_network"
607 -- IPv6 - ip_network (default "wan6")
608 ipn6 = ns:taboption("advanced", ListValue, "ipv6_network",
609 translate("Network") .. " [IPv6]" )
610 ipn6:depends("ipv6_source", "network")
611 ipn6.default = "wan6"
612 luci.tools.webadmin.cbi_add_networks(ipn6)
614 ipn6.description = translate("Defines the network to read systems IPv6-Address from")
616 ipn6.description = err_ipv6_other
618 function ipn6.cfgvalue(self, section)
619 return luci.tools.ddns.read_value(self, section, "ip_network")
621 function ipn6.validate(self, value)
622 if usev6:formvalue(section) == "0"
623 or src6:formvalue(section) ~= "network" then
624 -- ignore if IPv4 selected OR
625 -- ignore everything except "network"
630 return nil, err_tab_adv(self) .. err_ipv6_plain
633 function ipn6.write(self, section, value)
634 if usev6:formvalue(section) == "0"
635 or src6:formvalue(section) ~= "network" then
636 -- ignore if IPv4 selected OR
637 -- ignore everything except "network"
640 -- set also as "interface" for monitoring events changes/hotplug
641 self.map:set(section, "interface", value)
642 self.map:del(section, self.option) -- delete "ipv6_network" helper
643 return self.map:set(section, "ip_network", value) -- and write "ip_network"
647 -- IPv4 - ip_url (default "checkip.dyndns.com")
648 iurl4 = ns:taboption("advanced", Value, "ipv4_url",
649 translate("URL to detect") .. " [IPv4]",
650 translate("Defines the Web page to read systems IPv4-Address from") )
651 iurl4:depends("ipv4_source", "web")
652 iurl4.default = "http://checkip.dyndns.com"
653 function iurl4.cfgvalue(self, section)
654 return luci.tools.ddns.read_value(self, section, "ip_url")
656 function iurl4.validate(self, value)
657 if usev6:formvalue(section) == "1"
658 or src4:formvalue(section) ~= "web" then
659 -- ignore if IPv6 selected OR
660 -- ignore everything except "web"
662 elseif not value or #value == 0 then
663 return nil, err_tab_adv(self) .. translate("missing / required")
666 local url = luci.tools.ddns.parse_url(value)
667 if not (url.scheme == "http" or url.scheme == "https") then
668 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
669 elseif not url.host then
670 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
671 elseif luci.sys.call([[nslookup ]] ..
673 [[>/dev/null 2>&1]]) ~= 0 then
674 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
679 function iurl4.write(self, section, value)
680 if usev6:formvalue(section) == "1"
681 or src4:formvalue(section) ~= "web" then
682 -- ignore if IPv6 selected OR
683 -- ignore everything except "web"
686 self.map:del(section, self.option) -- delete "ipv4_url" helper
687 return self.map:set(section, "ip_url", value) -- and write "ip_url"
691 -- IPv6 - ip_url (default "checkipv6.dyndns.com")
692 iurl6 = ns:taboption("advanced", Value, "ipv6_url",
693 translate("URL to detect") .. " [IPv6]" )
694 iurl6:depends("ipv6_source", "web")
695 iurl6.default = "http://checkipv6.dyndns.com"
697 iurl6.description = translate("Defines the Web page to read systems IPv6-Address from")
699 iurl6.description = err_ipv6_other
701 function iurl6.cfgvalue(self, section)
702 return luci.tools.ddns.read_value(self, section, "ip_url")
704 function iurl6.validate(self, value)
705 if usev6:formvalue(section) == "0"
706 or src6:formvalue(section) ~= "web" then
707 -- ignore if IPv4 selected OR
708 -- ignore everything except "web"
710 elseif not has_ipv6 then
711 return nil, err_tab_adv(self) .. err_ipv6_plain
712 elseif not value or #value == 0 then
713 return nil, err_tab_adv(self) .. translate("missing / required")
716 local url = luci.tools.ddns.parse_url(value)
717 if not (url.scheme == "http" or url.scheme == "https") then
718 return nil, err_tab_adv(self) .. translate("must start with 'http://'")
719 elseif not url.host then
720 return nil, err_tab_adv(self) .. "<HOST> " .. translate("missing / required")
721 elseif luci.sys.call([[nslookup ]] ..
723 [[>/dev/null 2>&1]]) ~= 0 then
724 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
729 function iurl6.write(self, section, value)
730 if usev6:formvalue(section) == "0"
731 or src6:formvalue(section) ~= "web" then
732 -- ignore if IPv4 selected OR
733 -- ignore everything except "web"
736 self.map:del(section, self.option) -- delete "ipv6_url" helper
737 return self.map:set(section, "ip_url", value) -- and write "ip_url"
741 -- IPv4 + IPv6 - ip_interface
742 ipi = ns:taboption("advanced", ListValue, "ip_interface",
743 translate("Interface"),
744 translate("Defines the interface to read systems IP-Address from") )
745 ipi:depends("ipv4_source", "interface") -- IPv4
746 ipi:depends("ipv6_source", "interface") -- or IPv6
747 for _, v in pairs(luci.sys.net.devices()) do
748 -- show only interface set to a network
749 -- and ignore loopback
750 net = luci.tools.webadmin.iface_get_network(v)
751 if net and net ~= "loopback" then
755 function ipi.validate(self, value)
756 if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
757 or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
763 function ipi.write(self, section, value)
764 if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "interface")
765 or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "interface") then
768 -- get network from device to
769 -- set also as "interface" for monitoring events changes/hotplug
770 local net = luci.tools.webadmin.iface_get_network(value)
771 self.map:set(section, "interface", net)
772 return self.map:set(section, self.option, value)
776 -- IPv4 + IPv6 - ip_script (NEW)
777 ips = ns:taboption("advanced", Value, "ip_script",
779 translate("User defined script to read systems IP-Address") )
780 ips:depends("ipv4_source", "script") -- IPv4
781 ips:depends("ipv6_source", "script") -- or IPv6
782 ips.placeholder = "/path/to/script.sh"
783 function ips.validate(self, value)
784 if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
785 or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
787 elseif not value or not nixio.fs.access(value, "x") then
788 return nil, err_tab_adv(self) ..
789 translate("not found or not executable - Sample: '/path/to/script.sh'")
794 function ips.write(self, section, value)
795 if (usev6:formvalue(section) == "0" and src4:formvalue(section) ~= "script")
796 or (usev6:formvalue(section) == "1" and src6:formvalue(section) ~= "script") then
799 return self.map:set(section, self.option, value)
803 -- IPv4 - interface - default "wan"
804 -- event network to monitor changes/hotplug/dynamic_dns_updater.sh
805 -- only needs to be set if "ip_source"="web" or "script"
806 -- if "ip_source"="network" or "interface" we use their network
807 eif4 = ns:taboption("advanced", ListValue, "ipv4_interface",
808 translate("Event Network") .. " [IPv4]",
809 translate("Network on which the ddns-updater scripts will be started") )
810 eif4:depends("ipv4_source", "web")
811 eif4:depends("ipv4_source", "script")
813 luci.tools.webadmin.cbi_add_networks(eif4)
814 function eif4.cfgvalue(self, section)
815 return luci.tools.ddns.read_value(self, section, "interface")
817 function eif4.validate(self, value)
818 if usev6:formvalue(section) == "1"
819 or src4:formvalue(section) == "network"
820 or src4:formvalue(section) == "interface" then
821 return "" -- ignore IPv6, network, interface
826 function eif4.write(self, section, value)
827 if usev6:formvalue(section) == "1"
828 or src4:formvalue(section) == "network"
829 or src4:formvalue(section) == "interface" then
830 return true -- ignore IPv6, network, interface
832 self.map:del(section, self.option) -- delete "ipv4_interface" helper
833 return self.map:set(section, "interface", value) -- and write "interface"
837 -- IPv6 - interface (NEW) - default "wan6"
838 -- event network to monitor changes/hotplug (NEW)
839 -- only needs to be set if "ip_source"="web" or "script"
840 -- if "ip_source"="network" or "interface" we use their network
841 eif6 = ns:taboption("advanced", ListValue, "ipv6_interface",
842 translate("Event Network") .. " [IPv6]" )
843 eif6:depends("ipv6_source", "web")
844 eif6:depends("ipv6_source", "script")
845 eif6.default = "wan6"
846 luci.tools.webadmin.cbi_add_networks(eif6)
848 eif6.description = err_ipv6_other
850 eif6.description = translate("Network on which the ddns-updater scripts will be started")
852 function eif6.cfgvalue(self, section)
853 return luci.tools.ddns.read_value(self, section, "interface")
855 function eif6.validate(self, value)
856 if usev6:formvalue(section) == "0"
857 or src4:formvalue(section) == "network"
858 or src4:formvalue(section) == "interface" then
859 return "" -- ignore IPv4, network, interface
860 elseif not has_ipv6 then
861 return nil, err_tab_adv(self) .. err_ipv6_plain
866 function eif6.write(self, section, value)
867 if usev6:formvalue(section) == "0"
868 or src4:formvalue(section) == "network"
869 or src4:formvalue(section) == "interface" then
870 return true -- ignore IPv4, network, interface
872 self.map:del(section, self.option) -- delete "ipv6_interface" helper
873 return self.map:set(section, "interface", value) -- and write "interface"
877 -- IPv4 + IPv6 - force_ipversion (NEW)
878 -- optional to force wget/curl and host to use only selected IP version
879 -- command parameter "-4" or "-6"
880 if has_force or ( ( m:get(section, "force_ipversion") or "0" ) ~= "0" ) then
881 fipv = ns:taboption("advanced", Flag, "force_ipversion",
882 translate("Force IP Version") )
883 fipv.orientation = "horizontal"
884 function fipv.cfgvalue(self, section)
885 local value = AbstractValue.cfgvalue(self, section)
886 if not has_force and value ~= "0" then
887 self.description = bold_on .. font_red ..
888 translate("Force IP Version not supported") .. font_off .. "<br />" ..
889 translate("please disable") .. " !" .. bold_off
891 self.description = translate("OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.")
895 function fipv.validate(self, value)
896 if (value == "1" and has_force) or value == "0" then return value end
897 return nil, err_tab_adv(self) .. translate("Force IP Version not supported")
899 function fipv.write(self, section, value)
901 return self.map:set(section, self.option, value)
903 return self.map:del(section, self.option)
908 -- IPv4 + IPv6 - dns_server (NEW)
909 -- optional DNS Server to use resolving my IP if "ip_source"="web"
910 dns = ns:taboption("advanced", Value, "dns_server",
911 translate("DNS-Server"),
912 translate("OPTIONAL: Use non-default DNS-Server to detect 'Registered IP'.") .. "<br />" ..
913 translate("Format: IP or FQDN"))
914 dns.placeholder = "mydns.lan"
915 function dns.validate(self, value)
916 -- if .datatype is set, then it is checked before calling this function
918 return "" -- ignore on empty
919 elseif not luci.cbi.datatypes.hostname(value) then
920 return nil, err .. translate("use hostname, FQDN, IPv4- or IPv6-Address")
922 local ipv6 = usev6:formvalue(section)
923 local force = (fipv) and fipv:formvalue(section) or "0"
924 local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_dns ]] ..
925 value .. [[ ]] .. ipv6 .. [[ ]] .. force
926 local ret = luci.sys.call(command)
927 if ret == 0 then return value -- everything OK
928 elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
929 elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
930 elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
931 else return nil, err_tab_adv(self) .. translate("unspecific error")
936 -- IPv4 + IPv6 - force_dnstcp (NEW)
937 if has_dnstcp or ( ( m:get(section, "force_dnstcp") or "0" ) ~= "0" ) then
938 tcp = ns:taboption("advanced", Flag, "force_dnstcp",
939 translate("Force TCP on DNS") )
940 tcp.orientation = "horizontal"
941 function tcp.cfgvalue(self, section)
942 local value = AbstractValue.cfgvalue(self, section)
943 if not has_dnstcp and value ~= "0" then
944 self.description = bold_on .. font_red ..
945 translate("DNS requests via TCP not supported") .. font_off .. "<br />" ..
946 translate("please disable") .. " !" .. bold_off
948 self.description = translate("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests.")
952 function tcp.validate(self, value)
953 if (value == "1" and has_dnstcp ) or value == "0" then
956 return nil, err_tab_adv(self) .. translate("DNS requests via TCP not supported")
960 -- IPv4 + IPv6 - proxy (NEW)
961 -- optional Proxy to use for http/https requests [user:password@]proxyhost[:port]
962 if has_proxy or ( ( m:get(section, "proxy") or "" ) ~= "" ) then
963 pxy = ns:taboption("advanced", Value, "proxy",
964 translate("PROXY-Server") )
965 pxy.placeholder="user:password@myproxy.lan:8080"
966 function pxy.cfgvalue(self, section)
967 local value = AbstractValue.cfgvalue(self, section)
968 if not has_proxy and value ~= "" then
969 self.description = bold_on .. font_red ..
970 translate("PROXY-Server not supported") .. font_off .. "<br />" ..
971 translate("please remove entry") .. "!" .. bold_off
973 self.description = translate("OPTIONAL: Proxy-Server for detection and updates.") .. "<br />" ..
974 translate("Format") .. ": " .. bold_on .. "[user:password@]proxyhost:port" .. bold_off .. "<br />" ..
975 translate("IPv6 address must be given in square brackets") .. ": " ..
976 bold_on .. " [2001:db8::1]:8080" .. bold_off
980 function pxy.validate(self, value)
981 -- if .datatype is set, then it is checked before calling this function
983 return "" -- ignore on empty
984 elseif has_proxy then
985 local ipv6 = usev6:formvalue(section) or "0"
986 local force = (fipv) and fipv:formvalue(section) or "0"
987 local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh verify_proxy ]] ..
988 value .. [[ ]] .. ipv6 .. [[ ]] .. force
989 local ret = luci.sys.call(command)
990 if ret == 0 then return value
991 elseif ret == 2 then return nil, err_tab_adv(self) .. translate("nslookup can not resolve host")
992 elseif ret == 3 then return nil, err_tab_adv(self) .. translate("nc (netcat) can not connect")
993 elseif ret == 4 then return nil, err_tab_adv(self) .. translate("Forced IP Version don't matched")
994 elseif ret == 5 then return nil, err_tab_adv(self) .. translate("proxy port missing")
995 else return nil, err_tab_adv(self) .. translate("unspecific error")
998 return nil, err .. translate("PROXY-Server not supported")
1003 -- TAB: Timer #################################################################
1005 ci = ns:taboption("timer", Value, "check_interval",
1006 translate("Check Interval") )
1007 ci.template = "ddns/detail_value"
1009 ci.rmempty = false -- validate ourselves for translatable error messages
1010 function ci.validate(self, value)
1011 if not luci.cbi.datatypes.uinteger(value)
1012 or tonumber(value) < 1 then
1013 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1016 local secs = luci.tools.ddns.calc_seconds(value, cu:formvalue(section))
1020 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1023 function ci.write(self, section, value)
1024 -- simulate rmempty=true remove default
1025 local secs = luci.tools.ddns.calc_seconds(value, cu:formvalue(section))
1026 if secs ~= 600 then --default 10 minutes
1027 return self.map:set(section, self.option, value)
1029 self.map:del(section, "check_unit")
1030 return self.map:del(section, self.option)
1035 cu = ns:taboption("timer", ListValue, "check_unit", "not displayed, but needed otherwise error",
1036 translate("Interval to check for changed IP" .. "<br />" ..
1037 "Values below 5 minutes == 300 seconds are not supported") )
1038 cu.template = "ddns/detail_lvalue"
1039 cu.default = "minutes"
1040 cu.rmempty = false -- want to control write process
1041 cu:value("seconds", translate("seconds"))
1042 cu:value("minutes", translate("minutes"))
1043 cu:value("hours", translate("hours"))
1044 --cu:value("days", translate("days"))
1045 function cu.write(self, section, value)
1046 -- simulate rmempty=true remove default
1047 local secs = luci.tools.ddns.calc_seconds(ci:formvalue(section), value)
1048 if secs ~= 600 then --default 10 minutes
1049 return self.map:set(section, self.option, value)
1055 -- force_interval (modified)
1056 fi = ns:taboption("timer", Value, "force_interval",
1057 translate("Force Interval") )
1058 fi.template = "ddns/detail_value"
1059 fi.default = 72 -- see dynamic_dns_updater.sh script
1060 fi.rmempty = false -- validate ourselves for translatable error messages
1061 function fi.validate(self, value)
1062 if not luci.cbi.datatypes.uinteger(value)
1063 or tonumber(value) < 0 then
1064 return nil, err_tab_timer(self) .. translate("minimum value '0'")
1067 local force_s = luci.tools.ddns.calc_seconds(value, fu:formvalue(section))
1068 if force_s == 0 then
1072 local ci_value = ci:formvalue(section)
1073 if not luci.cbi.datatypes.uinteger(ci_value) then
1074 return "" -- ignore because error in check_interval above
1077 local check_s = luci.tools.ddns.calc_seconds(ci_value, cu:formvalue(section))
1078 if force_s >= check_s then
1082 return nil, err_tab_timer(self) .. translate("must be greater or equal 'Check Interval'")
1084 function fi.write(self, section, value)
1085 -- simulate rmempty=true remove default
1086 local secs = luci.tools.ddns.calc_seconds(value, fu:formvalue(section))
1087 if secs ~= 259200 then --default 72 hours == 3 days
1088 return self.map:set(section, self.option, value)
1090 self.map:del(section, "force_unit")
1091 return self.map:del(section, self.option)
1096 fu = ns:taboption("timer", ListValue, "force_unit", "not displayed, but needed otherwise error",
1097 translate("Interval to force updates send to DDNS Provider" .. "<br />" ..
1098 "Setting this parameter to 0 will force the script to only run once" .. "<br />" ..
1099 "Values lower 'Check Interval' except '0' are not supported") )
1100 fu.template = "ddns/detail_lvalue"
1101 fu.default = "hours"
1102 fu.rmempty = false -- want to control write process
1103 --fu:value("seconds", translate("seconds"))
1104 fu:value("minutes", translate("minutes"))
1105 fu:value("hours", translate("hours"))
1106 fu:value("days", translate("days"))
1107 function fu.write(self, section, value)
1108 -- simulate rmempty=true remove default
1109 local secs = luci.tools.ddns.calc_seconds(fi:formvalue(section), value)
1110 if secs ~= 259200 and secs ~= 0 then --default 72 hours == 3 days
1111 return self.map:set(section, self.option, value)
1117 -- retry_count (NEW)
1118 rc = ns:taboption("timer", Value, "retry_count",
1119 translate("Error Retry Counter"),
1120 translate("On Error the script will stop execution after given number of retrys") )
1122 rc.rmempty = false -- validate ourselves for translatable error messages
1123 function rc.validate(self, value)
1124 if not luci.cbi.datatypes.uinteger(value)
1125 or tonumber(value) < 1 then
1126 return nil, err_tab_timer(self) .. translate("minimum value '1'")
1131 function rc.write(self, section, value)
1132 -- simulate rmempty=true remove default
1133 if tonumber(value) ~= self.default then
1134 return self.map:set(section, self.option, value)
1136 return self.map:del(section, self.option)
1141 ri = ns:taboption("timer", Value, "retry_interval",
1142 translate("Error Retry Interval") )
1143 ri.template = "ddns/detail_value"
1145 ri.rmempty = false -- validate ourselves for translatable error messages
1146 function ri.validate(self, value)
1147 if not luci.cbi.datatypes.uinteger(value)
1148 or tonumber(value) < 1 then
1149 return nil, err_tab_timer(self) .. translate("minimum value '1'")
1154 function ri.write(self, section, value)
1155 -- simulate rmempty=true remove default
1156 local secs = luci.tools.ddns.calc_seconds(value, ru:formvalue(section))
1157 if secs ~= 60 then --default 60seconds
1158 return self.map:set(section, self.option, value)
1160 self.map:del(section, "retry_unit")
1161 return self.map:del(section, self.option)
1166 ru = ns:taboption("timer", ListValue, "retry_unit", "not displayed, but needed otherwise error",
1167 translate("On Error the script will retry the failed action after given time") )
1168 ru.template = "ddns/detail_lvalue"
1169 ru.default = "seconds"
1170 ru.rmempty = false -- want to control write process
1171 ru:value("seconds", translate("seconds"))
1172 ru:value("minutes", translate("minutes"))
1173 --ru:value("hours", translate("hours"))
1174 --ru:value("days", translate("days"))
1175 function ru.write(self, section, value)
1176 -- simulate rmempty=true remove default
1177 local secs = luci.tools.ddns.calc_seconds(ri:formvalue(section), value)
1178 if secs ~= 60 then --default 60seconds
1179 return self.map:set(section, self.option, value)
1181 return true -- will be deleted by retry_interval
1185 -- TAB: LogView (NEW) #############################################################################
1186 lv = ns:taboption("logview", DummyValue, "_logview")
1187 lv.template = "ddns/detail_logview"
1188 lv.inputtitle = translate("Read / Reread log file")
1190 function lv.cfgvalue(self, section)
1191 local lfile=log_dir .. "/" .. section .. ".log"
1192 if nixio.fs.access(lfile) then
1193 return lfile .. "\n" .. translate("Please press [Read] button")
1195 return lfile .. "\n" .. translate("File not found or empty")