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