Merge branch 'master' of https://github.com/openwrt/luci into proto=ipv6+aiccu+fixes
[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 -- compare versions using "<=" "<" ">" ">=" "=" "<<" ">>"
100 function ipkg_ver_compare(ver1, comp, ver2)
101         if not ver1 or not (#ver1 > 0)
102         or not ver2 or not (#ver2 > 0)
103         or not comp or not (#comp > 0) then return nil end
104         -- correct compare string
105         if comp == "<>" or comp == "><" or comp == "!=" or comp == "~=" then comp = "~="
106         elseif comp == "<=" or comp == "<" or comp == "=<" then comp = "<="
107         elseif comp == ">=" or comp == ">" or comp == "=>" then comp = ">="
108         elseif comp == "="  or comp == "==" then comp = "=="
109         elseif comp == "<<" then comp = "<"
110         elseif comp == ">>" then comp = ">"
111         else return nil end
112
113         local av1 = UTIL.split(ver1, "[%.%-]", nil, true)
114         local av2 = UTIL.split(ver2, "[%.%-]", nil, true)
115
116         for i = 1, math.max(table.getn(av1),table.getn(av2)), 1  do
117                 local s1 = av1[i] or ""
118                 local s2 = av2[i] or ""
119                 local n1 = tonumber(s1)
120                 local n2 = tonumber(s2)
121
122                 -- one numeric and other empty string then set other to 0
123                 if n1 and not n2 and (not s2 or #s2 == 0) then n2 = 0 end
124                 if n2 and not n1 and (not s1 or #s1 == 0) then n1 = 0 end
125
126                 local nc = (n1 and n2)  -- numeric compare
127
128                 if nc then
129                         -- first "not equal" found return true
130                         if comp == "~=" and (n1 ~= n2) then return true end
131                         -- first "lower" found return true
132                         if (comp == "<" or comp == "<=") and (n1 < n2) then return true end
133                         -- first "greater" found return true
134                         if (comp == ">" or comp == ">=") and (n1 > n2) then return true end
135                         -- not equal then return false
136                         if (n1 ~= n2) then return false end
137                 else
138                         if comp == "~=" and (s1 ~= s2) then return true end
139                         if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
140                         if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
141                         if (s1 ~= s2) then return false end
142                 end
143         end
144         -- all equal then true
145         return true
146 end
147
148 -- read version information for given package if installed
149 function ipkg_ver_installed(pkg)
150         local version = nil
151         local control = io.open("/usr/lib/opkg/info/%s.control" % pkg, "r")
152         if control then
153                 local ln
154                 repeat
155                         ln = control:read("*l")
156                         if ln and ln:match("^Version: ") then
157                                 version = ln:gsub("^Version: ", "")
158                                 break
159                         end
160                 until not ln
161                 control:close()
162         end
163         return version
164 end
165
166 -- replacement of build-in read of UCI option
167 -- modified AbstractValue.cfgvalue(self, section) from cbi.lua
168 -- needed to read from other option then current value definition
169 function read_value(self, section, option)
170         local value
171         if self.tag_error[section] then
172                 value = self:formvalue(section)
173         else
174                 value = self.map:get(section, option)
175         end
176
177         if not value then
178                 return nil
179         elseif not self.cast or self.cast == type(value) then
180                 return value
181         elseif self.cast == "string" then
182                 if type(value) == "table" then
183                         return value[1]
184                 end
185         elseif self.cast == "table" then
186                 return { value }
187         end
188 end
189
190 -- replacement of build-in Flag.parse of cbi.lua
191 -- modified to mark section as changed if value changes
192 -- current parse did not do this, but it is done AbstaractValue.parse()
193 function flag_parse(self, section)
194         local fexists = self.map:formvalue(
195                 luci.cbi.FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
196
197         if fexists then
198                 local fvalue = self:formvalue(section) and self.enabled or self.disabled
199                 local cvalue = self:cfgvalue(section)
200                 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
201                         self:write(section, fvalue)
202                 else
203                         self:remove(section)
204                 end
205                 if (fvalue ~= cvalue) then self.section.changed = true end
206         else
207                 self:remove(section)
208                 self.section.changed = true
209         end
210 end
211
212 -----------------------------------------------------------------------------
213 -- copied from https://svn.nmap.org/nmap/nselib/url.lua
214 -- @author Diego Nehab
215 -- @author Eddie Bell <ejlbell@gmail.com>
216 --[[
217     URI parsing, composition and relative URL resolution
218     LuaSocket toolkit.
219     Author: Diego Nehab
220     RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
221     parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
222 ]]--
223 ---
224 -- Parses a URL and returns a table with all its parts according to RFC 2396.
225 --
226 -- The following grammar describes the names given to the URL parts.
227 -- <code>
228 -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
229 -- <authority> ::= <userinfo>@<host>:<port>
230 -- <userinfo> ::= <user>[:<password>]
231 -- <path> :: = {<segment>/}<segment>
232 -- </code>
233 --
234 -- The leading <code>/</code> in <code>/<path></code> is considered part of
235 -- <code><path></code>.
236 -- @param url URL of request.
237 -- @param default Table with default values for each field.
238 -- @return A table with the following fields, where RFC naming conventions have
239 --   been preserved:
240 --     <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
241 --     <code>user</code>, <code>password</code>, <code>host</code>,
242 --     <code>port</code>, <code>path</code>, <code>params</code>,
243 --     <code>query</code>, and <code>fragment</code>.
244 -----------------------------------------------------------------------------
245 function parse_url(url) --, default)
246         -- initialize default parameters
247         local parsed = {}
248 --      for i,v in base.pairs(default or parsed) do
249 --              parsed[i] = v
250 --      end
251
252         -- remove whitespace
253 --      url = string.gsub(url, "%s", "")
254         -- get fragment
255         url = string.gsub(url, "#(.*)$",
256                 function(f)
257                         parsed.fragment = f
258                         return ""
259                 end)
260         -- get scheme. Lower-case according to RFC 3986 section 3.1.
261         url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
262                 function(s)
263                         parsed.scheme = string.lower(s);
264                         return ""
265                 end)
266         -- get authority
267         url = string.gsub(url, "^//([^/]*)",
268                 function(n)
269                         parsed.authority = n
270                         return ""
271                 end)
272         -- get query stringing
273         url = string.gsub(url, "%?(.*)",
274                 function(q)
275                         parsed.query = q
276                         return ""
277                 end)
278         -- get params
279         url = string.gsub(url, "%;(.*)",
280                 function(p)
281                         parsed.params = p
282                         return ""
283                 end)
284         -- path is whatever was left
285         parsed.path = url
286
287         local authority = parsed.authority
288         if not authority then
289                 return parsed
290         end
291         authority = string.gsub(authority,"^([^@]*)@",
292                 function(u)
293                         parsed.userinfo = u;
294                         return ""
295                 end)
296         authority = string.gsub(authority, ":([0-9]*)$",
297                 function(p)
298                         if p ~= "" then
299                                 parsed.port = p
300                         end;
301                         return ""
302                 end)
303         if authority ~= "" then
304                 parsed.host = authority
305         end
306
307         local userinfo = parsed.userinfo
308         if not userinfo then
309                 return parsed
310         end
311         userinfo = string.gsub(userinfo, ":([^:]*)$",
312                 function(p)
313                         parsed.password = p;
314                         return ""
315                 end)
316         parsed.user = userinfo
317         return parsed
318 end