2
0

ddns.lua 10 KB

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