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