ddns.lua 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
  3. -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de>
  4. -- Copyright 2014-2017 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
  5. -- Licensed to the public under the Apache License 2.0.
  6. module("luci.controller.ddns", package.seeall)
  7. local NX = require "nixio"
  8. local NXFS = require "nixio.fs"
  9. local DISP = require "luci.dispatcher"
  10. local HTTP = require "luci.http"
  11. local I18N = require "luci.i18n" -- not globally avalible here
  12. local IPKG = require "luci.model.ipkg"
  13. local SYS = require "luci.sys"
  14. local UCI = require "luci.model.uci"
  15. local UTIL = require "luci.util"
  16. local DDNS = require "luci.tools.ddns" -- ddns multiused functions
  17. luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
  18. local srv_name = "ddns-scripts"
  19. local srv_ver_min = "2.7.6" -- minimum version of service required
  20. local srv_ver_cmd = luci_helper .. [[ -V | awk {'print $2'}]]
  21. local app_name = "luci-app-ddns"
  22. local app_title = "Dynamic DNS"
  23. local app_version = "2.4.8-2"
  24. function index()
  25. local nxfs = require "nixio.fs" -- global definitions not available
  26. local sys = require "luci.sys" -- in function index()
  27. local ddns = require "luci.tools.ddns" -- ddns multiused functions
  28. local muci = require "luci.model.uci"
  29. -- no config create an empty one
  30. if not nxfs.access("/etc/config/ddns") then
  31. nxfs.writefile("/etc/config/ddns", "")
  32. end
  33. -- preset new option "lookup_host" if not already defined
  34. local uci = muci.cursor()
  35. local commit = false
  36. uci:foreach("ddns", "service", function (s)
  37. if not s["lookup_host"] and s["domain"] then
  38. uci:set("ddns", s[".name"], "lookup_host", s["domain"])
  39. commit = true
  40. end
  41. end)
  42. if commit then uci:commit("ddns") end
  43. uci:unload("ddns")
  44. entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59)
  45. entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true
  46. entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints",
  47. {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true
  48. entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true
  49. entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true
  50. entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true
  51. entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true
  52. end
  53. -- Application specific information functions
  54. function app_description()
  55. return I18N.translate("Dynamic DNS allows that your router can be reached with " ..
  56. "a fixed hostname while having a dynamically changing IP address.")
  57. .. [[<br />]]
  58. .. I18N.translate("OpenWrt Wiki") .. ": "
  59. .. [[<a href="http://wiki.openwrt.org/doc/howto/ddns.client" target="_blank">]]
  60. .. I18N.translate("DDNS Client Documentation") .. [[</a>]]
  61. .. " --- "
  62. .. [[<a href="http://wiki.openwrt.org/doc/uci/ddns" target="_blank">]]
  63. .. I18N.translate("DDNS Client Configuration") .. [[</a>]]
  64. end
  65. function app_title_back()
  66. return [[<a href="]]
  67. .. DISP.build_url("admin", "services", "ddns")
  68. .. [[">]]
  69. .. I18N.translate(app_title)
  70. .. [[</a>]]
  71. end
  72. -- Standardized application/service functions
  73. function app_title_main()
  74. return [[<a href="javascript:alert(']]
  75. .. I18N.translate("Version Information")
  76. .. [[\n\n]] .. app_name
  77. .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] .. app_version
  78. .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("required") .. [[:]]
  79. .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
  80. .. srv_ver_min .. [[ ]] .. I18N.translate("or higher")
  81. .. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("installed") .. [[:]]
  82. .. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
  83. .. (service_version() or I18N.translate("NOT installed"))
  84. .. [[\n\n]]
  85. .. [[')">]]
  86. .. I18N.translate(app_title)
  87. .. [[</a>]]
  88. end
  89. function service_version()
  90. local ver = nil
  91. ver = UTIL.exec(srv_ver_cmd)
  92. if #ver > 0 then return ver end
  93. IPKG.list_installed(srv_name, function(n, v, d)
  94. if v and (#v > 0) then ver = v end
  95. end
  96. )
  97. return ver
  98. end
  99. function service_ok()
  100. return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
  101. end
  102. -- internal function to read all sections status and return data array
  103. local function _get_status()
  104. local uci = UCI.cursor()
  105. local service = SYS.init.enabled("ddns") and 1 or 0
  106. local url_start = DISP.build_url("admin", "system", "startup")
  107. local data = {} -- Array to transfer data to javascript
  108. data[#data+1] = {
  109. enabled = service, -- service enabled
  110. url_up = url_start, -- link to enable DDS (System-Startup)
  111. }
  112. uci:foreach("ddns", "service", function (s)
  113. -- Get section we are looking at
  114. -- and enabled state
  115. local section = s[".name"]
  116. local enabled = tonumber(s["enabled"]) or 0
  117. local datelast = "_empty_" -- formatted date of last update
  118. local datenext = "_empty_" -- formatted date of next update
  119. -- get force seconds
  120. local force_seconds = DDNS.calc_seconds(
  121. tonumber(s["force_interval"]) or 72 ,
  122. s["force_unit"] or "hours" )
  123. -- get/validate pid and last update
  124. local pid = DDNS.get_pid(section)
  125. local uptime = SYS.uptime()
  126. local lasttime = DDNS.get_lastupd(section)
  127. if lasttime > uptime then -- /var might not be linked to /tmp
  128. lasttime = 0 -- and/or not cleared on reboot
  129. end
  130. -- no last update happen
  131. if lasttime == 0 then
  132. datelast = "_never_"
  133. -- we read last update
  134. else
  135. -- calc last update
  136. -- sys.epoch - sys uptime + lastupdate(uptime)
  137. local epoch = os.time() - uptime + lasttime
  138. -- use linux date to convert epoch
  139. datelast = DDNS.epoch2date(epoch)
  140. -- calc and fill next update
  141. datenext = DDNS.epoch2date(epoch + force_seconds)
  142. end
  143. -- process running but update needs to happen
  144. -- problems if force_seconds > uptime
  145. force_seconds = (force_seconds > uptime) and uptime or force_seconds
  146. if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then
  147. datenext = "_verify_"
  148. -- run once
  149. elseif force_seconds == 0 then
  150. datenext = "_runonce_"
  151. -- no process running and NOT enabled
  152. elseif pid == 0 and enabled == 0 then
  153. datenext = "_disabled_"
  154. -- no process running and enabled
  155. elseif pid == 0 and enabled ~= 0 then
  156. datenext = "_stopped_"
  157. end
  158. -- get/set monitored interface and IP version
  159. local iface = s["interface"] or "wan"
  160. local use_ipv6 = tonumber(s["use_ipv6"]) or 0
  161. local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4"
  162. iface = ipv .. " / " .. iface
  163. -- try to get registered IP
  164. local lookup_host = s["lookup_host"] or "_nolookup_"
  165. local dnsserver = s["dns_server"] or ""
  166. local force_ipversion = tonumber(s["force_ipversion"] or 0)
  167. local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
  168. local is_glue = tonumber(s["is_glue"] or 0)
  169. local command = luci_helper .. [[ -]]
  170. if (use_ipv6 == 1) then command = command .. [[6]] end
  171. if (force_ipversion == 1) then command = command .. [[f]] end
  172. if (force_dnstcp == 1) then command = command .. [[t]] end
  173. if (is_glue == 1) then command = command .. [[g]] end
  174. command = command .. [[l ]] .. lookup_host
  175. if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end
  176. command = command .. [[ -- get_registered_ip]]
  177. local reg_ip = SYS.exec(command)
  178. if reg_ip == "" then
  179. reg_ip = "_nodata_"
  180. end
  181. -- fill transfer array
  182. data[#data+1] = {
  183. section = section,
  184. enabled = enabled,
  185. iface = iface,
  186. lookup = lookup_host,
  187. reg_ip = reg_ip,
  188. pid = pid,
  189. datelast = datelast,
  190. datenext = datenext
  191. }
  192. end)
  193. uci:unload("ddns")
  194. return data
  195. end
  196. -- called by XHR.get from detail_logview.htm
  197. function logread(section)
  198. -- read application settings
  199. local uci = UCI.cursor()
  200. local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns"
  201. local lfile = ldir .. "/" .. section .. ".log"
  202. local ldata = NXFS.readfile(lfile)
  203. if not ldata or #ldata == 0 then
  204. ldata="_nodata_"
  205. end
  206. uci:unload("ddns")
  207. HTTP.write(ldata)
  208. end
  209. -- called by XHR.get from overview_status.htm
  210. function startstop(section, enabled)
  211. local uci = UCI.cursor()
  212. local pid = DDNS.get_pid(section)
  213. local data = {} -- Array to transfer data to javascript
  214. -- if process running we want to stop and return
  215. if pid > 0 then
  216. local tmp = NX.kill(pid, 15) -- terminate
  217. NX.nanosleep(2) -- 2 second "show time"
  218. -- status changed so return full status
  219. data = _get_status()
  220. HTTP.prepare_content("application/json")
  221. HTTP.write_json(data)
  222. return
  223. end
  224. -- read uncommitted changes
  225. -- we don't save and commit data from other section or other options
  226. -- only enabled will be done
  227. local exec = true
  228. local changed = uci:changes("ddns")
  229. for k_config, v_section in pairs(changed) do
  230. -- security check because uci.changes only gets our config
  231. if k_config ~= "ddns" then
  232. exec = false
  233. break
  234. end
  235. for k_section, v_option in pairs(v_section) do
  236. -- check if only section of button was changed
  237. if k_section ~= section then
  238. exec = false
  239. break
  240. end
  241. for k_option, v_value in pairs(v_option) do
  242. -- check if only enabled was changed
  243. if k_option ~= "enabled" then
  244. exec = false
  245. break
  246. end
  247. end
  248. end
  249. end
  250. -- we can not execute because other
  251. -- uncommitted changes pending, so exit here
  252. if not exec then
  253. HTTP.write("_uncommitted_")
  254. return
  255. end
  256. -- save enable state
  257. uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") )
  258. uci:save("ddns")
  259. uci:commit("ddns")
  260. uci:unload("ddns")
  261. -- start ddns-updater for section
  262. local command = luci_helper .. [[ -S ]] .. section .. [[ -- start]]
  263. os.execute(command)
  264. NX.nanosleep(3) -- 3 seconds "show time"
  265. -- status changed so return full status
  266. data = _get_status()
  267. HTTP.prepare_content("application/json")
  268. HTTP.write_json(data)
  269. end
  270. -- called by XHR.poll from overview_status.htm
  271. function status()
  272. local data = _get_status()
  273. HTTP.prepare_content("application/json")
  274. HTTP.write_json(data)
  275. end