ddns.lua 13 KB

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