Merge pull request #952 from cshore-history/pull-request-fstab-avoid-block-umount...
[oweals/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-2018 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.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"
27
28 function index()
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"
32
33         -- no config create an empty one
34         if not nxfs.access("/etc/config/ddns") then
35                 nxfs.writefile("/etc/config/ddns", "")
36         end
37
38         -- preset new option "lookup_host" if not already defined
39         local uci = muci.cursor()
40         local commit = false
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"])
44                         commit = true
45                 end
46         end)
47         if commit then uci:commit("ddns") end
48         uci:unload("ddns")
49
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
58 end
59
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.")
64                 .. [[<br />]]
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>]]
68                 .. " --- "
69                 .. [[<a href="http://wiki.openwrt.org/doc/uci/ddns" target="_blank">]]
70                 .. I18N.translate("DDNS Client Configuration") .. [[</a>]]
71 end
72 function app_title_back()
73         return  [[<a href="]]
74                 .. DISP.build_url("admin", "services", "ddns")
75                 .. [[">]]
76                 .. I18N.translate(app_title)
77                 .. [[</a>]]
78 end
79
80 -- Standardized application/service functions
81 function app_title_main()
82         tmp = {}
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>]]
97                 
98         return table.concat(tmp)
99 end
100
101 function service_version()
102         
103         local srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
104         local ver
105         
106         if IPKG then
107                 ver = IPKG.info(srv_name)[srv_name].Version
108         else
109                 ver = UTIL.exec(srv_ver_cmd)
110         end
111         
112         if ver and #ver > 0 then return ver or nil end
113         
114 end
115
116 function service_ok()
117         return  IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
118 end
119
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
126
127         data[#data+1]   = {
128                 enabled    = service,           -- service enabled
129                 url_up     = url_start,         -- link to enable DDS (System-Startup)
130         }
131
132         uci:foreach("ddns", "service", function (s)
133
134                 -- Get section we are looking at
135                 -- and enabled state
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
140
141                 -- get force seconds
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
151                 end
152
153                 -- no last update happen
154                 if lasttime == 0 then
155                         datelast = "_never_"
156
157                 -- we read last update
158                 else
159                         -- calc 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)
166                 end
167
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_"
173
174                 -- run once
175                 elseif force_seconds == 0 then
176                         datenext = "_runonce_"
177
178                 -- no process running and NOT enabled
179                 elseif pid == 0 and enabled == 0 then
180                         datenext  = "_disabled_"
181
182                 -- no process running and enabled
183                 elseif pid == 0 and enabled ~= 0 then
184                         datenext = "_stopped_"
185                 end
186
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
192
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)
214                 end
215                 if reg_ip == "" then
216                         reg_ip = "_nodata_"
217                 end
218
219                 -- fill transfer array
220                 data[#data+1]   = {
221                         section  = section,
222                         enabled  = enabled,
223                         iface    = iface,
224                         lookup   = lookup_host,
225                         reg_ip   = reg_ip,
226                         pid      = pid,
227                         datelast = datelast,
228                         datenext = datenext
229                 }
230         end)
231
232         uci:unload("ddns")
233         return data
234 end
235
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)
243
244         if not ldata or #ldata == 0 then
245                 ldata="_nodata_"
246         end
247         uci:unload("ddns")
248         HTTP.write(ldata)
249 end
250
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
256
257         -- if process running we want to stop and return
258         if pid > 0 then
259                 local tmp = NX.kill(pid, 15)    -- terminate
260                 NX.nanosleep(2) -- 2 second "show time"
261                 -- status changed so return full status
262                 data = _get_status()
263                 HTTP.prepare_content("application/json")
264                 HTTP.write_json(data)
265                 return
266         end
267
268         -- read uncommitted changes
269         -- we don't save and commit data from other section or other options
270         -- only enabled will be done
271         local exec        = true
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
276                         exec = false
277                         break
278                 end
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
282                                 exec = false
283                                 break
284                         end
285                         for k_option, v_value in pairs(v_option) do
286                                 -- check if only enabled was changed
287                                 if k_option ~= "enabled" then
288                                         exec = false
289                                         break
290                                 end
291                         end
292                 end
293         end
294
295         -- we can not execute because other
296         -- uncommitted changes pending, so exit here
297         if not exec then
298                 HTTP.write("_uncommitted_")
299                 return
300         end
301
302         -- save enable state
303         uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
304         uci:save("ddns")
305         uci:commit("ddns")
306         uci:unload("ddns")
307
308         -- start ddns-updater for section
309         local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
310         os.execute(command)
311         NX.nanosleep(3) -- 3 seconds "show time"
312
313         -- status changed so return full status
314         data = _get_status()
315         HTTP.prepare_content("application/json")
316         HTTP.write_json(data)
317 end
318
319 -- called by XHR.poll from overview_status.htm
320 function status()
321         local data = _get_status()
322         HTTP.prepare_content("application/json")
323         HTTP.write_json(data)
324 end
325