1 -- Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
2 -- Licensed to the public under the Apache License 2.0.
4 module("luci.tools.ddns", package.seeall)
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"
13 -- function to calculate seconds from given interval and unit
14 function calc_seconds(interval, unit)
15 if not tonumber(interval) then
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)
30 -- check if IPv6 supported by OpenWrt
32 return NXFS.access("/proc/net/ipv6_route")
33 and NXFS.access("/usr/sbin/ip6tables")
36 -- check if Wget with SSL support or cURL installed
38 if (SYS.call([[ grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 ]]) == 0) then
41 return NXFS.access("/usr/bin/curl")
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
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)
55 -- only BusyBox Wget is installed
57 return NXFS.access("/usr/bin/wget")
61 -- check if BIND host installed
62 function check_bind_host()
63 return NXFS.access("/usr/bin/host")
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"
73 format = format:gsub("%%n", "<br />") -- replace newline
74 format = format:gsub("%%t", " ") -- replace tab
75 return os.date(format, epoch)
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 )
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
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)
104 if self.tag_error[section] then
105 value = self:formvalue(section)
107 value = self.map:get(section, option)
112 elseif not self.cast or self.cast == type(value) then
114 elseif self.cast == "string" then
115 if type(value) == "table" then
118 elseif self.cast == "table" then
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
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)
140 if cvalue[i] ~= fvalue[i] then
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)
154 -- error handling; validate return "nil"
156 if novld then -- and "novld" set
157 return -- then exit without raising an error
160 if fexist then -- and there is a formvalue
161 self:add_error(section, "invalid", errtxt)
162 return -- so data are invalid
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
170 -- lets continue with value returned from validate
171 eq_cfg = ( vvalue == cvalue ) -- update equal_config flag
172 local vexist = ( vvalue and (#vvalue > 0) ) -- not "nil" and "not empty"
173 local eq_def = ( vvalue == self.default ) -- equal_default flag
175 -- not forcewrite and (rmempty or optional)
176 -- and (no data or equal_default)
177 if not self.forcewrite and rm_opt
178 and (not vexist or eq_def) then
179 if self:remove(section) then -- remove data from UCI
180 self.section.changed = true -- and push events
185 -- not forcewrite and no changes, so nothing to write
186 if not self.forcewrite and eq_cfg then
190 -- write data to UCI; raise event only on changes
191 if self:write(section, vvalue) and not eq_cfg then
192 self.section.changed = true
196 -----------------------------------------------------------------------------
197 -- copied from https://svn.nmap.org/nmap/nselib/url.lua
198 -- @author Diego Nehab
199 -- @author Eddie Bell <ejlbell@gmail.com>
201 URI parsing, composition and relative URL resolution
204 RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
205 parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
208 -- Parses a URL and returns a table with all its parts according to RFC 2396.
210 -- The following grammar describes the names given to the URL parts.
212 -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
213 -- <authority> ::= <userinfo>@<host>:<port>
214 -- <userinfo> ::= <user>[:<password>]
215 -- <path> :: = {<segment>/}<segment>
218 -- The leading <code>/</code> in <code>/<path></code> is considered part of
219 -- <code><path></code>.
220 -- @param url URL of request.
221 -- @param default Table with default values for each field.
222 -- @return A table with the following fields, where RFC naming conventions have
224 -- <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
225 -- <code>user</code>, <code>password</code>, <code>host</code>,
226 -- <code>port</code>, <code>path</code>, <code>params</code>,
227 -- <code>query</code>, and <code>fragment</code>.
228 -----------------------------------------------------------------------------
229 function parse_url(url) --, default)
230 -- initialize default parameters
232 -- for i,v in base.pairs(default or parsed) do
237 -- url = string.gsub(url, "%s", "")
239 url = string.gsub(url, "#(.*)$",
244 -- get scheme. Lower-case according to RFC 3986 section 3.1.
245 url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
247 parsed.scheme = string.lower(s);
251 url = string.gsub(url, "^//([^/]*)",
256 -- get query stringing
257 url = string.gsub(url, "%?(.*)",
263 url = string.gsub(url, "%;(.*)",
268 -- path is whatever was left
271 local authority = parsed.authority
272 if not authority then
275 authority = string.gsub(authority,"^([^@]*)@",
280 authority = string.gsub(authority, ":([0-9]*)$",
287 if authority ~= "" then
288 parsed.host = authority
291 local userinfo = parsed.userinfo
295 userinfo = string.gsub(userinfo, ":([^:]*)$",
300 parsed.user = userinfo