952c9ffe3f5ce2af0f6628790ab72ae38c7bf9e2
[project/luci.git] / applications / luci-ddns / luasrc / controller / ddns.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
6 Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
7
8 Licensed under the Apache License, Version 2.0 (the "License");
9 you may not use this file except in compliance with the License.
10 You may obtain a copy of the License at
11
12         http://www.apache.org/licenses/LICENSE-2.0
13
14 $Id$
15 ]]--
16
17 module("luci.controller.ddns", package.seeall)
18
19 local NX   = require "nixio"
20 local NXFS = require "nixio.fs"
21 local DISP = require "luci.dispatcher"
22 local HTTP = require "luci.http"
23 local UCI  = require "luci.model.uci"
24 local SYS  = require "luci.sys"
25 local DDNS = require "luci.tools.ddns"          -- ddns multiused functions
26 local UTIL = require "luci.util"
27
28 local luci_ddns_version = "2.1.0-1"     -- luci-app-ddns / openwrt Makefile compatible version
29 local ddns_scripts_min  = "2.1.0-1"     -- minimum version of ddns-scripts required
30
31 function index()
32         -- above 'require "mod"' definitions are not recognized 
33         -- inside index() during initialisation
34
35         -- no configuration file, don't start
36         if not nixio.fs.access("/etc/config/ddns") then
37                 return
38         end
39         -- ddns-scripts 1.0.0 installed, run old luci app
40         if not nixio.fs.access("/usr/lib/ddns/services_ipv6")
41             or nixio.fs.access("/usr/lib/ddns/url_escape.sed") then
42                 local page
43                 page = entry({"admin", "services", "ddns"}, cbi("ddns/ddns"), _("Dynamic DNS"), 60)
44                 page.dependent = true
45                 page = entry({"mini", "network", "ddns"}, cbi("ddns/ddns", {autoapply=true}), _("Dynamic DNS"), 60)
46                 page.dependent = true
47         -- it looks like ddns-scripts 2.x.x are installed
48         else
49                 entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
50                 entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
51                 entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints", 
52                         {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
53                 entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
54                 entry( {"admin", "services", "ddns", "startstop"}, call("startstop") ).leaf = true
55                 entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
56         end
57 end
58
59 -- function to read all sections status and return data array
60 local function _get_status()
61         local uci        = UCI.cursor()
62         local service    = SYS.init.enabled("ddns") and 1 or 0
63         local url_start  = DISP.build_url("admin", "system", "startup")
64         local luci_build = DDNS.ipkg_version("luci-app-ddns").version
65         local ddns_act   = DDNS.ipkg_version("ddns-scripts").version
66         local data       = {}   -- Array to transfer data to javascript
67
68         data[#data+1]   = {
69                 enabled    = service,           -- service enabled
70                 url_up     = url_start,         -- link to enable DDS (System-Startup)
71                 luci_ver   = luci_ddns_version, -- luci-app-ddns / openwrt Makefile compatible version
72                 luci_build = luci_build,        -- installed luci build
73                 script_min = ddns_scripts_min,  -- minimum version of ddns-scripts needed
74                 script_ver = ddns_act           -- installed ddns-scripts
75         }
76
77         uci:foreach("ddns", "service", function (s)
78
79                 -- Get section we are looking at
80                 -- and enabled state
81                 local section   = s[".name"]
82                 local enabled   = tonumber(s["enabled"]) or 0
83                 local datelast  = "_empty_"     -- formated date of last update 
84                 local datenext  = "_empty_"     -- formated date of next update
85
86                 -- get force seconds
87                 local force_seconds = DDNS.calc_seconds(
88                                 tonumber(s["force_interval"]) or 72 ,
89                                 s["force_unit"] or "hours" )
90                 -- get/validate pid and last update 
91                 local pid      = DDNS.get_pid(section)
92                 local uptime   = SYS.uptime()
93                 local lasttime = DDNS.get_lastupd(section)
94                 if lasttime > uptime then       -- /var might not be linked to /tmp
95                         lasttime = 0            -- and/or not cleared on reboot
96                 end
97
98                 -- no last update happen
99                 if lasttime == 0 then
100                         datelast = "_never_"
101
102                 -- we read last update
103                 else
104                         -- calc last update
105                         --             sys.epoch - sys uptime   + lastupdate(uptime)
106                         local epoch = os.time() - uptime + lasttime
107                         -- use linux date to convert epoch
108                         datelast = DDNS.epoch2date(epoch)
109                         -- calc and fill next update
110                         datenext = DDNS.epoch2date(epoch + force_seconds)
111                 end
112
113                 -- process running but update needs to happen
114                 -- problems it force_seconds > uptime
115                 force_seconds = (force_seconds > uptime) and uptime or force_seconds
116                 if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
117                         datenext = "_verify_"
118
119                 -- run once 
120                 elseif force_seconds == 0 then
121                         datenext = "_runonce_"
122
123                 -- no process running and NOT enabled
124                 elseif pid == 0 and enabled == 0 then
125                         datenext  = "_disabled_"
126
127                 -- no process running and NOT 
128                 elseif pid == 0 and enabled ~= 0 then
129                         datenext = "_stopped_"
130                 end
131                 
132                 -- get/set monitored interface and IP version
133                 local iface     = s["interface"] or "_nonet_"
134                 local use_ipv6  = tonumber(s["use_ipv6"]) or 0
135                 if iface ~= "_nonet_" then
136                         local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
137                         iface = ipv .. " / " .. iface
138                 end
139
140                 -- try to get registered IP
141                 local domain    = s["domain"] or "_nodomain_"
142                 local dnsserver = s["dns_server"] or ""
143                 local force_ipversion = tonumber(s["force_ipversion"] or 0)
144                 local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
145                 local command = [[/usr/lib/ddns/dynamic_dns_lucihelper.sh]]
146                 command = command .. [[ get_registered_ip ]] .. domain .. [[ ]] .. use_ipv6 .. 
147                         [[ ]] .. force_ipversion .. [[ ]] .. force_dnstcp .. [[ ]] .. dnsserver
148                 local reg_ip = SYS.exec(command)
149                 if reg_ip == "" then 
150                         reg_ip = "_nodata_"
151                 end
152
153                 -- fill transfer array
154                 data[#data+1]   = {
155                         section  = section,
156                         enabled  = enabled,
157                         iface    = iface,
158                         domain   = domain,
159                         reg_ip   = reg_ip,
160                         pid      = pid,
161                         datelast = datelast,
162                         datenext = datenext
163                 }
164         end)
165
166         uci:unload("ddns")
167         return data
168 end
169
170 -- called by XHR.get from detail_logview.htm
171 function logread(section)
172         -- read application settings
173         local uci         = UCI.cursor()
174         local log_dir     = uci:get("ddns", "global", "log_dir") or "/var/log/ddns"
175         local lfile=log_dir .. "/" .. section .. ".log"
176
177         local ldata=NXFS.readfile(lfile)
178         if not ldata or #ldata == 0 then
179                 ldata="_nodata_"
180         end 
181         uci:unload("ddns")
182         HTTP.write(ldata)
183 end
184
185 -- called by XHR.get from overview_status.htm
186 function startstop(section, enabled)
187         local uci  = UCI.cursor()
188         local data = {}         -- Array to transfer data to javascript
189
190         -- if process running we want to stop and return
191         local pid = DDNS.get_pid(section)
192         if pid > 0 then
193                 local tmp = NX.kill(pid, 15)    -- terminate
194                 NX.nanosleep(2) -- 2 second "show time"
195                 -- status changed so return full status
196                 data = _get_status()
197                 HTTP.prepare_content("application/json")
198                 HTTP.write_json(data)
199                 return
200         end
201
202         -- read uncommited changes
203         -- we don't save and commit data from other section or other options
204         -- only enabled will be done
205         local exec        = true
206         local changed     = uci:changes("ddns")
207         for k_config, v_section in pairs(changed) do
208                 -- security check because uci.changes only gets our config
209                 if k_config ~= "ddns" then
210                         exec = false
211                         break
212                 end
213                 for k_section, v_option in pairs(v_section) do
214                         -- check if only section of button was changed
215                         if k_section ~= section then
216                                 exec = false
217                                 break
218                         end
219                         for k_option, v_value in pairs(v_option) do
220                                 -- check if only enabled was changed
221                                 if k_option ~= "enabled" then
222                                         exec = false
223                                         break
224                                 end
225                         end
226                 end
227         end
228
229         -- we can not execute because other 
230         -- uncommited changes pending, so exit here
231         if not exec then
232                 HTTP.write("_uncommited_")
233                 return
234         end
235
236         -- save enable state
237         uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
238         uci:save("ddns") 
239         uci:commit("ddns")
240         uci:unload("ddns")
241
242         -- start dynamic_dns_updater.sh script
243         os.execute ([[/usr/lib/ddns/dynamic_dns_updater.sh %s 0 > /dev/null 2>&1 &]] % section)
244         NX.nanosleep(3) -- 3 seconds "show time"
245         
246         -- status changed so return full status
247         data = _get_status()
248         HTTP.prepare_content("application/json")
249         HTTP.write_json(data)
250 end
251
252 -- called by XHR.poll from overview_status.htm
253 function status()
254         local data = _get_status()
255         HTTP.prepare_content("application/json")
256         HTTP.write_json(data)
257 end
258
259 -- check if installed ddns-scripts version < required version
260 function update_needed()
261         local sver = DDNS.ipkg_version("ddns-scripts")
262         local rver = UTIL.split(ddns_scripts_min, "[%.%-]", nil, true)
263         return (sver.major < (tonumber(rver[1]) or 0))
264             or (sver.minor < (tonumber(rver[2]) or 0))
265             or (sver.patch < (tonumber(rver[3]) or 0))
266             or (sver.build < (tonumber(rver[4]) or 0))
267 end
268