653e63c16ad1cf5f4783b2856584164c82a55261
[project/luci.git] / applications / luci-app-ddns / root / usr / libexec / rpcd / luci.ddns
1 #!/usr/bin/env lua
2
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"
9
10 local ddns_package_path = "/usr/share/ddns"
11 local luci_helper = "/usr/lib/ddns/dynamic_dns_lucihelper.sh"
12 local srv_name = "ddns-scripts"
13
14 -- convert epoch date to given format
15 local function epoch2date(epoch, format)
16 if not format or #format < 2 then
17 local uci = UCI.cursor()
18 format = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
19 uci:unload("ddns")
20 end
21 format = format:gsub("%%n", "<br />") -- replace newline
22 format = format:gsub("%%t", " ") -- replace tab
23 return os.date(format, epoch)
24 end
25
26 -- function to calculate seconds from given interval and unit
27 local function calc_seconds(interval, unit)
28 if not tonumber(interval) then
29 return nil
30 elseif unit == "days" then
31 return (tonumber(interval) * 86400) -- 60 sec * 60 min * 24 h
32 elseif unit == "hours" then
33 return (tonumber(interval) * 3600) -- 60 sec * 60 min
34 elseif unit == "minutes" then
35 return (tonumber(interval) * 60) -- 60 sec
36 elseif unit == "seconds" then
37 return tonumber(interval)
38 else
39 return nil
40 end
41 end
42
43 local methods = {
44 get_services_log = {
45 args = { service_name = "service_name" },
46 call = function(args)
47 local result = "File not found or empty"
48 local uci = UCI.cursor()
49
50 local dirlog = uci:get('ddns', 'global', 'ddns_logdir') or "/var/log/ddns"
51
52 -- Fallback to default logdir with unsecure path
53 if dirlog:match('%.%.%/') then dirlog = "/var/log/ddns" end
54
55 if args and args.service_name and fs.access("%s/%s.log" % { dirlog, args.service_name }) then
56 result = fs.readfile("%s/%s.log" % { dirlog, args.service_name })
57 end
58
59 uci.unload()
60
61 return { result = result }
62 end
63 },
64 get_services_status = {
65 call = function()
66 local uci = UCI.cursor()
67
68 local rundir = uci:get("ddns", "global", "ddns_rundir") or "/var/run/ddns"
69 local date_format = uci:get("ddns", "global", "ddns_dateformat")
70 local res = {}
71
72 uci:foreach("ddns", "service", function (s)
73 local ip, last_update, next_update
74 local section = s[".name"]
75 if fs.access("%s/%s.ip" % { rundir, section }) then
76 ip = fs.readfile("%s/%s.ip" % { rundir, section })
77 else
78 local dnsserver = s["dns_server"] or ""
79 local force_ipversion = tonumber(s["force_ipversion"] or 0)
80 local force_dnstcp = tonumber(s["force_dnstcp"] or 0)
81 local is_glue = tonumber(s["is_glue"] or 0)
82 local command = { luci_helper , [[ -]] }
83 local lookup_host = s["lookup_host"] or "_nolookup_"
84
85 if (use_ipv6 == 1) then command[#command+1] = [[6]] end
86 if (force_ipversion == 1) then command[#command+1] = [[f]] end
87 if (force_dnstcp == 1) then command[#command+1] = [[t]] end
88 if (is_glue == 1) then command[#command+1] = [[g]] end
89 command[#command+1] = [[l ]]
90 command[#command+1] = lookup_host
91 command[#command+1] = [[ -S ]]
92 command[#command+1] = section
93 if (#dnsserver > 0) then command[#command+1] = [[ -d ]] .. dnsserver end
94 command[#command+1] = [[ -- get_registered_ip]]
95 line = util.exec(table.concat(command))
96 end
97
98 local last_update = tonumber(fs.readfile("%s/%s.update" % { rundir, section } ) or 0)
99 local next_update, converted_last_update
100 local pid = tonumber(fs.readfile("%s/%s.pid" % { rundir, section } ) or 0)
101
102 if pid > 0 and not nixio.kill(pid, 0) then
103 pid = 0
104 end
105
106 local uptime = sys.uptime()
107
108 local force_seconds = calc_seconds(
109 tonumber(s["force_interval"]) or 72,
110 s["force_unit"] or "hours" )
111
112 local check_seconds = calc_seconds(
113 tonumber(s["check_interval"]) or 10,
114 s["check_unit"] or "minutes" )
115
116 if last_update > 0 then
117 local epoch = os.time() - uptime + last_update
118 -- use linux date to convert epoch
119 converted_last_update = epoch2date(epoch,date_format)
120 next_update = epoch2date(epoch + force_seconds + check_seconds)
121 end
122
123 if pid > 0 and ( last_update + force_seconds + check_seconds - uptime ) <= 0 then
124 next_update = "Verify"
125
126 -- run once
127 elseif force_seconds == 0 then
128 next_update = "Run once"
129
130 -- no process running and NOT enabled
131 elseif pid == 0 and s['enabled'] == '0' then
132 next_update = "Disabled"
133
134 -- no process running and enabled
135 elseif pid == 0 and s['enabled'] ~= '0' then
136 next_update = "Stopped"
137 end
138
139 res[section] = {
140 ip = ip and ip:gsub("\n","") or nil,
141 last_update = last_update ~= 0 and converted_last_update or nil,
142 next_update = next_update or nil,
143 pid = pid or nil,
144 }
145 end
146 )
147
148 uci:unload("ddns")
149
150 return res
151
152 end
153 },
154 get_ddns_state = {
155 call = function()
156 local ipkg = require "luci.model.ipkg"
157 local uci = UCI.cursor()
158 local dateformat = uci:get("ddns", "global", "ddns_dateformat") or "%F %R"
159 local services_mtime = fs.stat(ddns_package_path .. "/list", 'mtime')
160 uci:unload("ddns")
161 local ver, srv_ver_cmd
162 local res = {}
163
164 if ipkg then
165 ver = ipkg.info(srv_name)[srv_name].Version
166 else
167 srv_ver_cmd = luci_helper .. " -V | awk {'print $2'} "
168 ver = util.exec(srv_ver_cmd)
169 end
170
171 res['_version'] = ver and #ver > 0 and ver or nil
172 res['_enabled'] = sys.init.enabled("ddns")
173 res['_curr_dateformat'] = os.date(dateformat)
174 res['_services_list'] = services_mtime and os.date(dateformat, services_mtime) or 'NO_LIST'
175
176 return res
177 end
178 },
179 get_env = {
180 call = function()
181 local res = {}
182 local cache = {}
183
184 local function has_wget()
185 return (sys.call( [[command -v wget >/dev/null 2>&1]] ) == 0)
186 end
187
188 local function has_wgetssl()
189 if cache['has_wgetssl'] then return cache['has_wgetssl'] end
190 local res = has_wget() and (sys.call( [[wget --version | grep -qF +https >/dev/null 2>&1]] ) == 0)
191 cache['has_wgetssl'] = res
192 return res
193 end
194
195 local function has_curlssl()
196 return (sys.call( [[$(command -v curl) -V 2>&1 | grep -qF "https"]] ) == 0)
197 end
198
199 local function has_fetch()
200 if cache['has_fetch'] then return cache['has_fetch'] end
201 local res = (sys.call( [[command -v uclient-fetch >/dev/null 2>&1]] ) == 0)
202 cache['has_fetch'] = res
203 return res
204 end
205
206 local function has_fetchssl()
207 return fs.access("/lib/libustream-ssl.so")
208 end
209
210 local function has_curl()
211 if cache['has_curl'] then return cache['has_curl'] end
212 local res = (sys.call( [[command -v curl >/dev/null 2>&1]] ) == 0)
213 cache['has_curl'] = res
214 return res
215 end
216
217 local function has_curlpxy()
218 return (sys.call( [[grep -i "all_proxy" /usr/lib/libcurl.so* >/dev/null 2>&1]] ) == 0)
219 end
220
221 local function has_bbwget()
222 return (sys.call( [[$(command -v wget) -V 2>&1 | grep -iqF "busybox"]] ) == 0)
223 end
224
225 res['has_wget'] = has_wget() or false
226 res['has_curl'] = has_curl() or false
227
228 res['has_ssl'] = has_wgetssl() or has_curlssl() or (has_fetch() and has_fetchssl()) or false
229 res['has_proxy'] = has_wgetssl() or has_curlpxy() or has_fetch() or has_bbwget or false
230 res['has_forceip'] = has_wgetssl() or has_curl() or has_fetch() or false
231 res['has_bindnet'] = has_curl() or has_wgetssl() or false
232
233 local function has_bindhost()
234 if cache['has_bindhost'] then return cache['has_bindhost'] end
235 local res = (sys.call( [[command -v host >/dev/null 2>&1]] ) == 0)
236 if res then
237 cache['has_bindhost'] = res
238 return true
239 end
240 res = (sys.call( [[command -v khost >/dev/null 2>&1]] ) == 0)
241 if res then
242 cache['has_bindhost'] = res
243 return true
244 end
245 res = (sys.call( [[command -v drill >/dev/null 2>&1]] ) == 0)
246 if res then
247 cache['has_bindhost'] = res
248 return true
249 end
250 cache['has_bindhost'] = false
251 return false
252 end
253
254 res['has_bindhost'] = cache['has_bindhost'] or has_bindhost() or false
255
256 local function has_hostip()
257 return (sys.call( [[command -v hostip >/dev/null 2>&1]] ) == 0)
258 end
259
260 local function has_nslookup()
261 return (sys.call( [[command -v nslookup >/dev/null 2>&1]] ) == 0)
262 end
263
264 res['has_dnsserver'] = cache['has_bindhost'] or has_nslookup() or has_hostip() or has_bindhost() or false
265
266 local function check_certs()
267 local _, v = fs.glob("/etc/ssl/certs/*.crt")
268 if ( v == 0 ) then _, v = fs.glob("/etc/ssl/certs/*.pem") end
269 return (v > 0)
270 end
271
272 res['has_cacerts'] = check_certs() or false
273
274 res['has_ipv6'] = (fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables"))
275
276 return res
277 end
278 }
279 }
280
281 local function parseInput()
282 local parse = json.new()
283 local done, err
284
285 while true do
286 local chunk = io.read(4096)
287 if not chunk then
288 break
289 elseif not done and not err then
290 done, err = parse:parse(chunk)
291 end
292 end
293
294 if not done then
295 print(json.stringify({ error = err or "Incomplete input" }))
296 os.exit(1)
297 end
298
299 return parse:get()
300 end
301
302 local function validateArgs(func, uargs)
303 local method = methods[func]
304 if not method then
305 print(json.stringify({ error = "Method not found" }))
306 os.exit(1)
307 end
308
309 if type(uargs) ~= "table" then
310 print(json.stringify({ error = "Invalid arguments" }))
311 os.exit(1)
312 end
313
314 uargs.ubus_rpc_session = nil
315
316 local k, v
317 local margs = method.args or {}
318 for k, v in pairs(uargs) do
319 if margs[k] == nil or
320 (v ~= nil and type(v) ~= type(margs[k]))
321 then
322 print(json.stringify({ error = "Invalid arguments" }))
323 os.exit(1)
324 end
325 end
326
327 return method
328 end
329
330 if arg[1] == "list" then
331 local _, method, rv = nil, nil, {}
332 for _, method in pairs(methods) do rv[_] = method.args or {} end
333 print((json.stringify(rv):gsub(":%[%]", ":{}")))
334 elseif arg[1] == "call" then
335 local args = parseInput()
336 local method = validateArgs(arg[2], args)
337 local result, code = method.call(args)
338 print((json.stringify(result):gsub("^%[%]$", "{}")))
339 os.exit(code or 0)
340 end