luci-app-ddns: rollup to 2.3.0 to reflect changes on ddns-scripts
[project/luci.git] / applications / luci-app-ddns / luasrc / tools / ddns.lua
1 -- Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
2 -- Licensed to the public under the Apache License 2.0.
3
4 module("luci.tools.ddns", package.seeall)
5
6 local NX   = require "nixio"
7 local NXFS = require "nixio.fs"
8 local OPKG = require "luci.model.ipkg"
9 local UCI  = require "luci.model.uci"
10 local SYS  = require "luci.sys"
11 local UTIL = require "luci.util"
12
13 -- function to calculate seconds from given interval and unit
14 function calc_seconds(interval, unit)
15         if not tonumber(interval) then
16                 return nil
17         elseif unit == "days" then
18                 return (tonumber(interval) * 86400)     -- 60 sec * 60 min * 24 h
19         elseif unit == "hours" then
20                 return (tonumber(interval) * 3600)      -- 60 sec * 60 min
21         elseif unit == "minutes" then
22                 return (tonumber(interval) * 60)        -- 60 sec
23         elseif unit == "seconds" then
24                 return tonumber(interval)
25         else
26                 return nil
27         end
28 end
29
30 -- check if IPv6 supported by OpenWrt
31 function check_ipv6()
32         return NXFS.access("/proc/net/ipv6_route")
33            and NXFS.access("/usr/sbin/ip6tables")
34 end
35
36 -- check if Wget with SSL support or cURL installed
37 function check_ssl()
38         if (SYS.call([[ grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 ]]) == 0) then
39                 return true
40         else
41                 return NXFS.access("/usr/bin/curl")
42         end
43 end
44
45 -- check if Wget with SSL or cURL with proxy support installed
46 function check_proxy()
47         -- we prefere GNU Wget for communication
48         if (SYS.call([[ grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 ]]) == 0) then
49                 return true
50
51         -- if not installed cURL must support proxy
52         elseif NXFS.access("/usr/bin/curl") then
53                 return (SYS.call([[ grep -i all_proxy /usr/lib/libcurl.so* >/dev/null 2>&1 ]]) == 0)
54
55         -- only BusyBox Wget is installed
56         else
57                 return NXFS.access("/usr/bin/wget")
58         end
59 end
60
61 -- check if BIND host installed
62 function check_bind_host()
63         return NXFS.access("/usr/bin/host")
64 end
65
66 -- convert epoch date to given format
67 function epoch2date(epoch, format)
68         if not format or #format < 2 then
69                 local uci = UCI.cursor()
70                 format    = uci:get("ddns", "global", "date_format") or "%F %R"
71                 uci:unload("ddns")
72         end
73         format = format:gsub("%%n", "<br />")   -- replace newline
74         format = format:gsub("%%t", "    ")     -- replace tab
75         return os.date(format, epoch)
76 end
77
78 -- read lastupdate from [section].update file
79 function get_lastupd(section)
80         local uci     = UCI.cursor()
81         local run_dir = uci:get("ddns", "global", "run_dir") or "/var/run/ddns"
82         local etime   = tonumber(NXFS.readfile("%s/%s.update" % { run_dir, section } ) or 0 )
83         uci:unload("ddns")
84         return etime
85 end
86
87 -- read PID from run file and verify if still running
88 function get_pid(section)
89         local uci     = UCI.cursor()
90         local run_dir = uci:get("ddns", "global", "run_dir") or "/var/run/ddns"
91         local pid     = tonumber(NXFS.readfile("%s/%s.pid" % { run_dir, section } ) or 0 )
92         if pid > 0 and not NX.kill(pid, 0) then
93                 pid = 0
94         end
95         uci:unload("ddns")
96         return pid
97 end
98
99 -- replacement of build-in read of UCI option
100 -- modified AbstractValue.cfgvalue(self, section) from cbi.lua
101 -- needed to read from other option then current value definition
102 function read_value(self, section, option)
103         local value
104         if self.tag_error[section] then
105                 value = self:formvalue(section)
106         else
107                 value = self.map:get(section, option)
108         end
109
110         if not value then
111                 return nil
112         elseif not self.cast or self.cast == type(value) then
113                 return value
114         elseif self.cast == "string" then
115                 if type(value) == "table" then
116                         return value[1]
117                 end
118         elseif self.cast == "table" then
119                 return { value }
120         end
121 end
122
123 -- replacement of build-in parse of "Value"
124 -- modified AbstractValue.parse(self, section, novld) from cbi.lua
125 -- validate is called if rmempty/optional true or false
126 -- before write check if forcewrite, value eq default, and more
127 function value_parse(self, section, novld)
128         local fvalue = self:formvalue(section)
129         local fexist = ( fvalue and (#fvalue > 0) )     -- not "nil" and "not empty"
130         local cvalue = self:cfgvalue(section)
131         local rm_opt = ( self.rmempty or self.optional )
132         local eq_cfg                                    -- flag: equal cfgvalue
133
134         -- If favlue and cvalue are both tables and have the same content
135         -- make them identical
136         if type(fvalue) == "table" and type(cvalue) == "table" then
137                 eq_cfg = (#fvalue == #cvalue)
138                 if eq_cfg then
139                         for i=1, #fvalue do
140                                 if cvalue[i] ~= fvalue[i] then
141                                         eq_cfg = false
142                                 end
143                         end
144                 end
145                 if eq_cfg then
146                         fvalue = cvalue
147                 end
148         end
149
150         -- removed parameter "section" from function call because used/accepted nowhere
151         -- also removed call to function "transfer"
152         local vvalue, errtxt = self:validate(fvalue)
153
154         -- error handling; validate return "nil"
155         if not vvalue then
156                 if novld then           -- and "novld" set
157                         return          -- then exit without raising an error
158                 end
159
160                 if fexist then          -- and there is a formvalue
161                         self:add_error(section, "invalid", errtxt)
162                         return          -- so data are invalid
163
164                 elseif not rm_opt then  -- and empty formvalue but NOT (rmempty or optional) set
165                         self:add_error(section, "missing", errtxt)
166                         return          -- so data is missing
167                 end
168         end
169         -- for whatever reason errtxt set and not handled above
170         assert( not (errtxt and (#errtxt > 0)), "unhandled validate error" )
171
172         -- lets continue with value returned from validate
173         eq_cfg  = ( vvalue == cvalue )                  -- update equal_config flag
174         local vexist = ( vvalue and (#vvalue > 0) )     -- not "nil" and "not empty"
175         local eq_def = ( vvalue == self.default )       -- equal_default flag
176
177         -- not forcewrite and (rmempty or optional)
178         -- and (no data or equal_default)
179         if not self.forcewrite and rm_opt
180           and (not vexist or eq_def) then
181                 if self:remove(section) then            -- remove data from UCI
182                         self.section.changed = true     -- and push events
183                 end
184                 return
185         end
186
187         -- not forcewrite and no changes, so nothing to write
188         if not self.forcewrite and eq_cfg then
189                 return
190         end
191
192         -- write data to UCI; raise event only on changes
193         if self:write(section, vvalue) and not eq_cfg then
194                 self.section.changed = true
195         end
196 end
197
198 -----------------------------------------------------------------------------
199 -- copied from https://svn.nmap.org/nmap/nselib/url.lua
200 -- @author Diego Nehab
201 -- @author Eddie Bell <ejlbell@gmail.com>
202 --[[
203     URI parsing, composition and relative URL resolution
204     LuaSocket toolkit.
205     Author: Diego Nehab
206     RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
207     parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
208 ]]--
209 ---
210 -- Parses a URL and returns a table with all its parts according to RFC 2396.
211 --
212 -- The following grammar describes the names given to the URL parts.
213 -- <code>
214 -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
215 -- <authority> ::= <userinfo>@<host>:<port>
216 -- <userinfo> ::= <user>[:<password>]
217 -- <path> :: = {<segment>/}<segment>
218 -- </code>
219 --
220 -- The leading <code>/</code> in <code>/<path></code> is considered part of
221 -- <code><path></code>.
222 -- @param url URL of request.
223 -- @param default Table with default values for each field.
224 -- @return A table with the following fields, where RFC naming conventions have
225 --   been preserved:
226 --     <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
227 --     <code>user</code>, <code>password</code>, <code>host</code>,
228 --     <code>port</code>, <code>path</code>, <code>params</code>,
229 --     <code>query</code>, and <code>fragment</code>.
230 -----------------------------------------------------------------------------
231 function parse_url(url) --, default)
232         -- initialize default parameters
233         local parsed = {}
234 --      for i,v in base.pairs(default or parsed) do
235 --              parsed[i] = v
236 --      end
237
238         -- remove whitespace
239 --      url = string.gsub(url, "%s", "")
240         -- get fragment
241         url = string.gsub(url, "#(.*)$",
242                 function(f)
243                         parsed.fragment = f
244                         return ""
245                 end)
246         -- get scheme. Lower-case according to RFC 3986 section 3.1.
247         url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
248                 function(s)
249                         parsed.scheme = string.lower(s);
250                         return ""
251                 end)
252         -- get authority
253         url = string.gsub(url, "^//([^/]*)",
254                 function(n)
255                         parsed.authority = n
256                         return ""
257                 end)
258         -- get query stringing
259         url = string.gsub(url, "%?(.*)",
260                 function(q)
261                         parsed.query = q
262                         return ""
263                 end)
264         -- get params
265         url = string.gsub(url, "%;(.*)",
266                 function(p)
267                         parsed.params = p
268                         return ""
269                 end)
270         -- path is whatever was left
271         parsed.path = url
272
273         local authority = parsed.authority
274         if not authority then
275                 return parsed
276         end
277         authority = string.gsub(authority,"^([^@]*)@",
278                 function(u)
279                         parsed.userinfo = u;
280                         return ""
281                 end)
282         authority = string.gsub(authority, ":([0-9]*)$",
283                 function(p)
284                         if p ~= "" then
285                                 parsed.port = p
286                         end;
287                         return ""
288                 end)
289         if authority ~= "" then
290                 parsed.host = authority
291         end
292
293         local userinfo = parsed.userinfo
294         if not userinfo then
295                 return parsed
296         end
297         userinfo = string.gsub(userinfo, ":([^:]*)$",
298                 function(p)
299                         parsed.password = p;
300                         return ""
301                 end)
302         parsed.user = userinfo
303         return parsed
304 end