Every request directed to the ddns app call ddns tools module. Ddns tools module have lots of global variable that call slow os.execute function. This adds 10 second to every ddns request even if the function that is requested doesn't need that global variable. This commit introduce env_info function that execute os.execute command by executing what is actually requested and not process all the variables. Also remove 2 unecessary module that are not used. More researh find that major slowdown was caused by the calling of ddns script for the version check. Now we check if opkg is present and use it to check ddns-scripts version. Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
430 lines
13 KiB
Lua
Executable file
430 lines
13 KiB
Lua
Executable file
-- Copyright 2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
|
|
-- Licensed to the public under the Apache License 2.0.
|
|
|
|
module("luci.tools.ddns", package.seeall)
|
|
|
|
local NX = require "nixio"
|
|
local NXFS = require "nixio.fs"
|
|
local UCI = require "luci.model.uci"
|
|
local SYS = require "luci.sys"
|
|
|
|
function env_info(type)
|
|
|
|
if ( type == "has_ssl" ) or ( type == "has_proxy" ) or ( type == "has_forceip" )
|
|
or ( type == "has_bindnet" ) or ( type == "has_fetch" )
|
|
or ( type == "has_wgetssl" ) or ( type == "has_curl" )
|
|
or ( type == "has_curlssl" ) or ( type == "has_curlpxy" )
|
|
or ( type == "has_fetchssl" ) or ( type == "has_bbwget" ) then
|
|
|
|
local function has_wgetssl()
|
|
return (SYS.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0) -- and true or nil
|
|
end
|
|
|
|
local function has_curlssl()
|
|
return (SYS.call( [[$(which curl) -V 2>&1 | grep "Protocols:" | grep -qF "https"]] ) ~= 0)
|
|
end
|
|
|
|
local function has_fetch()
|
|
return (SYS.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
|
|
end
|
|
|
|
local function has_fetchssl()
|
|
return NXFS.access("/lib/libustream-ssl.so")
|
|
end
|
|
|
|
local function has_curl()
|
|
return (SYS.call( [[which curl >/dev/null 2>&1]] ) == 0)
|
|
end
|
|
|
|
local function has_curlpxy()
|
|
return (SYS.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
|
|
end
|
|
|
|
local function has_bbwget()
|
|
return (SYS.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
|
|
end
|
|
|
|
if type == "has_wgetssl" then
|
|
return has_wgetssl()
|
|
|
|
elseif type == "has_curl" then
|
|
return has_curl()
|
|
|
|
elseif type == "has_curlssl" then
|
|
return has_curlssl()
|
|
|
|
elseif type == "has_curlpxy" then
|
|
return has_curlpxy()
|
|
|
|
elseif type == "has_fetch" then
|
|
return has_fetch()
|
|
|
|
elseif type == "has_fetchssl" then
|
|
return has_fetchssl()
|
|
|
|
elseif type == "has_bbwget" then
|
|
return has_bbwget()
|
|
|
|
elseif type == "has_ssl" then
|
|
if has_wgetssl() then return true end
|
|
if has_curlssl() then return true end
|
|
if (has_fetch() and has_fetchssl()) then return true end
|
|
return false
|
|
|
|
elseif type == "has_proxy" then
|
|
if has_wgetssl() then return true end
|
|
if has_curlpxy() then return true end
|
|
if has_fetch() then return true end
|
|
if has_bbwget() then return true end
|
|
return false
|
|
|
|
elseif type == "has_forceip" then
|
|
if has_wgetssl() then return true end
|
|
if has_curl() then return true end
|
|
if has_fetch() then return true end -- only really needed for transfer
|
|
return false
|
|
|
|
elseif type == "has_bindnet" then
|
|
if has_curl() then return true end
|
|
if has_wgetssl() then return true end
|
|
return false
|
|
end
|
|
|
|
elseif ( type == "has_dnsserver" ) or ( type == "has_bindhost" ) or ( type == "has_hostip" ) or ( type == "has_nslookup" ) then
|
|
local function has_bindhost()
|
|
if (SYS.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
|
|
if (SYS.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
|
|
if (SYS.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
|
|
if (SYS.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
|
|
return false
|
|
end
|
|
|
|
local function has_hostip()
|
|
return (SYS.call( [[which hostip >/dev/null 2>&1]] ) == 0)
|
|
end
|
|
|
|
local function has_nslookup()
|
|
return (SYS.call( [[$(which nslookup) localhost 2>&1 | grep -qF "(null)"]] ) ~= 0)
|
|
end
|
|
|
|
if type == "has_bindhost" then
|
|
return has_bindhost()
|
|
elseif type == "has_hostip" then
|
|
return has_hostip()
|
|
elseif type == "has_nslookup" then
|
|
return has_nslookup()
|
|
elseif tyep == "has_dnsserver" then
|
|
if has_bindhost() then return true end
|
|
if has_hostip() then return true end
|
|
if has_nslookup() then return true end
|
|
return false
|
|
end
|
|
|
|
elseif type == "has_ipv6" then
|
|
return (NXFS.access("/proc/net/ipv6_route") and NXFS.access("/usr/sbin/ip6tables"))
|
|
|
|
elseif type == "has_cacerts" then
|
|
--old _check_certs() local function
|
|
local _, v = NXFS.glob("/etc/ssl/certs/*.crt")
|
|
if ( v == 0 ) then _, v = NXFS.glob("/etc/ssl/certs/*.pem") end
|
|
return (v > 0)
|
|
else
|
|
return
|
|
end
|
|
|
|
end
|
|
|
|
-- function to calculate seconds from given interval and unit
|
|
function calc_seconds(interval, unit)
|
|
if not tonumber(interval) then
|
|
return nil
|
|
elseif unit == "days" then
|
|
return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
|
|
elseif unit == "hours" then
|
|
return (tonumber(interval) * 3600) -- 60 sec * 60 min
|
|
elseif unit == "minutes" then
|
|
return (tonumber(interval) * 60) -- 60 sec
|
|
elseif unit == "seconds" then
|
|
return tonumber(interval)
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- convert epoch date to given format
|
|
function epoch2date(epoch, format)
|
|
if not format or #format < 2 then
|
|
local uci = UCI.cursor()
|
|
format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
|
|
uci:unload("ddns")
|
|
end
|
|
format = format:gsub("%%n", "<br />") -- replace newline
|
|
format = format:gsub("%%t", " ") -- replace tab
|
|
return os.date(format, epoch)
|
|
end
|
|
|
|
-- read lastupdate from [section].update file
|
|
function get_lastupd(section)
|
|
local uci = UCI.cursor()
|
|
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
|
local etime = tonumber(NXFS.readfile("%s/%s.update" % { rdir, section } ) or 0 )
|
|
uci:unload("ddns")
|
|
return etime
|
|
end
|
|
|
|
-- read registered IP from [section].ip file
|
|
function get_regip(section, chk_sec)
|
|
local uci = UCI.cursor()
|
|
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
|
local ip = "NOFILE"
|
|
if NXFS.access("%s/%s.ip" % { rdir, section }) then
|
|
local ftime = NXFS.stat("%s/%s.ip" % { rdir, section }, "ctime") or 0
|
|
local otime = os.time()
|
|
-- give ddns-scripts time (9 sec) to update file
|
|
if otime < (ftime + chk_sec + 9) then
|
|
ip = NXFS.readfile("%s/%s.ip" % { rdir, section })
|
|
end
|
|
end
|
|
uci:unload("ddns")
|
|
return ip
|
|
end
|
|
|
|
-- read PID from run file and verify if still running
|
|
function get_pid(section)
|
|
local uci = UCI.cursor()
|
|
local rdir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
|
|
local pid = tonumber(NXFS.readfile("%s/%s.pid" % { rdir, section } ) or 0 )
|
|
if pid > 0 and not NX.kill(pid, 0) then
|
|
pid = 0
|
|
end
|
|
uci:unload("ddns")
|
|
return pid
|
|
end
|
|
|
|
-- replacement of build-in read of UCI option
|
|
-- modified AbstractValue.cfgvalue(self, section) from cbi.lua
|
|
-- needed to read from other option then current value definition
|
|
function read_value(self, section, option)
|
|
local value
|
|
if self.tag_error[section] then
|
|
value = self:formvalue(section)
|
|
else
|
|
value = self.map:get(section, option)
|
|
end
|
|
|
|
if not value then
|
|
return nil
|
|
elseif not self.cast or self.cast == type(value) then
|
|
return value
|
|
elseif self.cast == "string" then
|
|
if type(value) == "table" then
|
|
return value[1]
|
|
end
|
|
elseif self.cast == "table" then
|
|
return { value }
|
|
end
|
|
end
|
|
|
|
-- replacement of build-in parse of "Value"
|
|
-- modified AbstractValue.parse(self, section, novld) from cbi.lua
|
|
-- validate is called if rmempty/optional true or false
|
|
-- before write check if forcewrite, value eq default, and more
|
|
function value_parse(self, section, novld)
|
|
local fvalue = self:formvalue(section)
|
|
local fexist = ( fvalue and (#fvalue > 0) ) -- not "nil" and "not empty"
|
|
local cvalue = self:cfgvalue(section)
|
|
local rm_opt = ( self.rmempty or self.optional )
|
|
local eq_cfg -- flag: equal cfgvalue
|
|
|
|
-- If favlue and cvalue are both tables and have the same content
|
|
-- make them identical
|
|
if type(fvalue) == "table" and type(cvalue) == "table" then
|
|
eq_cfg = (#fvalue == #cvalue)
|
|
if eq_cfg then
|
|
for i=1, #fvalue do
|
|
if cvalue[i] ~= fvalue[i] then
|
|
eq_cfg = false
|
|
end
|
|
end
|
|
end
|
|
if eq_cfg then
|
|
fvalue = cvalue
|
|
end
|
|
end
|
|
|
|
-- removed parameter "section" from function call because used/accepted nowhere
|
|
-- also removed call to function "transfer"
|
|
local vvalue, errtxt = self:validate(fvalue)
|
|
|
|
-- error handling; validate return "nil"
|
|
if not vvalue then
|
|
if novld then -- and "novld" set
|
|
return -- then exit without raising an error
|
|
end
|
|
|
|
if fexist then -- and there is a formvalue
|
|
self:add_error(section, "invalid", errtxt or self.title .. ": invalid")
|
|
return -- so data are invalid
|
|
elseif not rm_opt then -- and empty formvalue but NOT (rmempty or optional) set
|
|
self:add_error(section, "missing", errtxt or self.title .. ": missing")
|
|
return -- so data is missing
|
|
elseif errtxt then
|
|
self:add_error(section, "invalid", errtxt)
|
|
return
|
|
end
|
|
-- error ("\n option: " .. self.option ..
|
|
-- "\n fvalue: " .. tostring(fvalue) ..
|
|
-- "\n fexist: " .. tostring(fexist) ..
|
|
-- "\n cvalue: " .. tostring(cvalue) ..
|
|
-- "\n vvalue: " .. tostring(vvalue) ..
|
|
-- "\n vexist: " .. tostring(vexist) ..
|
|
-- "\n rm_opt: " .. tostring(rm_opt) ..
|
|
-- "\n eq_cfg: " .. tostring(eq_cfg) ..
|
|
-- "\n eq_def: " .. tostring(eq_def) ..
|
|
-- "\n novld : " .. tostring(novld) ..
|
|
-- "\n errtxt: " .. tostring(errtxt) )
|
|
end
|
|
|
|
-- lets continue with value returned from validate
|
|
eq_cfg = ( vvalue == cvalue ) -- update equal_config flag
|
|
local vexist = ( vvalue and (#vvalue > 0) ) and true or false -- not "nil" and "not empty"
|
|
local eq_def = ( vvalue == self.default ) -- equal_default flag
|
|
|
|
-- (rmempty or optional) and (no data or equal_default)
|
|
if rm_opt and (not vexist or eq_def) then
|
|
if self:remove(section) then -- remove data from UCI
|
|
self.section.changed = true -- and push events
|
|
end
|
|
return
|
|
end
|
|
|
|
-- not forcewrite and no changes, so nothing to write
|
|
if not self.forcewrite and eq_cfg then
|
|
return
|
|
end
|
|
|
|
-- we should have a valid value here
|
|
assert (vvalue, "\n option: " .. self.option ..
|
|
"\n fvalue: " .. tostring(fvalue) ..
|
|
"\n fexist: " .. tostring(fexist) ..
|
|
"\n cvalue: " .. tostring(cvalue) ..
|
|
"\n vvalue: " .. tostring(vvalue) ..
|
|
"\n vexist: " .. tostring(vexist) ..
|
|
"\n rm_opt: " .. tostring(rm_opt) ..
|
|
"\n eq_cfg: " .. tostring(eq_cfg) ..
|
|
"\n eq_def: " .. tostring(eq_def) ..
|
|
"\n errtxt: " .. tostring(errtxt) )
|
|
|
|
-- write data to UCI; raise event only on changes
|
|
if self:write(section, vvalue) and not eq_cfg then
|
|
self.section.changed = true
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- copied from https://svn.nmap.org/nmap/nselib/url.lua
|
|
-- @author Diego Nehab
|
|
-- @author Eddie Bell <ejlbell@gmail.com>
|
|
--[[
|
|
URI parsing, composition and relative URL resolution
|
|
LuaSocket toolkit.
|
|
Author: Diego Nehab
|
|
RCS ID: $Id: url.lua,v 1.37 2005/11/22 08:33:29 diego Exp $
|
|
parse_query and build_query added For nmap (Eddie Bell <ejlbell@gmail.com>)
|
|
]]--
|
|
---
|
|
-- Parses a URL and returns a table with all its parts according to RFC 2396.
|
|
--
|
|
-- The following grammar describes the names given to the URL parts.
|
|
-- <code>
|
|
-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>
|
|
-- <authority> ::= <userinfo>@<host>:<port>
|
|
-- <userinfo> ::= <user>[:<password>]
|
|
-- <path> :: = {<segment>/}<segment>
|
|
-- </code>
|
|
--
|
|
-- The leading <code>/</code> in <code>/<path></code> is considered part of
|
|
-- <code><path></code>.
|
|
-- @param url URL of request.
|
|
-- @param default Table with default values for each field.
|
|
-- @return A table with the following fields, where RFC naming conventions have
|
|
-- been preserved:
|
|
-- <code>scheme</code>, <code>authority</code>, <code>userinfo</code>,
|
|
-- <code>user</code>, <code>password</code>, <code>host</code>,
|
|
-- <code>port</code>, <code>path</code>, <code>params</code>,
|
|
-- <code>query</code>, and <code>fragment</code>.
|
|
-----------------------------------------------------------------------------
|
|
function parse_url(url) --, default)
|
|
-- initialize default parameters
|
|
local parsed = {}
|
|
-- for i,v in base.pairs(default or parsed) do
|
|
-- parsed[i] = v
|
|
-- end
|
|
|
|
-- remove whitespace
|
|
-- url = string.gsub(url, "%s", "")
|
|
-- get fragment
|
|
url = string.gsub(url, "#(.*)$",
|
|
function(f)
|
|
parsed.fragment = f
|
|
return ""
|
|
end)
|
|
-- get scheme. Lower-case according to RFC 3986 section 3.1.
|
|
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
|
|
function(s)
|
|
parsed.scheme = string.lower(s);
|
|
return ""
|
|
end)
|
|
-- get authority
|
|
url = string.gsub(url, "^//([^/]*)",
|
|
function(n)
|
|
parsed.authority = n
|
|
return ""
|
|
end)
|
|
-- get query stringing
|
|
url = string.gsub(url, "%?(.*)",
|
|
function(q)
|
|
parsed.query = q
|
|
return ""
|
|
end)
|
|
-- get params
|
|
url = string.gsub(url, "%;(.*)",
|
|
function(p)
|
|
parsed.params = p
|
|
return ""
|
|
end)
|
|
-- path is whatever was left
|
|
parsed.path = url
|
|
|
|
local authority = parsed.authority
|
|
if not authority then
|
|
return parsed
|
|
end
|
|
authority = string.gsub(authority,"^([^@]*)@",
|
|
function(u)
|
|
parsed.userinfo = u;
|
|
return ""
|
|
end)
|
|
authority = string.gsub(authority, ":([0-9]*)$",
|
|
function(p)
|
|
if p ~= "" then
|
|
parsed.port = p
|
|
end;
|
|
return ""
|
|
end)
|
|
if authority ~= "" then
|
|
parsed.host = authority
|
|
end
|
|
|
|
local userinfo = parsed.userinfo
|
|
if not userinfo then
|
|
return parsed
|
|
end
|
|
userinfo = string.gsub(userinfo, ":([^:]*)$",
|
|
function(p)
|
|
parsed.password = p;
|
|
return ""
|
|
end)
|
|
parsed.user = userinfo
|
|
return parsed
|
|
end
|