1dfa6255417c1d73cf36c75ead165fe5a525d0e0
[project/luci.git] / applications / luci-app-ddns / luasrc / controller / ddns.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
3 -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
4 -- Copyright 2014-2017 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
5 -- Licensed to the public under the Apache License 2.0.
6
7 module("luci.controller.ddns", package.seeall)
8
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
19
20 luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
21
22 local srv_name = "ddns-scripts"
23 local srv_ver_min = "2.7.6" -- minimum version of service required
24 local srv_ver_cmd = luci_helper .. [[ -V | awk {'print $2'}]]
25 local app_name = "luci-app-ddns"
26 local app_title = "Dynamic DNS"
27 local app_version = "2.4.8-2"
28
29 function index()
30 local nxfs = require "nixio.fs" -- global definitions not available
31 local sys = require "luci.sys" -- in function index()
32 local ddns = require "luci.tools.ddns" -- ddns multiused functions
33 local muci = require "luci.model.uci"
34
35 -- no config create an empty one
36 if not nxfs.access("/etc/config/ddns") then
37 nxfs.writefile("/etc/config/ddns", "")
38 end
39
40 -- preset new option "lookup_host" if not already defined
41 local uci = muci.cursor()
42 local commit = false
43 uci:foreach("ddns", "service", function (s)
44 if not s["lookup_host"] and s["domain"] then
45 uci:set("ddns", s[".name"], "lookup_host", s["domain"])
46 commit = true
47 end
48 end)
49 if commit then uci:commit("ddns") end
50 uci:unload("ddns")
51
52 entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
53 entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
54 entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints",
55 {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
56 entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true
57 entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
58 entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true
59 entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
60 end
61
62 -- Application specific information functions
63 function app_description()
64 return I18N.translate("Dynamic DNS allows that your router can be reached with " ..
65 "a fixed hostname while having a dynamically changing IP address.")
66 .. [[<br />]]
67 .. I18N.translate("OpenWrt Wiki") .. ": "
68 .. [[<a href="http://wiki.openwrt.org/doc/howto/ddns.client" target="_blank">]]
69 .. I18N.translate("DDNS Client Documentation") .. [[</a>]]
70 .. " --- "
71 .. [[<a href="http://wiki.openwrt.org/doc/uci/ddns" target="_blank">]]
72 .. I18N.translate("DDNS Client Configuration") .. [[</a>]]
73 end
74 function app_title_back()
75 return [[<a href="]]
76 .. DISP.build_url("admin", "services", "ddns")
77 .. [[">]]
78 .. I18N.translate(app_title)
79 .. [[</a>]]
80 end
81
82 -- Standardized application/service functions
83 function app_title_main()
84 return [[<a href="javascript:alert(']]
85 .. I18N.translate("Version Information")
86 .. [[\n\n]] .. app_name
87 .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] .. app_version
88 .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("required") .. [[:]]
89 .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
90 .. srv_ver_min .. [[ ]] .. I18N.translate("or higher")
91 .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("installed") .. [[:]]
92 .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
93 .. (service_version() or I18N.translate("NOT installed"))
94 .. [[\n\n]]
95 .. [[')">]]
96 .. I18N.translate(app_title)
97 .. [[</a>]]
98 end
99 function service_version()
100 local ver = nil
101
102 ver = UTIL.exec(srv_ver_cmd)
103 if #ver > 0 then return ver end
104
105 IPKG.list_installed(srv_name, function(n, v, d)
106 if v and (#v > 0) then ver = v end
107 end
108 )
109 return ver
110 end
111 function service_ok()
112 return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
113 end
114
115 -- internal function to read all sections status and return data array
116 local function _get_status()
117 local uci = UCI.cursor()
118 local service = SYS.init.enabled("ddns") and 1 or 0
119 local url_start = DISP.build_url("admin", "system", "startup")
120 local data = {} -- Array to transfer data to javascript
121
122 data[#data+1] = {
123 enabled = service, -- service enabled
124 url_up = url_start, -- link to enable DDS (System-Startup)
125 }
126
127 uci:foreach("ddns", "service", function (s)
128
129 -- Get section we are looking at
130 -- and enabled state
131 local section = s[".name"]
132 local enabled = tonumber(s["enabled"]) or 0
133 local datelast = "_empty_" -- formatted date of last update
134 local datenext = "_empty_" -- formatted date of next update
135
136 -- get force seconds
137 local force_seconds = DDNS.calc_seconds(
138 tonumber(s["force_interval"]) or 72 ,
139 s["force_unit"] or "hours" )
140 -- get/validate pid and last update
141 local pid = DDNS.get_pid(section)
142 local uptime = SYS.uptime()
143 local lasttime = DDNS.get_lastupd(section)
144 if lasttime > uptime then -- /var might not be linked to /tmp
145 lasttime = 0 -- and/or not cleared on reboot
146 end
147
148 -- no last update happen
149 if lasttime == 0 then
150 datelast = "_never_"
151
152 -- we read last update
153 else
154 -- calc last update
155 -- sys.epoch - sys uptime + lastupdate(uptime)
156 local epoch = os.time() - uptime + lasttime
157 -- use linux date to convert epoch
158 datelast = DDNS.epoch2date(epoch)
159 -- calc and fill next update
160 datenext = DDNS.epoch2date(epoch + force_seconds)
161 end
162
163 -- process running but update needs to happen
164 -- problems if force_seconds > uptime
165 force_seconds = (force_seconds > uptime) and uptime or force_seconds
166 if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
167 datenext = "_verify_"
168
169 -- run once
170 elseif force_seconds == 0 then
171 datenext = "_runonce_"
172
173 -- no process running and NOT enabled
174 elseif pid == 0 and enabled == 0 then
175 datenext = "_disabled_"
176
177 -- no process running and enabled
178 elseif pid == 0 and enabled ~= 0 then
179 datenext = "_stopped_"
180 end
181
182 -- get/set monitored interface and IP version
183 local iface = s["interface"] or "wan"
184 local use_ipv6 = tonumber(s["use_ipv6"]) or 0
185 local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
186 iface = ipv .. " / " .. iface
187
188 -- try to get registered IP
189 local lookup_host = s["lookup_host"] or "_nolookup_"
190 local dnsserver = s["dns_server"] or ""
191 local force_ipversion = tonumber(s["force_ipversion"] or 0)
192 local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
193 local is_glue = tonumber(s["is_glue"] or 0)
194 local command = luci_helper .. [[ -]]
195 if (use_ipv6 == 1) then command = command .. [[6]] end
196 if (force_ipversion == 1) then command = command .. [[f]] end
197 if (force_dnstcp == 1) then command = command .. [[t]] end
198 if (is_glue == 1) then command = command .. [[g]] end
199 command = command .. [[l ]] .. lookup_host
200 if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
201 command = command .. [[ -- get_registered_ip]]
202 local reg_ip = SYS.exec(command)
203 if reg_ip == "" then
204 reg_ip = "_nodata_"
205 end
206
207 -- fill transfer array
208 data[#data+1] = {
209 section = section,
210 enabled = enabled,
211 iface = iface,
212 lookup = lookup_host,
213 reg_ip = reg_ip,
214 pid = pid,
215 datelast = datelast,
216 datenext = datenext
217 }
218 end)
219
220 uci:unload("ddns")
221 return data
222 end
223
224 -- called by XHR.get from detail_logview.htm
225 function logread(section)
226 -- read application settings
227 local uci = UCI.cursor()
228 local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
229 local lfile = ldir .. "/" .. section .. ".log"
230 local ldata = NXFS.readfile(lfile)
231
232 if not ldata or #ldata == 0 then
233 ldata="_nodata_"
234 end
235 uci:unload("ddns")
236 HTTP.write(ldata)
237 end
238
239 -- called by XHR.get from overview_status.htm
240 function startstop(section, enabled)
241 local uci = UCI.cursor()
242 local pid = DDNS.get_pid(section)
243 local data = {} -- Array to transfer data to javascript
244
245 -- if process running we want to stop and return
246 if pid > 0 then
247 local tmp = NX.kill(pid, 15) -- terminate
248 NX.nanosleep(2) -- 2 second "show time"
249 -- status changed so return full status
250 data = _get_status()
251 HTTP.prepare_content("application/json")
252 HTTP.write_json(data)
253 return
254 end
255
256 -- read uncommitted changes
257 -- we don't save and commit data from other section or other options
258 -- only enabled will be done
259 local exec = true
260 local changed = uci:changes("ddns")
261 for k_config, v_section in pairs(changed) do
262 -- security check because uci.changes only gets our config
263 if k_config ~= "ddns" then
264 exec = false
265 break
266 end
267 for k_section, v_option in pairs(v_section) do
268 -- check if only section of button was changed
269 if k_section ~= section then
270 exec = false
271 break
272 end
273 for k_option, v_value in pairs(v_option) do
274 -- check if only enabled was changed
275 if k_option ~= "enabled" then
276 exec = false
277 break
278 end
279 end
280 end
281 end
282
283 -- we can not execute because other
284 -- uncommitted changes pending, so exit here
285 if not exec then
286 HTTP.write("_uncommitted_")
287 return
288 end
289
290 -- save enable state
291 uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
292 uci:save("ddns")
293 uci:commit("ddns")
294 uci:unload("ddns")
295
296 -- start ddns-updater for section
297 local command = luci_helper .. [[ -S ]] .. section .. [[ -- start]]
298 os.execute(command)
299 NX.nanosleep(3) -- 3 seconds "show time"
300
301 -- status changed so return full status
302 data = _get_status()
303 HTTP.prepare_content("application/json")
304 HTTP.write_json(data)
305 end
306
307 -- called by XHR.poll from overview_status.htm
308 function status()
309 local data = _get_status()
310 HTTP.prepare_content("application/json")
311 HTTP.write_json(data)
312 end
313