Merge pull request #1690 from karlp/pagekite
[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-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 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.9-1"
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 chk_sec  = DDNS.calc_seconds(
191                                         tonumber(s["check_interval"]) or 10,
192                                         s["check_unit"] or "minutes" )
193                 local reg_ip = DDNS.get_regip(section, chk_sec)
194                 if reg_ip == "NOFILE" then
195                         local dnsserver = s["dns_server"] or ""
196                         local force_ipversion = tonumber(s["force_ipversion"] or 0)
197                         local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
198                         local is_glue = tonumber(s["is_glue"] or 0)
199                         local command = luci_helper .. [[ -]]
200                         if (use_ipv6 == 1) then command = command .. [[6]] end
201                         if (force_ipversion == 1) then command = command .. [[f]] end
202                         if (force_dnstcp == 1) then command = command .. [[t]] end
203                         if (is_glue == 1) then command = command .. [[g]] end
204                         command = command .. [[l ]] .. lookup_host
205                         command = command .. [[ -S ]] .. section
206                         if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
207                         command = command .. [[ -- get_registered_ip]]
208                         reg_ip = SYS.exec(command)
209                 end
210                 if reg_ip == "" then
211                         reg_ip = "_nodata_"
212                 end
213
214                 -- fill transfer array
215                 data[#data+1]   = {
216                         section  = section,
217                         enabled  = enabled,
218                         iface    = iface,
219                         lookup   = lookup_host,
220                         reg_ip   = reg_ip,
221                         pid      = pid,
222                         datelast = datelast,
223                         datenext = datenext
224                 }
225         end)
226
227         uci:unload("ddns")
228         return data
229 end
230
231 -- called by XHR.get from detail_logview.htm
232 function logread(section)
233         -- read application settings
234         local uci       = UCI.cursor()
235         local ldir      = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
236         local lfile     = ldir .. "/" .. section .. ".log"
237         local ldata     = NXFS.readfile(lfile)
238
239         if not ldata or #ldata == 0 then
240                 ldata="_nodata_"
241         end
242         uci:unload("ddns")
243         HTTP.write(ldata)
244 end
245
246 -- called by XHR.get from overview_status.htm
247 function startstop(section, enabled)
248         local uci  = UCI.cursor()
249         local pid  = DDNS.get_pid(section)
250         local data = {}         -- Array to transfer data to javascript
251
252         -- if process running we want to stop and return
253         if pid > 0 then
254                 local tmp = NX.kill(pid, 15)    -- terminate
255                 NX.nanosleep(2) -- 2 second "show time"
256                 -- status changed so return full status
257                 data = _get_status()
258                 HTTP.prepare_content("application/json")
259                 HTTP.write_json(data)
260                 return
261         end
262
263         -- read uncommitted changes
264         -- we don't save and commit data from other section or other options
265         -- only enabled will be done
266         local exec        = true
267         local changed     = uci:changes("ddns")
268         for k_config, v_section in pairs(changed) do
269                 -- security check because uci.changes only gets our config
270                 if k_config ~= "ddns" then
271                         exec = false
272                         break
273                 end
274                 for k_section, v_option in pairs(v_section) do
275                         -- check if only section of button was changed
276                         if k_section ~= section then
277                                 exec = false
278                                 break
279                         end
280                         for k_option, v_value in pairs(v_option) do
281                                 -- check if only enabled was changed
282                                 if k_option ~= "enabled" then
283                                         exec = false
284                                         break
285                                 end
286                         end
287                 end
288         end
289
290         -- we can not execute because other
291         -- uncommitted changes pending, so exit here
292         if not exec then
293                 HTTP.write("_uncommitted_")
294                 return
295         end
296
297         -- save enable state
298         uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
299         uci:save("ddns")
300         uci:commit("ddns")
301         uci:unload("ddns")
302
303         -- start ddns-updater for section
304         local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
305         os.execute(command)
306         NX.nanosleep(3) -- 3 seconds "show time"
307
308         -- status changed so return full status
309         data = _get_status()
310         HTTP.prepare_content("application/json")
311         HTTP.write_json(data)
312 end
313
314 -- called by XHR.poll from overview_status.htm
315 function status()
316         local data = _get_status()
317         HTTP.prepare_content("application/json")
318         HTTP.write_json(data)
319 end
320