-- Copyright 2008 Steven Barth <steven@midlink.org> -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> -- Copyright 2013 Manuel Munz <freifunk at somakoma dot de> -- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com> -- Licensed to the public under the Apache License 2.0. module("luci.controller.ddns", package.seeall) local NX = require "nixio" local NXFS = require "nixio.fs" local DISP = require "luci.dispatcher" local HTTP = require "luci.http" local I18N = require "luci.i18n" -- not globally avalible here local IPKG = require "luci.model.ipkg" local SYS = require "luci.sys" local UCI = require "luci.model.uci" local UTIL = require "luci.util" local DDNS = require "luci.tools.ddns" -- ddns multiused functions luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh" local srv_name = "ddns-scripts" local srv_ver_min = "2.7.7" -- minimum version of service required local app_name = "luci-app-ddns" local app_title = "Dynamic DNS" local app_version = "2.4.9-1" local translate = I18N.translate function index() local nxfs = require "nixio.fs" -- global definitions not available local sys = require "luci.sys" -- in function index() local muci = require "luci.model.uci" -- no config create an empty one if not nxfs.access("/etc/config/ddns") then nxfs.writefile("/etc/config/ddns", "") end -- preset new option "lookup_host" if not already defined local uci = muci.cursor() local commit = false uci:foreach("ddns", "service", function (s) if not s["lookup_host"] and s["domain"] then uci:set("ddns", s[".name"], "lookup_host", s["domain"]) commit = true end end) if commit then uci:commit("ddns") end uci:unload("ddns") entry( {"admin", "services", "ddns"}, cbi("ddns/overview"), _("Dynamic DNS"), 59) entry( {"admin", "services", "ddns", "detail"}, cbi("ddns/detail"), nil ).leaf = true entry( {"admin", "services", "ddns", "hints"}, cbi("ddns/hints", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil ).leaf = true entry( {"admin", "services", "ddns", "global"}, cbi("ddns/global"), nil ).leaf = true entry( {"admin", "services", "ddns", "logview"}, call("logread") ).leaf = true entry( {"admin", "services", "ddns", "startstop"}, post("startstop") ).leaf = true entry( {"admin", "services", "ddns", "status"}, call("status") ).leaf = true end -- Application specific information functions function app_description() local tmp = {} tmp[#tmp+1] = translate("Dynamic DNS allows that your router can be reached with \ a fixed hostname while having a dynamically changing IP address.") tmp[#tmp+1] = [[<br />]] tmp[#tmp+1] = translate("OpenWrt Wiki") .. ": " tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/services/ddns/client" target="_blank">]] tmp[#tmp+1] = translate("DDNS Client Documentation") tmp[#tmp+1] = [[</a>]] tmp[#tmp+1] = " --- " tmp[#tmp+1] = [[<a href="https://openwrt.org/docs/guide-user/base-system/ddns" target="_blank">]] tmp[#tmp+1] = translate("DDNS Client Configuration") tmp[#tmp+1] = [[</a>]] return table.concat(tmp) end function app_title_back() local tmp = {} tmp[#tmp+1] = [[<a href="]] tmp[#tmp+1] = DISP.build_url("admin", "services", "ddns") tmp[#tmp+1] = [[">]] tmp[#tmp+1] = translate(app_title) tmp[#tmp+1] = [[</a>]] return table.concat(tmp) end -- Standardized application/service functions function app_title_main() local tmp = {} tmp[#tmp+1] = [[<a href="javascript:alert(']] tmp[#tmp+1] = translate("Version Information") tmp[#tmp+1] = [[\n\n]] .. app_name tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]] .. app_version tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("required") .. [[:]] tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]] tmp[#tmp+1] = srv_ver_min .. [[ ]] .. translate("or higher") tmp[#tmp+1] = [[\n\n]] .. srv_name .. [[ ]] .. translate("installed") .. [[:]] tmp[#tmp+1] = [[\n]] .. translate("Version") .. [[: ]] tmp[#tmp+1] = (service_version() or translate("NOT installed")) tmp[#tmp+1] = [[\n\n]] tmp[#tmp+1] = [[')">]] tmp[#tmp+1] = translate(app_title) tmp[#tmp+1] = [[</a>]] return table.concat(tmp) end function service_version() local srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} " local ver if IPKG then ver = IPKG.info(srv_name)[srv_name].Version else ver = UTIL.exec(srv_ver_cmd) end if ver and #ver > 0 then return ver or nil end end function service_ok() return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min) end -- internal function to read all sections status and return data array local function _get_status() local uci = UCI.cursor() local service = SYS.init.enabled("ddns") and 1 or 0 local url_start = DISP.build_url("admin", "system", "startup") local data = {} -- Array to transfer data to javascript data[#data+1] = { enabled = service, -- service enabled url_up = url_start, -- link to enable DDS (System-Startup) } uci:foreach("ddns", "service", function (s) -- Get section we are looking at -- and enabled state local section = s[".name"] local enabled = tonumber(s["enabled"]) or 0 local datelast = "_empty_" -- formatted date of last update local datenext = "_empty_" -- formatted date of next update local datenextstat = nil -- get force seconds local force_seconds = DDNS.calc_seconds( tonumber(s["force_interval"]) or 72 , s["force_unit"] or "hours" ) -- get/validate pid and last update local pid = DDNS.get_pid(section) local uptime = SYS.uptime() local lasttime = DDNS.get_lastupd(section) if lasttime > uptime then -- /var might not be linked to /tmp lasttime = 0 -- and/or not cleared on reboot end -- no last update happen if lasttime == 0 then datelast = "_never_" -- we read last update else -- calc last update -- sys.epoch - sys uptime + lastupdate(uptime) local epoch = os.time() - uptime + lasttime -- use linux date to convert epoch datelast = DDNS.epoch2date(epoch) -- calc and fill next update datenext = DDNS.epoch2date(epoch + force_seconds) end -- process running but update needs to happen -- problems if force_seconds > uptime force_seconds = (force_seconds > uptime) and uptime or force_seconds if pid > 0 and ( lasttime + force_seconds - uptime ) <= 0 then datenext = "_verify_" datenextstat = translate("Verify") -- run once elseif force_seconds == 0 then datenext = "_runonce_" datenextstat = translate("Run once") -- no process running and NOT enabled elseif pid == 0 and enabled == 0 then datenext = "_disabled_" datenextstat = translate("Disabled") -- no process running and enabled elseif pid == 0 and enabled ~= 0 then datenext = "_stopped_" datenextstat = translate("Stopped") end -- get/set monitored interface and IP version local iface = s["interface"] or "wan" local use_ipv6 = tonumber(s["use_ipv6"]) or 0 local ipv = (use_ipv6 == 1) and "IPv6" or "IPv4" iface = ipv .. " / " .. iface -- try to get registered IP local lookup_host = s["lookup_host"] or "_nolookup_" local chk_sec = DDNS.calc_seconds( tonumber(s["check_interval"]) or 10, s["check_unit"] or "minutes" ) local reg_ip = DDNS.get_regip(section, chk_sec) if reg_ip == "NOFILE" then local dnsserver = s["dns_server"] or "" local force_ipversion = tonumber(s["force_ipversion"] or 0) local force_dnstcp = tonumber(s["force_dnstcp"] or 0) local is_glue = tonumber(s["is_glue"] or 0) local command = luci_helper .. [[ -]] if (use_ipv6 == 1) then command = command .. [[6]] end if (force_ipversion == 1) then command = command .. [[f]] end if (force_dnstcp == 1) then command = command .. [[t]] end if (is_glue == 1) then command = command .. [[g]] end command = command .. [[l ]] .. lookup_host command = command .. [[ -S ]] .. section if (#dnsserver > 0) then command = command .. [[ -d ]] .. dnsserver end command = command .. [[ -- get_registered_ip]] reg_ip = SYS.exec(command) end -- fill transfer array data[#data+1] = { section = section, enabled = enabled, iface = iface, lookup = lookup_host, reg_ip = reg_ip, pid = pid, datelast = datelast, datenext = datenext, datenextstat = datenextstat } end) uci:unload("ddns") return data end -- called by XHR.get from detail_logview.htm function logread(section) -- read application settings local uci = UCI.cursor() local ldir = uci:get("ddns", "global", "ddns_logdir") or "/var/log/ddns" local lfile = ldir .. "/" .. section .. ".log" local ldata = NXFS.readfile(lfile) if not ldata or #ldata == 0 then ldata="_nodata_" end uci:unload("ddns") HTTP.write(ldata) end -- called by XHR.get from overview_status.htm function startstop(section, enabled) local uci = UCI.cursor() local pid = DDNS.get_pid(section) local data = {} -- Array to transfer data to javascript -- if process running we want to stop and return if pid > 0 then local tmp = NX.kill(pid, 15) -- terminate NX.nanosleep(2) -- 2 second "show time" -- status changed so return full status data = _get_status() HTTP.prepare_content("application/json") HTTP.write_json(data) return end -- read uncommitted changes -- we don't save and commit data from other section or other options -- only enabled will be done local exec = true local changed = uci:changes("ddns") for k_config, v_section in pairs(changed) do -- security check because uci.changes only gets our config if k_config ~= "ddns" then exec = false break end for k_section, v_option in pairs(v_section) do -- check if only section of button was changed if k_section ~= section then exec = false break end for k_option, v_value in pairs(v_option) do -- check if only enabled was changed if k_option ~= "enabled" then exec = false break end end end end -- we can not execute because other -- uncommitted changes pending, so exit here if not exec then HTTP.write("_uncommitted_") return end -- save enable state uci:set("ddns", section, "enabled", ( (enabled == "true") and "1" or "0") ) uci:save("ddns") uci:commit("ddns") uci:unload("ddns") -- start ddns-updater for section local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) } os.execute(command) NX.nanosleep(3) -- 3 seconds "show time" -- status changed so return full status data = _get_status() HTTP.prepare_content("application/json") HTTP.write_json(data) end -- called by XHR.poll from overview_status.htm function status() local data = _get_status() HTTP.prepare_content("application/json") HTTP.write_json(data) end