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