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-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
5 -- Licensed to the public under the Apache License 2.0.
7 module("luci.controller.ddns", package.seeall)
9 local NX = require "nixio"
10 local NXFS = require "nixio.fs"
11 local DISP = require "luci.dispatcher"
12 local HTTP = require "luci.http"
13 local I18N = require "luci.i18n" -- not globally avalible here
14 local IPKG = require "luci.model.ipkg"
15 local SYS = require "luci.sys"
16 local UCI = require "luci.model.uci"
17 local UTIL = require "luci.util"
18 local DDNS = require "luci.tools.ddns" -- ddns multiused functions
20 luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
22 local srv_name = "ddns-scripts"
23 local srv_ver_min = "2.7.7" -- minimum version of service required
24 local app_name = "luci-app-ddns"
25 local app_title = "Dynamic DNS"
26 local app_version = "2.4.9-1"
29 local nxfs = require "nixio.fs" -- global definitions not available
30 local sys = require "luci.sys" -- in function index()
31 local muci = require "luci.model.uci"
33 -- no config create an empty one
34 if not nxfs.access("/etc/config/ddns") then
35 nxfs.writefile("/etc/config/ddns", "")
38 -- preset new option "lookup_host" if not already defined
39 local uci = muci.cursor()
41 uci:foreach("ddns", "service", function (s)
42 if not s["lookup_host"] and s["domain"] then
43 uci:set("ddns", s[".name"], "lookup_host", s["domain"])
47 if commit then uci:commit("ddns") end
50 entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
51 entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
52 entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints",
53 {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
54 entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true
55 entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
56 entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true
57 entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
60 -- Application specific information functions
61 function app_description()
62 return I18N.translate("Dynamic DNS allows that your router can be reached with " ..
63 "a fixed hostname while having a dynamically changing IP address.")
65 .. I18N.translate("OpenWrt Wiki") .. ": "
66 .. [[<a href="http://wiki.openwrt.org/doc/howto/ddns.client" target="_blank">]]
67 .. I18N.translate("DDNS Client Documentation") .. [[</a>]]
69 .. [[<a href="http://wiki.openwrt.org/doc/uci/ddns" target="_blank">]]
70 .. I18N.translate("DDNS Client Configuration") .. [[</a>]]
72 function app_title_back()
74 .. DISP.build_url("admin", "services", "ddns")
76 .. I18N.translate(app_title)
80 -- Standardized application/service functions
81 function app_title_main()
83 tmp[#tmp+1] = [[<a href="javascript:alert(']]
84 tmp[#tmp+1] = I18N.translate("Version Information")
85 tmp[#tmp+1] = [[\n\n]] .. app_name
86 tmp[#tmp+1] = [[\n]] .. I18N.translate("Version") .. [[: ]] .. app_version
87 tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("required") .. [[:]]
88 tmp[#tmp+1] = [[\n]] .. I18N.translate("Version") .. [[: ]]
89 tmp[#tmp+1] = srv_ver_min .. [[ ]] .. I18N.translate("or higher")
90 tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("installed") .. [[:]]
91 tmp[#tmp+1] = [[\n]] .. I18N.translate("Version") .. [[: ]]
92 tmp[#tmp+1] = (service_version() or I18N.translate("NOT installed"))
93 tmp[#tmp+1] = [[\n\n]]
94 tmp[#tmp+1] = [[')">]]
95 tmp[#tmp+1] = I18N.translate(app_title)
96 tmp[#tmp+1] = [[</a>]]
98 return table.concat(tmp)
101 function service_version()
103 local srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
107 ver = IPKG.info(srv_name)[srv_name].Version
109 ver = UTIL.exec(srv_ver_cmd)
112 if ver and #ver > 0 then return ver or nil end
116 function service_ok()
117 return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
120 -- internal function to read all sections status and return data array
121 local function _get_status()
122 local uci = UCI.cursor()
123 local service = SYS.init.enabled("ddns") and 1 or 0
124 local url_start = DISP.build_url("admin", "system", "startup")
125 local data = {} -- Array to transfer data to javascript
128 enabled = service, -- service enabled
129 url_up = url_start, -- link to enable DDS (System-Startup)
132 uci:foreach("ddns", "service", function (s)
134 -- Get section we are looking at
136 local section = s[".name"]
137 local enabled = tonumber(s["enabled"]) or 0
138 local datelast = "_empty_" -- formatted date of last update
139 local datenext = "_empty_" -- formatted date of next update
142 local force_seconds = DDNS.calc_seconds(
143 tonumber(s["force_interval"]) or 72 ,
144 s["force_unit"] or "hours" )
145 -- get/validate pid and last update
146 local pid = DDNS.get_pid(section)
147 local uptime = SYS.uptime()
148 local lasttime = DDNS.get_lastupd(section)
149 if lasttime > uptime then -- /var might not be linked to /tmp
150 lasttime = 0 -- and/or not cleared on reboot
153 -- no last update happen
154 if lasttime == 0 then
157 -- we read last update
160 -- sys.epoch - sys uptime + lastupdate(uptime)
161 local epoch = os.time() - uptime + lasttime
162 -- use linux date to convert epoch
163 datelast = DDNS.epoch2date(epoch)
164 -- calc and fill next update
165 datenext = DDNS.epoch2date(epoch + force_seconds)
168 -- process running but update needs to happen
169 -- problems if force_seconds > uptime
170 force_seconds = (force_seconds > uptime) and uptime or force_seconds
171 if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
172 datenext = "_verify_"
175 elseif force_seconds == 0 then
176 datenext = "_runonce_"
178 -- no process running and NOT enabled
179 elseif pid == 0 and enabled == 0 then
180 datenext = "_disabled_"
182 -- no process running and enabled
183 elseif pid == 0 and enabled ~= 0 then
184 datenext = "_stopped_"
187 -- get/set monitored interface and IP version
188 local iface = s["interface"] or "wan"
189 local use_ipv6 = tonumber(s["use_ipv6"]) or 0
190 local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
191 iface = ipv .. " / " .. iface
193 -- try to get registered IP
194 local lookup_host = s["lookup_host"] or "_nolookup_"
195 local chk_sec = DDNS.calc_seconds(
196 tonumber(s["check_interval"]) or 10,
197 s["check_unit"] or "minutes" )
198 local reg_ip = DDNS.get_regip(section, chk_sec)
199 if reg_ip == "NOFILE" then
200 local dnsserver = s["dns_server"] or ""
201 local force_ipversion = tonumber(s["force_ipversion"] or 0)
202 local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
203 local is_glue = tonumber(s["is_glue"] or 0)
204 local command = luci_helper .. [[ -]]
205 if (use_ipv6 == 1) then command = command .. [[6]] end
206 if (force_ipversion == 1) then command = command .. [[f]] end
207 if (force_dnstcp == 1) then command = command .. [[t]] end
208 if (is_glue == 1) then command = command .. [[g]] end
209 command = command .. [[l ]] .. lookup_host
210 command = command .. [[ -S ]] .. section
211 if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
212 command = command .. [[ -- get_registered_ip]]
213 reg_ip = SYS.exec(command)
219 -- fill transfer array
224 lookup = lookup_host,
236 -- called by XHR.get from detail_logview.htm
237 function logread(section)
238 -- read application settings
239 local uci = UCI.cursor()
240 local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
241 local lfile = ldir .. "/" .. section .. ".log"
242 local ldata = NXFS.readfile(lfile)
244 if not ldata or #ldata == 0 then
251 -- called by XHR.get from overview_status.htm
252 function startstop(section, enabled)
253 local uci = UCI.cursor()
254 local pid = DDNS.get_pid(section)
255 local data = {} -- Array to transfer data to javascript
257 -- if process running we want to stop and return
259 local tmp = NX.kill(pid, 15) -- terminate
260 NX.nanosleep(2) -- 2 second "show time"
261 -- status changed so return full status
263 HTTP.prepare_content("application/json")
264 HTTP.write_json(data)
268 -- read uncommitted changes
269 -- we don't save and commit data from other section or other options
270 -- only enabled will be done
272 local changed = uci:changes("ddns")
273 for k_config, v_section in pairs(changed) do
274 -- security check because uci.changes only gets our config
275 if k_config ~= "ddns" then
279 for k_section, v_option in pairs(v_section) do
280 -- check if only section of button was changed
281 if k_section ~= section then
285 for k_option, v_value in pairs(v_option) do
286 -- check if only enabled was changed
287 if k_option ~= "enabled" then
295 -- we can not execute because other
296 -- uncommitted changes pending, so exit here
298 HTTP.write("_uncommitted_")
303 uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
308 -- start ddns-updater for section
309 local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
311 NX.nanosleep(3) -- 3 seconds "show time"
313 -- status changed so return full status
315 HTTP.prepare_content("application/json")
316 HTTP.write_json(data)
319 -- called by XHR.poll from overview_status.htm
321 local data = _get_status()
322 HTTP.prepare_content("application/json")
323 HTTP.write_json(data)