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