3 local json = require "luci.jsonc"
4 local nixio = require "nixio"
5 local fs = require "nixio.fs"
6 local UCI = require "luci.model.uci"
7 local sys = require "luci.sys"
8 local util = require "luci.util"
10 local luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
11 local srv_name = "ddns-scripts"
13 -- convert epoch date to given format
14 local function epoch2date(epoch, format)
15 if not format or #format < 2 then
16 local uci = UCI.cursor()
17 format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
20 format = format:gsub("%%n", "<br />") -- replace newline
21 format = format:gsub("%%t", " ") -- replace tab
22 return os.date(format, epoch)
25 -- function to calculate seconds from given interval and unit
26 local function calc_seconds(interval, unit)
27 if not tonumber(interval) then
29 elseif unit == "days" then
30 return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
31 elseif unit == "hours" then
32 return (tonumber(interval) * 3600) -- 60 sec * 60 min
33 elseif unit == "minutes" then
34 return (tonumber(interval) * 60) -- 60 sec
35 elseif unit == "seconds" then
36 return tonumber(interval)
44 args = { service_name = "service_name" },
46 local result = "File not found or empty"
47 local uci = UCI.cursor()
49 local dirlog = uci:get('ddns', 'global', 'ddns_logdir') or "/var/log/ddns"
51 -- Fallback to default logdir with unsecure path
52 if dirlog:match('%.%.%/') then dirlog = "/var/log/ddns" end
54 if args and args.service_name and fs.access("%s/%s.log" % { dirlog, args.service_name }) then
55 result = fs.readfile("%s/%s.log" % { dirlog, args.service_name })
60 return { result = result }
63 get_services_status = {
65 local uci = UCI.cursor()
67 local rundir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
68 local date_format = uci:get("ddns", "global", "ddns_dateformat")
71 uci:foreach("ddns", "service", function (s)
72 local ip, last_update, next_update
73 local section = s[".name"]
74 if fs.access("%s/%s.ip" % { rundir, section }) then
75 ip = fs.readfile("%s/%s.ip" % { rundir, section })
77 local dnsserver = s["dns_server"] or ""
78 local force_ipversion = tonumber(s["force_ipversion"] or 0)
79 local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
80 local is_glue = tonumber(s["is_glue"] or 0)
81 local command = { luci_helper , [[ -]] }
82 local lookup_host = s["lookup_host"] or "_nolookup_"
84 if (use_ipv6 == 1) then command[#command+1] = [[6]] end
85 if (force_ipversion == 1) then command[#command+1] = [[f]] end
86 if (force_dnstcp == 1) then command[#command+1] = [[t]] end
87 if (is_glue == 1) then command[#command+1] = [[g]] end
88 command[#command+1] = [[l ]]
89 command[#command+1] = lookup_host
90 command[#command+1] = [[ -S ]]
91 command[#command+1] = section
92 if (#dnsserver > 0) then command[#command+1] = [[ -d ]] .. dnsserver end
93 command[#command+1] = [[ -- get_registered_ip]]
94 line = util.exec(table.concat(command))
97 local last_update = tonumber(fs.readfile("%s/%s.update" % { rundir, section } ) or 0)
98 local next_update, converted_last_update
99 local pid = tonumber(fs.readfile("%s/%s.pid" % { rundir, section } ) or 0)
101 if pid > 0 and not nixio.kill(pid, 0) then
105 local uptime = sys.uptime()
107 local force_seconds = calc_seconds(
108 tonumber(s["force_interval"]) or 72,
109 s["force_unit"] or "hours" )
111 -- process running but update needs to happen
112 -- problems if force_seconds > uptime
113 force_seconds = (force_seconds > uptime) and uptime or force_seconds
115 if last_update > 0 then
116 local epoch = os.time() - uptime + last_update + force_seconds
117 -- use linux date to convert epoch
118 converted_last_update = epoch2date(epoch,date_format)
119 next_update = epoch2date(epoch + force_seconds)
122 if pid > 0 and ( last_update + force_seconds - uptime ) <= 0 then
123 next_update = "Verify"
126 elseif force_seconds == 0 then
127 next_update = "Run once"
129 -- no process running and NOT enabled
130 elseif pid == 0 and s['enabled'] == '0' then
131 next_update = "Disabled"
133 -- no process running and enabled
134 elseif pid == 0 and s['enabled'] ~= '0' then
135 next_update = "Stopped"
139 ip = ip and ip:gsub("\n","") or nil,
140 last_update = last_update ~= 0 and converted_last_update or nil,
141 next_update = next_update or nil,
155 local ipkg = require "luci.model.ipkg"
156 local uci = UCI.cursor()
157 local dateformat = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
159 local ver, srv_ver_cmd
163 ver = ipkg.info(srv_name)[srv_name].Version
165 srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
166 ver = util.exec(srv_ver_cmd)
169 res['_version'] = ver and #ver > 0 and ver or nil
170 res['_enabled'] = sys.init.enabled("ddns")
171 res['_curr_dateformat'] = os.date(dateformat)
181 local function has_wget()
182 return (sys.call( [[which wget >/dev/null 2>&1]] ) == 0)
185 local function has_wgetssl()
186 if cache['has_wgetssl'] then return cache['has_wgetssl'] end
187 local res = (sys.call( [[which wget-ssl >/dev/null 2>&1]] ) == 0)
188 cache['has_wgetssl'] = res
192 local function has_curlssl()
193 return (sys.call( [[$(which curl) -V 2>&1 | grep -qF "https"]] ) == 0)
196 local function has_fetch()
197 if cache['has_fetch'] then return cache['has_fetch'] end
198 local res = (sys.call( [[which uclient-fetch >/dev/null 2>&1]] ) == 0)
199 cache['has_fetch'] = res
203 local function has_fetchssl()
204 return fs.access("/lib/libustream-ssl.so")
207 local function has_curl()
208 if cache['has_curl'] then return cache['has_curl'] end
209 local res = (sys.call( [[which curl >/dev/null 2>&1]] ) == 0)
210 cache['has_curl'] = res
214 local function has_curlpxy()
215 return (sys.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
218 local function has_bbwget()
219 return (sys.call( [[$(which wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
222 res['has_wget'] = has_wget() or false
223 res['has_curl'] = has_curl() or false
225 res['has_ssl'] = has_wgetssl() or has_curlssl() or (has_fetch() and has_fetchssl()) or false
226 res['has_proxy'] = has_wgetssl() or has_curlpxy() or has_fetch() or has_bbwget or false
227 res['has_forceip'] = has_wgetssl() or has_curl() or has_fetch() or false
228 res['has_bindnet'] = has_curl() or has_wgetssl() or false
230 local function has_bindhost()
231 if (sys.call( [[which host >/dev/null 2>&1]] ) == 0) then return true end
232 if (sys.call( [[which khost >/dev/null 2>&1]] ) == 0) then return true end
233 if (sys.call( [[which drill >/dev/null 2>&1]] ) == 0) then return true end
237 local function has_hostip()
238 return (sys.call( [[which hostip >/dev/null 2>&1]] ) == 0)
241 local function has_nslookup()
242 return (sys.call( [[which nslookup >/dev/null 2>&1]] ) == 0)
245 res['has_dnsserver'] = has_bindhost() or has_hostip() or has_nslookup() or false
247 local function check_certs()
248 local _, v = fs.glob("/etc/ssl/certs/*.crt")
249 if ( v == 0 ) then _, v = fs.glob("/etc/ssl/certs/*.pem") end
253 res['has_cacerts'] = check_certs() or false
255 res['has_ipv6'] = (fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables"))
262 local function parseInput()
263 local parse = json.new()
267 local chunk = io.read(4096)
270 elseif not done and not err then
271 done, err = parse:parse(chunk)
276 print(json.stringify({ error = err or "Incomplete input" }))
283 local function validateArgs(func, uargs)
284 local method = methods[func]
286 print(json.stringify({ error = "Method not found" }))
290 if type(uargs) ~= "table" then
291 print(json.stringify({ error = "Invalid arguments" }))
295 uargs.ubus_rpc_session = nil
298 local margs = method.args or {}
299 for k, v in pairs(uargs) do
300 if margs[k] == nil or
301 (v ~= nil and type(v) ~= type(margs[k]))
303 print(json.stringify({ error = "Invalid arguments" }))
311 if arg[1] == "list" then
312 local _, method, rv = nil, nil, {}
313 for _, method in pairs(methods) do rv[_] = method.args or {} end
314 print((json.stringify(rv):gsub(":%[%]", ":{}")))
315 elseif arg[1] == "call" then
316 local args = parseInput()
317 local method = validateArgs(arg[2], args)
318 local result, code = method.call(args)
319 print((json.stringify(result):gsub("^%[%]$", "{}")))