Merge pull request #250 from openwrt-es/luci-next2
[project/luci.git] / applications / luci-ddns / luasrc / tools / ddns.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 shared module for luci-app-ddns
5 Copyright 2014 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
6
7 function parse_url copied from https://svn.nmap.org/nmap/nselib/url.lua
8 Parses a URL and returns a table with all its parts according to RFC 2396.
9 @author Diego Nehab     @author Eddie Bell <ejlbell@gmail.com>
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15         http://www.apache.org/licenses/LICENSE-2.0
16
17 ]]--
18
19 module("luci.tools.ddns", package.seeall)
20
21 local NX   = require "nixio"
22 local NXFS = require "nixio.fs"
23 local OPKG = require "luci.model.ipkg"
24 local UCI  = require "luci.model.uci"
25 local SYS  = require "luci.sys"
26 local UTIL = require "luci.util"
27
28 -- function to calculate seconds from given interval and unit
29 function calc_seconds(interval, unit)
30         if not tonumber(interval) then
31                 return nil
32         elseif unit == "days" then
33                 return (tonumber(interval) * 86400)     -- 60 sec * 60 min * 24 h
34         elseif unit == "hours" then
35                 return (tonumber(interval) * 3600)      -- 60 sec * 60 min
36         elseif unit == "minutes" then
37                 return (tonumber(interval) * 60)        -- 60 sec
38         elseif unit == "seconds" then
39                 return tonumber(interval)
40         else
41                 return nil
42         end
43 end
44
45 -- check if IPv6 supported by OpenWrt
46 function check_ipv6()
47         return NXFS.access("/proc/net/ipv6_route") 
48            and NXFS.access("/usr/sbin/ip6tables")
49 end
50
51 -- check if Wget with SSL support or cURL installed
52 function check_ssl()
53         if (SYS.call([[ grep -iq "\+ssl" /usr/bin/wget 2>/dev/null ]]) == 0) then
54                 return true
55         else
56                 return NXFS.access("/usr/bin/curl")
57         end
58 end
59
60 -- check if Wget with SSL or cURL with proxy support installed
61 function check_proxy()
62         -- we prefere GNU Wget for communication
63         if (SYS.call([[ grep -iq "\+ssl" /usr/bin/wget 2>/dev/null ]]) == 0) then
64                 return true
65
66         -- if not installed cURL must support proxy
67         elseif NXFS.access("/usr/bin/curl") then
68                 return (SYS.call([[ grep -iq all_proxy /usr/lib/libcurl.so* 2>/dev/null ]]) == 0)
69
70         -- only BusyBox Wget is installed
71         else
72                 return NXFS.access("/usr/bin/wget")
73         end
74 end
75
76 -- check if BIND host installed
77 function check_bind_host()
78         return NXFS.access("/usr/bin/host")
79 end
80
81 -- convert epoch date to given format
82 function epoch2date(epoch, format)
83         if not format or #format < 2 then
84                 local uci = UCI.cursor()
85                 format    = uci:get("ddns", "global", "date_format") or "%F %R"
86                 uci:unload("ddns")
87         end
88         format = format:gsub("%%n", "<br />")   -- replace newline
89         format = format:gsub("%%t", "    ")     -- replace tab
90         return os.date(format, epoch)
91 end
92
93 -- read lastupdate from [section].update file
94 function get_lastupd(section)
95         local uci     = UCI.cursor()
96         local run_dir = uci:get("ddns", "global", "run_dir") or "/var/run/ddns"
97         local etime   = tonumber(NXFS.readfile("%s/%s.update" % { run_dir, section } ) or 0 )
98         uci:unload("ddns")
99         return etime
100 end
101
102 -- read PID from run file and verify if still running
103 function get_pid(section)
104         local uci     = UCI.cursor()
105         local run_dir = uci:get("ddns", "global", "run_dir") or "/var/run/ddns"
106         local pid     = tonumber(NXFS.readfile("%s/%s.pid" % { run_dir, section } ) or 0 )
107         if pid > 0 and not NX.kill(pid, 0) then
108                 pid = 0
109         end
110         uci:unload("ddns")
111         return pid
112 end
113
114 -- read version information for given package if installed
115 function ipkg_version(package)
116         if not package then 
117                 return nil
118         end
119         local info = OPKG.info(package)
120         local data = {}
121         local version = ""
122         local i = 0
123         for k, v in pairs(info) do
124                 if v.Package == package and v.Status.installed then             
125                         version = v.Version
126                         i = i + 1
127                 end
128         end
129         if i > 1 then   -- more then one valid record
130                 return data
131         end
132         local sver = UTIL.split(version, "[%.%-]", nil, true)
133         data = {
134                 version = version,
135                 major   = tonumber(sver[1]) or 0,
136                 minor   = tonumber(sver[2]) or 0,
137                 patch   = tonumber(sver[3]) or 0,
138                 build   = tonumber(sver[4]) or 0
139         }
140         return data
141 end
142
143 -- replacement of build-in read of UCI option
144 -- modified AbstractValue.cfgvalue(self, section) from cbi.lua
145 -- needed to read from other option then current value definition
146 function read_value(self, section, option)
147         local value
148         if self.tag_error[section] then
149                 value = self:formvalue(section)
150         else
151                 value = self.map:get(section, option)
152         end
153
154         if not value then
155                 return nil
156         elseif not self.cast or self.cast == type(value) then
157                 return value
158         elseif self.cast == "string" then
159                 if type(value) == "table" then
160                         return value[1]
161                 end
162         elseif self.cast == "table" then
163                 return { value }
164         end
165 end
166
167 -- replacement of build-in Flag.parse of cbi.lua
168 -- modified to mark section as changed if value changes
169 -- current parse did not do this, but it is done AbstaractValue.parse()
170 function flag_parse(self, section)
171         local fexists = self.map:formvalue(
172                 luci.cbi.FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
173
174         if fexists then
175                 local fvalue = self:formvalue(section) and self.enabled or self.disabled
176                 local cvalue = self:cfgvalue(section)
177                 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
178                         self:write(section, fvalue)
179                 else
180                         self:remove(section)
181                 end
182                 if (fvalue ~= cvalue) then self.section.changed = true end
183         else
184                 self:remove(section)
185                 self.section.changed = true 
186         end
187 end
188
189 -----------------------------------------------------------------------------
190 -- copied from https://svn.nmap.org/nmap/nselib/url.lua
191 -- @author Diego Nehab
192 -- @author Eddie Bell <ejlbell@gmail.com>
193 --[[
194     URI parsing, composition and relative URL resolution
195     LuaSocket toolkit.
196     Author: Diego Nehab
197     RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
198     parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
199 ]]--
200 ---
201 -- Parses a URL and returns a table with all its parts according to RFC 2396.
202 --
203 -- The following grammar describes the names given to the URL parts.
204 -- <code>
205 -- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
206 -- <authority> ::= <userinfo>@<host>:<port>
207 -- <userinfo> ::= <user>[:<password>]
208 -- <path> :: = {<segment>/}<segment>
209 -- </code>
210 --
211 -- The leading <code>/</code> in <code>/<path></code> is considered part of
212 -- <code><path></code>.
213 -- @param url URL of request.
214 -- @param default Table with default values for each field.
215 -- @return A table with the following fields, where RFC naming conventions have
216 --   been preserved:
217 --     <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
218 --     <code>user</code>, <code>password</code>, <code>host</code>,
219 --     <code>port</code>, <code>path</code>, <code>params</code>,
220 --     <code>query</code>, and <code>fragment</code>.
221 -----------------------------------------------------------------------------
222 function parse_url(url) --, default)
223         -- initialize default parameters
224         local parsed = {}
225 --      for i,v in base.pairs(default or parsed) do 
226 --              parsed[i] = v
227 --      end
228
229         -- remove whitespace
230 --      url = string.gsub(url, "%s", "")
231         -- get fragment
232         url = string.gsub(url, "#(.*)$", 
233                 function(f)
234                         parsed.fragment = f
235                         return ""
236                 end)
237         -- get scheme. Lower-case according to RFC 3986 section 3.1.
238         url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
239                 function(s)
240                         parsed.scheme = string.lower(s);
241                         return ""
242                 end)
243         -- get authority
244         url = string.gsub(url, "^//([^/]*)",
245                 function(n)
246                         parsed.authority = n
247                         return ""
248                 end)
249         -- get query stringing
250         url = string.gsub(url, "%?(.*)",
251                 function(q)
252                         parsed.query = q
253                         return ""
254                 end)
255         -- get params
256         url = string.gsub(url, "%;(.*)",
257                 function(p)
258                         parsed.params = p
259                         return ""
260                 end)
261         -- path is whatever was left
262         parsed.path = url
263
264         local authority = parsed.authority
265         if not authority then 
266                 return parsed
267         end
268         authority = string.gsub(authority,"^([^@]*)@",
269                 function(u)
270                         parsed.userinfo = u;
271                         return ""
272                 end)
273         authority = string.gsub(authority, ":([0-9]*)$",
274                 function(p)
275                         if p ~= "" then
276                                 parsed.port = p
277                         end;
278                         return ""
279                 end)
280         if authority ~= "" then
281                 parsed.host = authority
282         end
283
284         local userinfo = parsed.userinfo
285         if not userinfo then
286                 return parsed
287         end
288         userinfo = string.gsub(userinfo, ":([^:]*)$",
289                 function(p)
290                         parsed.password = p;
291                         return ""
292                 end)
293         parsed.user = userinfo
294         return parsed
295 end