changed stat to lstat in nixio_lstat function
[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-2016 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 local srv_name    = "ddns-scripts"
21 local srv_ver_min = "2.7.2"                     -- minimum version of service required
22 local srv_ver_cmd = [[/usr/lib/ddns/dynamic_dns_updater.sh --version | awk {'print $2'}]]
23 local app_name    = "luci-app-ddns"
24 local app_title   = "Dynamic DNS"
25 local app_version = "2.4.6-1"
26
27 function index()
28         local nxfs      = require "nixio.fs"            -- global definitions not available
29         local sys       = require "luci.sys"            -- in function index()
30         local ddns      = require "luci.tools.ddns"     -- ddns multiused functions
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         return  [[<a href="javascript:alert(']]
83                         .. I18N.translate("Version Information")
84                         .. [[\n\n]] .. app_name
85                         .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] .. app_version
86                         .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("required") .. [[:]]
87                         .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
88                                 .. srv_ver_min .. [[ ]] .. I18N.translate("or higher")
89                         .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("installed") .. [[:]]
90                         .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
91                                 .. (service_version() or I18N.translate("NOT installed"))
92                         .. [[\n\n]]
93                 .. [[')">]]
94                 .. I18N.translate(app_title)
95                 .. [[</a>]]
96 end
97 function service_version()
98         local ver = nil
99         IPKG.list_installed(srv_name, function(n, v, d)
100                         if v and (#v > 0) then ver = v end
101                 end
102         )
103         if not ver or (#ver == 0) then
104                 ver = UTIL.exec(srv_ver_cmd)
105                 if #ver == 0 then ver = nil end
106         end
107         return  ver
108 end
109 function service_ok()
110         return  IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
111 end
112
113 -- internal function to read all sections status and return data array
114 local function _get_status()
115         local uci        = UCI.cursor()
116         local service    = SYS.init.enabled("ddns") and 1 or 0
117         local url_start  = DISP.build_url("admin", "system", "startup")
118         local data       = {}   -- Array to transfer data to javascript
119
120         data[#data+1]   = {
121                 enabled    = service,           -- service enabled
122                 url_up     = url_start,         -- link to enable DDS (System-Startup)
123         }
124
125         uci:foreach("ddns", "service", function (s)
126
127                 -- Get section we are looking at
128                 -- and enabled state
129                 local section   = s[".name"]
130                 local enabled   = tonumber(s["enabled"]) or 0
131                 local datelast  = "_empty_"     -- formatted date of last update
132                 local datenext  = "_empty_"     -- formatted date of next update
133
134                 -- get force seconds
135                 local force_seconds = DDNS.calc_seconds(
136                                 tonumber(s["force_interval"]) or 72 ,
137                                 s["force_unit"] or "hours" )
138                 -- get/validate pid and last update
139                 local pid      = DDNS.get_pid(section)
140                 local uptime   = SYS.uptime()
141                 local lasttime = DDNS.get_lastupd(section)
142                 if lasttime > uptime then       -- /var might not be linked to /tmp
143                         lasttime = 0            -- and/or not cleared on reboot
144                 end
145
146                 -- no last update happen
147                 if lasttime == 0 then
148                         datelast = "_never_"
149
150                 -- we read last update
151                 else
152                         -- calc last update
153                         --             sys.epoch - sys uptime   + lastupdate(uptime)
154                         local epoch = os.time() - uptime + lasttime
155                         -- use linux date to convert epoch
156                         datelast = DDNS.epoch2date(epoch)
157                         -- calc and fill next update
158                         datenext = DDNS.epoch2date(epoch + force_seconds)
159                 end
160
161                 -- process running but update needs to happen
162                 -- problems if force_seconds > uptime
163                 force_seconds = (force_seconds > uptime) and uptime or force_seconds
164                 if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
165                         datenext = "_verify_"
166
167                 -- run once
168                 elseif force_seconds == 0 then
169                         datenext = "_runonce_"
170
171                 -- no process running and NOT enabled
172                 elseif pid == 0 and enabled == 0 then
173                         datenext  = "_disabled_"
174
175                 -- no process running and enabled
176                 elseif pid == 0 and enabled ~= 0 then
177                         datenext = "_stopped_"
178                 end
179
180                 -- get/set monitored interface and IP version
181                 local iface     = s["interface"] or "_nonet_"
182                 local use_ipv6  = tonumber(s["use_ipv6"]) or 0
183                 if iface ~= "_nonet_" then
184                         local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
185                         iface = ipv .. " / " .. iface
186                 end
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 = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh]]
195                 command = command .. [[ get_registered_ip ]] .. lookup_host .. [[ ]] .. use_ipv6 ..
196                         [[ ]] .. force_ipversion .. [[ ]] .. force_dnstcp ..
197                         [[ ]] .. is_glue .. [[ ]] .. dnsserver
198                 local reg_ip = SYS.exec(command)
199                 if reg_ip == "" then
200                         reg_ip = "_nodata_"
201                 end
202
203                 -- fill transfer array
204                 data[#data+1]   = {
205                         section  = section,
206                         enabled  = enabled,
207                         iface    = iface,
208                         lookup   = lookup_host,
209                         reg_ip   = reg_ip,
210                         pid      = pid,
211                         datelast = datelast,
212                         datenext = datenext
213                 }
214         end)
215
216         uci:unload("ddns")
217         return data
218 end
219
220 -- called by XHR.get from detail_logview.htm
221 function logread(section)
222         -- read application settings
223         local uci       = UCI.cursor()
224         local log_dir   = uci:get("ddns", "global", "log_dir") or "/var/log/ddns"
225         local lfile     = log_dir .. "/" .. section .. ".log"
226         local ldata     = NXFS.readfile(lfile)
227
228         if not ldata or #ldata == 0 then
229                 ldata="_nodata_"
230         end
231         uci:unload("ddns")
232         HTTP.write(ldata)
233 end
234
235 -- called by XHR.get from overview_status.htm
236 function startstop(section, enabled)
237         local uci  = UCI.cursor()
238         local pid  = DDNS.get_pid(section)
239         local data = {}         -- Array to transfer data to javascript
240
241         -- if process running we want to stop and return
242         if pid > 0 then
243                 local tmp = NX.kill(pid, 15)    -- terminate
244                 NX.nanosleep(2) -- 2 second "show time"
245                 -- status changed so return full status
246                 data = _get_status()
247                 HTTP.prepare_content("application/json")
248                 HTTP.write_json(data)
249                 return
250         end
251
252         -- read uncommitted changes
253         -- we don't save and commit data from other section or other options
254         -- only enabled will be done
255         local exec        = true
256         local changed     = uci:changes("ddns")
257         for k_config, v_section in pairs(changed) do
258                 -- security check because uci.changes only gets our config
259                 if k_config ~= "ddns" then
260                         exec = false
261                         break
262                 end
263                 for k_section, v_option in pairs(v_section) do
264                         -- check if only section of button was changed
265                         if k_section ~= section then
266                                 exec = false
267                                 break
268                         end
269                         for k_option, v_value in pairs(v_option) do
270                                 -- check if only enabled was changed
271                                 if k_option ~= "enabled" then
272                                         exec = false
273                                         break
274                                 end
275                         end
276                 end
277         end
278
279         -- we can not execute because other
280         -- uncommitted changes pending, so exit here
281         if not exec then
282                 HTTP.write("_uncommitted_")
283                 return
284         end
285
286         -- save enable state
287         uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
288         uci:save("ddns")
289         uci:commit("ddns")
290         uci:unload("ddns")
291
292         -- start dynamic_dns_updater.sh script
293         os.execute ([[/usr/lib/ddns/dynamic_dns_updater.sh %s 0 > /dev/null 2>&1 &]] % section)
294         NX.nanosleep(3) -- 3 seconds "show time"
295
296         -- status changed so return full status
297         data = _get_status()
298         HTTP.prepare_content("application/json")
299         HTTP.write_json(data)
300 end
301
302 -- called by XHR.poll from overview_status.htm
303 function status()
304         local data = _get_status()
305         HTTP.prepare_content("application/json")
306         HTTP.write_json(data)
307 end
308