c8d10f29b077ec64d42352c0b467cb8a0fd78c89
[project/luci.git] / applications / luci-ddns / luasrc / model / cbi / ddns / detail.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
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>
8
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>
14
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
18
19 http://www.apache.org/licenses/LICENSE-2.0
20
21 $Id$
22 ]]--
23
24 require "luci.dispatcher"
25 require "nixio.fs"
26 require "luci.sys"
27 require "luci.tools.webadmin"
28 require "luci.cbi.datatypes"
29 require "luci.tools.ddns" -- ddns multiused functions
30
31 -- takeover arguments
32 section = arg[1]
33
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
41
42 -- html constants
43 font_red = "<font color='red'>"
44 font_off = "</font>"
45 bold_on = "<strong>"
46 bold_off = "</strong>"
47
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 ..
52 font_red ..
53 translate("IPv6 not supported") ..
54 font_off ..
55 "<br />" .. translate("please select 'IPv4' address version") ..
56 bold_off
57 err_ipv6_other = bold_on ..
58 font_red ..
59 translate("IPv6 not supported") ..
60 font_off ..
61 "<br />" .. translate("please select 'IPv4' address version in") .. " " ..
62 [[<a href="]] ..
63 luci.dispatcher.build_url("admin", "services", "ddns", "detail", section) ..
64 "?tab.dns." .. section .. "=basic" ..
65 [[">]] ..
66 translate("Basic Settings") ..
67 [[</a>]] ..
68 bold_off
69
70 function err_tab_basic(self)
71 return translate("Basic Settings") .. " - " .. self.title .. ": "
72 end
73 function err_tab_adv(self)
74 return translate("Advanced Settings") .. " - " .. self.title .. ": "
75 end
76 function err_tab_timer(self)
77 return translate("Timer Settings") .. " - " .. self.title .. ": "
78 end
79
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)
85 local _network = "-"
86 local _url = "-"
87 local _interface = "-"
88 local _script = "-"
89 local _proxy = ""
90
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)
109 end
110
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)
115
116 if ret == 0 then
117 return true -- valid
118 else
119 return nil -- invalid
120 end
121 end
122
123 -- cbi-map definition
124 m = Map("ddns")
125
126 m.title = [[<a href="]] .. luci.dispatcher.build_url("admin", "services", "ddns") .. [[">]] ..
127 translate("Dynamic DNS") .. [[</a>]]
128
129 m.description = translate("Dynamic DNS allows that your router can be reached with " ..
130 "a fixed hostname while having a dynamically changing " ..
131 "IP address.")
132
133 m.redirect = luci.dispatcher.build_url("admin", "services", "ddns")
134
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"
138 -- log directory
139 log_dir = m.uci:get(m.config, "global", "log_dir") or "/var/log/ddns"
140
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 )
150
151 -- TAB: Basic ##################################################################
152 -- enabled
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"
158
159 -- use_ipv6 (NEW)
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"
164 usev6.default = "0"
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") )
170 end
171 if value == "1" and not has_ipv6 then
172 self.description = err_ipv6_basic
173 end
174 return value
175 end
176 function usev6.validate(self, value)
177 if (value == "1" and has_ipv6) or value == "0" then
178 return value
179 end
180 return nil, err_tab_basic(self) .. err_ipv6_plain
181 end
182 function usev6.write(self, section, value)
183 if value == "0" then -- force rmempty
184 return self.map:del(section, self.option)
185 else
186 return self.map:set(section, self.option, value)
187 end
188 end
189
190 -- IPv4 - service_name
191 svc4 = ns:taboption("basic", ListValue, "ipv4_service_name",
192 translate("DDNS Service provider") .. " [IPv4]" )
193 svc4.default = "-"
194 svc4:depends("use_ipv6", "0") -- only show on IPv4
195
196 local services4 = { }
197 local fd4 = io.open("/usr/lib/ddns/services", "r")
198
199 if fd4 then
200 local ln
201 repeat
202 ln = fd4:read("*l")
203 local s = ln and ln:match('^%s*"([^"]+)"')
204 if s then services4[#services4+1] = s end
205 until not ln
206 fd4:close()
207 end
208
209 for _, v in luci.util.vspairs(services4) do svc4:value(v) end
210 svc4:value("-", translate("-- custom --") )
211
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
215 return "-"
216 else
217 return v
218 end
219 end
220 function svc4.validate(self, value)
221 if usev6:formvalue(section) == "0" then -- do only on IPv4
222 return value
223 else
224 return "" -- supress validate error
225 end
226 end
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)
233 else
234 return self.map:del(section, "service_name")
235 end
236 end
237 end
238
239 -- IPv6 - service_name
240 svc6 = ns:taboption("basic", ListValue, "ipv6_service_name",
241 translate("DDNS Service provider") .. " [IPv6]" )
242 svc6.default = "-"
243 svc6:depends("use_ipv6", "1") -- only show on IPv6
244 if not has_ipv6 then
245 svc6.description = err_ipv6_basic
246 end
247
248 local services6 = { }
249 local fd6 = io.open("/usr/lib/ddns/services_ipv6", "r")
250
251 if fd6 then
252 local ln
253 repeat
254 ln = fd6:read("*l")
255 local s = ln and ln:match('^%s*"([^"]+)"')
256 if s then services6[#services6+1] = s end
257 until not ln
258 fd6:close()
259 end
260
261 for _, v in luci.util.vspairs(services6) do svc6:value(v) end
262 svc6:value("-", translate("-- custom --") )
263
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
267 return "-"
268 else
269 return v
270 end
271 end
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
276 else
277 return "" -- supress validate error
278 end
279 end
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)
286 else
287 return self.map:del(section, "service_name")
288 end
289 end
290 end
291
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)
301
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")
308 else
309 return "" -- suppress validate error / update_script is given
310 end
311 elseif (#script > 0) then
312 return nil, err_tab_basic(self) .. translate("either url or script could be set")
313 end
314
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
324 end
325
326 return value
327 end
328
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)
337
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")
344 else
345 return "" -- suppress validate error / update_url is given
346 end
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")
351 end
352 return value
353 end
354
355 -- IPv4/IPv6 - domain
356 dom = ns:taboption("basic", Value, "domain",
357 translate("Hostname/Domain"),
358 translate("Replaces [DOMAIN] in Update-URL") )
359 dom.rmempty = false
360 dom.placeholder = "mypersonaldomain.dyndns.org"
361 function dom.validate(self, value)
362 if not value
363 or not (#value > 0)
364 or not luci.cbi.datatypes.hostname(value) then
365 return nil, err_tab_basic(self) .. translate("invalid - Sample") .. ": 'mypersonaldomain.dyndns.org'"
366 else
367 return value
368 end
369 end
370
371 -- IPv4/IPv6 - username
372 user = ns:taboption("basic", Value, "username",
373 translate("Username"),
374 translate("Replaces [USERNAME] in Update-URL") )
375 user.rmempty = false
376 function user.validate(self, value)
377 if not value then
378 return nil, err_tab_basic(self) .. translate("missing / required")
379 end
380 return value
381 end
382
383 -- IPv4/IPv6 - password
384 pw = ns:taboption("basic", Value, "password",
385 translate("Password"),
386 translate("Replaces [PASSWORD] in Update-URL") )
387 pw.rmempty = false
388 pw.password = true
389 function pw.validate(self, value)
390 if not value then
391 return nil, err_tab_basic(self) .. translate("missing / required")
392 end
393 return value
394 end
395
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
408 else
409 self.description = translate("Enable secure communication with DDNS provider")
410 end
411 return value
412 end
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") .. " !"
416 end
417 function https.write(self, section, value)
418 if value == "1" then
419 return self.map:set(section, self.option, value)
420 else
421 self.map:del(section, "cacert")
422 return self.map:del(section, self.option)
423 end
424 end
425 end
426
427 -- IPv4/IPv6 - cacert (NEW)
428 if has_ssl then
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
440 end
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
445 return value
446 end
447 end
448 return nil, err_tab_basic(self) ..
449 translate("file or directory not found or not 'IGNORE'") .. " !"
450 end
451 end
452
453 -- use_syslog
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.") )
457 slog.default = "0"
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"))
463
464 -- use_logfile (NEW)
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
472
473 -- TAB: Advanced ##############################################################
474 -- IPv4 - ip_source
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")
486 end
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")
493 else
494 return value
495 end
496 end
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")
516 end
517 self.map:del(section, self.option) -- delete "ipv4_source" helper
518 return self.map:set(section, "ip_source", value) -- and write "ip_source
519 end
520
521 -- IPv6 - 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"))
531 if not has_ipv6 then
532 src6.description = err_ipv6_other
533 end
534 function src6.cfgvalue(self, section)
535 return luci.tools.ddns.read_value(self, section, "ip_source")
536 end
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")
545 else
546 return value
547 end
548 end
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")
568 end
569 self.map:del(section, self.option) -- delete "ipv4_source" helper
570 return self.map:set(section, "ip_source", value) -- and write "ip_source
571 end
572
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")
578 ipn4.default = "wan"
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")
582 end
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"
588 return ""
589 else
590 return value
591 end
592 end
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"
598 return true
599 else
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"
604 end
605 end
606
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)
613 if has_ipv6 then
614 ipn6.description = translate("Defines the network to read systems IPv6-Address from")
615 else
616 ipn6.description = err_ipv6_other
617 end
618 function ipn6.cfgvalue(self, section)
619 return luci.tools.ddns.read_value(self, section, "ip_network")
620 end
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"
626 return ""
627 elseif has_ipv6 then
628 return value
629 else
630 return nil, err_tab_adv(self) .. err_ipv6_plain
631 end
632 end
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"
638 return true
639 else
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"
644 end
645 end
646
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")
655 end
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"
661 return ""
662 elseif not value or #value == 0 then
663 return nil, err_tab_adv(self) .. translate("missing / required")
664 end
665
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 ]] ..
672 url.host ..
673 [[>/dev/null 2>&1]]) ~= 0 then
674 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
675 else
676 return value
677 end
678 end
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"
684 return true
685 else
686 self.map:del(section, self.option) -- delete "ipv4_url" helper
687 return self.map:set(section, "ip_url", value) -- and write "ip_url"
688 end
689 end
690
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"
696 if has_ipv6 then
697 iurl6.description = translate("Defines the Web page to read systems IPv6-Address from")
698 else
699 iurl6.description = err_ipv6_other
700 end
701 function iurl6.cfgvalue(self, section)
702 return luci.tools.ddns.read_value(self, section, "ip_url")
703 end
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"
709 return ""
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")
714 end
715
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 ]] ..
722 url.host ..
723 [[>/dev/null 2>&1]]) ~= 0 then
724 return nil, err_tab_adv(self) .. translate("can not resolve host: ") .. url.host
725 else
726 return value
727 end
728 end
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"
734 return true
735 else
736 self.map:del(section, self.option) -- delete "ipv6_url" helper
737 return self.map:set(section, "ip_url", value) -- and write "ip_url"
738 end
739 end
740
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
752 ipi:value(v)
753 end
754 end
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
758 return ""
759 else
760 return value
761 end
762 end
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
766 return true
767 else
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)
773 end
774 end
775
776 -- IPv4 + IPv6 - ip_script (NEW)
777 ips = ns:taboption("advanced", Value, "ip_script",
778 translate("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
786 return ""
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'")
790 else
791 return value
792 end
793 end
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
797 return true
798 else
799 return self.map:set(section, self.option, value)
800 end
801 end
802
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")
812 eif4.default = "wan"
813 luci.tools.webadmin.cbi_add_networks(eif4)
814 function eif4.cfgvalue(self, section)
815 return luci.tools.ddns.read_value(self, section, "interface")
816 end
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
822 else
823 return value
824 end
825 end
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
831 else
832 self.map:del(section, self.option) -- delete "ipv4_interface" helper
833 return self.map:set(section, "interface", value) -- and write "interface"
834 end
835 end
836
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)
847 if not has_ipv6 then
848 eif6.description = err_ipv6_other
849 else
850 eif6.description = translate("Network on which the ddns-updater scripts will be started")
851 end
852 function eif6.cfgvalue(self, section)
853 return luci.tools.ddns.read_value(self, section, "interface")
854 end
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
862 else
863 return value
864 end
865 end
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
871 else
872 self.map:del(section, self.option) -- delete "ipv6_interface" helper
873 return self.map:set(section, "interface", value) -- and write "interface"
874 end
875 end
876
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
890 else
891 self.description = translate("OPTIONAL: Force the usage of pure IPv4/IPv6 only communication.")
892 end
893 return value
894 end
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")
898 end
899 function fipv.write(self, section, value)
900 if value == "1" then
901 return self.map:set(section, self.option, value)
902 else
903 return self.map:del(section, self.option)
904 end
905 end
906 end
907
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
917 if not value then
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")
921 else
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")
932 end
933 end
934 end
935
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
947 else
948 self.description = translate("OPTIONAL: Force the use of TCP instead of default UDP on DNS requests.")
949 end
950 return value
951 end
952 function tcp.validate(self, value)
953 if (value == "1" and has_dnstcp ) or value == "0" then
954 return value
955 end
956 return nil, err_tab_adv(self) .. translate("DNS requests via TCP not supported")
957 end
958 end
959
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
972 else
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
977 end
978 return value
979 end
980 function pxy.validate(self, value)
981 -- if .datatype is set, then it is checked before calling this function
982 if not value then
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")
996 end
997 else
998 return nil, err .. translate("PROXY-Server not supported")
999 end
1000 end
1001 end
1002
1003 -- TAB: Timer #################################################################
1004 -- check_interval
1005 ci = ns:taboption("timer", Value, "check_interval",
1006 translate("Check Interval") )
1007 ci.template = "ddns/detail_value"
1008 ci.default = 10
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")
1014 end
1015
1016 local secs = luci.tools.ddns.calc_seconds(value, cu:formvalue(section))
1017 if secs >= 300 then
1018 return value
1019 else
1020 return nil, err_tab_timer(self) .. translate("minimum value 5 minutes == 300 seconds")
1021 end
1022 end
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)
1028 else
1029 self.map:del(section, "check_unit")
1030 return self.map:del(section, self.option)
1031 end
1032 end
1033
1034 -- check_unit
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)
1050 else
1051 return true
1052 end
1053 end
1054
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'")
1065 end
1066
1067 local force_s = luci.tools.ddns.calc_seconds(value, fu:formvalue(section))
1068 if force_s == 0 then
1069 return value
1070 end
1071
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
1075 end
1076
1077 local check_s = luci.tools.ddns.calc_seconds(ci_value, cu:formvalue(section))
1078 if force_s >= check_s then
1079 return value
1080 end
1081
1082 return nil, err_tab_timer(self) .. translate("must be greater or equal 'Check Interval'")
1083 end
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)
1089 else
1090 self.map:del(section, "force_unit")
1091 return self.map:del(section, self.option)
1092 end
1093 end
1094
1095 -- force_unit
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)
1112 else
1113 return true
1114 end
1115 end
1116
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") )
1121 rc.default = 5
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'")
1127 else
1128 return value
1129 end
1130 end
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)
1135 else
1136 return self.map:del(section, self.option)
1137 end
1138 end
1139
1140 -- retry_interval
1141 ri = ns:taboption("timer", Value, "retry_interval",
1142 translate("Error Retry Interval") )
1143 ri.template = "ddns/detail_value"
1144 ri.default = 60
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'")
1150 else
1151 return value
1152 end
1153 end
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)
1159 else
1160 self.map:del(section, "retry_unit")
1161 return self.map:del(section, self.option)
1162 end
1163 end
1164
1165 -- retry_unit
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)
1180 else
1181 return true -- will be deleted by retry_interval
1182 end
1183 end
1184
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")
1189 lv.rows = 50
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")
1194 end
1195 return lfile .. "\n" .. translate("File not found or empty")
1196 end
1197
1198 return m