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