luci-base: sys.lua: make timeout optional when parsing conntrack entries
[project/luci.git] / modules / luci-base / luasrc / sys.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local io = require "io"
5 local os = require "os"
6 local table = require "table"
7 local nixio = require "nixio"
8 local fs = require "nixio.fs"
9 local uci = require "luci.model.uci"
10
11 local luci = {}
12 luci.util = require "luci.util"
13 luci.ip = require "luci.ip"
14
15 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack =
16 tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack
17
18
19 module "luci.sys"
20
21 function call(...)
22 return os.execute(...) / 256
23 end
24
25 exec = luci.util.exec
26
27 -- containing the whole environment is returned otherwise this function returns
28 -- the corresponding string value for the given name or nil if no such variable
29 -- exists.
30 getenv = nixio.getenv
31
32 function hostname(newname)
33 if type(newname) == "string" and #newname > 0 then
34 fs.writefile( "/proc/sys/kernel/hostname", newname )
35 return newname
36 else
37 return nixio.uname().nodename
38 end
39 end
40
41 function httpget(url, stream, target)
42 if not target then
43 local source = stream and io.popen or luci.util.exec
44 return source("wget -qO- %s" % luci.util.shellquote(url))
45 else
46 return os.execute("wget -qO %s %s" %
47 {luci.util.shellquote(target), luci.util.shellquote(url)})
48 end
49 end
50
51 function reboot()
52 return os.execute("reboot >/dev/null 2>&1")
53 end
54
55 function syslog()
56 return luci.util.exec("logread")
57 end
58
59 function dmesg()
60 return luci.util.exec("dmesg")
61 end
62
63 function uniqueid(bytes)
64 local rand = fs.readfile("/dev/urandom", bytes)
65 return rand and nixio.bin.hexlify(rand)
66 end
67
68 function uptime()
69 return nixio.sysinfo().uptime
70 end
71
72
73 net = {}
74
75 local function _nethints(what, callback)
76 local _, k, e, mac, ip, name, duid, iaid
77 local cur = uci.cursor()
78 local ifn = { }
79 local hosts = { }
80 local lookup = { }
81
82 local function _add(i, ...)
83 local k = select(i, ...)
84 if k then
85 if not hosts[k] then hosts[k] = { } end
86 hosts[k][1] = select(1, ...) or hosts[k][1]
87 hosts[k][2] = select(2, ...) or hosts[k][2]
88 hosts[k][3] = select(3, ...) or hosts[k][3]
89 hosts[k][4] = select(4, ...) or hosts[k][4]
90 end
91 end
92
93 luci.ip.neighbors(nil, function(neigh)
94 if neigh.mac and neigh.family == 4 then
95 _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
96 elseif neigh.mac and neigh.family == 6 then
97 _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
98 end
99 end)
100
101 if fs.access("/etc/ethers") then
102 for e in io.lines("/etc/ethers") do
103 mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
104 mac = luci.ip.checkmac(mac)
105 if mac and name then
106 if luci.ip.checkip4(name) then
107 _add(what, mac, name, nil, nil)
108 else
109 _add(what, mac, nil, nil, name)
110 end
111 end
112 end
113 end
114
115 cur:foreach("dhcp", "dnsmasq",
116 function(s)
117 if s.leasefile and fs.access(s.leasefile) then
118 for e in io.lines(s.leasefile) do
119 mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
120 mac = luci.ip.checkmac(mac)
121 if mac and ip then
122 _add(what, mac, ip, nil, name ~= "*" and name)
123 end
124 end
125 end
126 end
127 )
128
129 cur:foreach("dhcp", "odhcpd",
130 function(s)
131 if type(s.leasefile) == "string" and fs.access(s.leasefile) then
132 for e in io.lines(s.leasefile) do
133 duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+")
134 mac = net.duid_to_mac(duid)
135 if mac then
136 if ip and iaid == "ipv4" then
137 _add(what, mac, ip, nil, name ~= "*" and name)
138 elseif ip then
139 _add(what, mac, nil, ip, name ~= "*" and name)
140 end
141 end
142 end
143 end
144 end
145 )
146
147 cur:foreach("dhcp", "host",
148 function(s)
149 for mac in luci.util.imatch(s.mac) do
150 mac = luci.ip.checkmac(mac)
151 if mac then
152 _add(what, mac, s.ip, nil, s.name)
153 end
154 end
155 end)
156
157 for _, e in ipairs(nixio.getifaddrs()) do
158 if e.name ~= "lo" then
159 ifn[e.name] = ifn[e.name] or { }
160 if e.family == "packet" and e.addr and #e.addr == 17 then
161 ifn[e.name][1] = e.addr:upper()
162 elseif e.family == "inet" then
163 ifn[e.name][2] = e.addr
164 elseif e.family == "inet6" then
165 ifn[e.name][3] = e.addr
166 end
167 end
168 end
169
170 for _, e in pairs(ifn) do
171 if e[what] and (e[2] or e[3]) then
172 _add(what, e[1], e[2], e[3], e[4])
173 end
174 end
175
176 for _, e in pairs(hosts) do
177 lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
178 end
179
180 if #lookup > 0 then
181 lookup = luci.util.ubus("network.rrdns", "lookup", {
182 addrs = lookup,
183 timeout = 250,
184 limit = 1000
185 }) or { }
186 end
187
188 for _, e in luci.util.kspairs(hosts) do
189 callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
190 end
191 end
192
193 -- Each entry contains the values in the following order:
194 -- [ "mac", "name" ]
195 function net.mac_hints(callback)
196 if callback then
197 _nethints(1, function(mac, v4, v6, name)
198 name = name or v4
199 if name and name ~= mac then
200 callback(mac, name or v4)
201 end
202 end)
203 else
204 local rv = { }
205 _nethints(1, function(mac, v4, v6, name)
206 name = name or v4
207 if name and name ~= mac then
208 rv[#rv+1] = { mac, name or v4 }
209 end
210 end)
211 return rv
212 end
213 end
214
215 -- Each entry contains the values in the following order:
216 -- [ "ip", "name" ]
217 function net.ipv4_hints(callback)
218 if callback then
219 _nethints(2, function(mac, v4, v6, name)
220 name = name or mac
221 if name and name ~= v4 then
222 callback(v4, name)
223 end
224 end)
225 else
226 local rv = { }
227 _nethints(2, function(mac, v4, v6, name)
228 name = name or mac
229 if name and name ~= v4 then
230 rv[#rv+1] = { v4, name }
231 end
232 end)
233 return rv
234 end
235 end
236
237 -- Each entry contains the values in the following order:
238 -- [ "ip", "name" ]
239 function net.ipv6_hints(callback)
240 if callback then
241 _nethints(3, function(mac, v4, v6, name)
242 name = name or mac
243 if name and name ~= v6 then
244 callback(v6, name)
245 end
246 end)
247 else
248 local rv = { }
249 _nethints(3, function(mac, v4, v6, name)
250 name = name or mac
251 if name and name ~= v6 then
252 rv[#rv+1] = { v6, name }
253 end
254 end)
255 return rv
256 end
257 end
258
259 function net.host_hints(callback)
260 if callback then
261 _nethints(1, function(mac, v4, v6, name)
262 if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
263 callback(mac, v4, v6, name)
264 end
265 end)
266 else
267 local rv = { }
268 _nethints(1, function(mac, v4, v6, name)
269 if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
270 local e = { }
271 if v4 then e.ipv4 = v4 end
272 if v6 then e.ipv6 = v6 end
273 if name then e.name = name end
274 rv[mac] = e
275 end
276 end)
277 return rv
278 end
279 end
280
281 function net.conntrack(callback)
282 local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
283 if not ok or not nfct then
284 return nil
285 end
286
287 local line, connt = nil, (not callback) and { }
288 for line in nfct do
289 local fam, l3, l4, rest =
290 line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$")
291
292 local timeout, tuples = rest:match("^(%d+) +(.+)$")
293
294 if not tuples then
295 tuples = rest
296 end
297
298 if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then
299 l4 = nixio.getprotobynumber(l4)
300
301 local entry = {
302 bytes = 0,
303 packets = 0,
304 layer3 = fam,
305 layer4 = l4 and l4.name or "unknown",
306 timeout = tonumber(timeout, 10)
307 }
308
309 local key, val
310 for key, val in tuples:gmatch("(%w+)=(%S+)") do
311 if key == "bytes" or key == "packets" then
312 entry[key] = entry[key] + tonumber(val, 10)
313 elseif key == "src" or key == "dst" then
314 if entry[key] == nil then
315 entry[key] = luci.ip.new(val):string()
316 end
317 elseif key == "sport" or key == "dport" then
318 if entry[key] == nil then
319 entry[key] = val
320 end
321 elseif val then
322 entry[key] = val
323 end
324 end
325
326 if callback then
327 callback(entry)
328 else
329 connt[#connt+1] = entry
330 end
331 end
332 end
333
334 return callback and true or connt
335 end
336
337 function net.devices()
338 local devs = {}
339 local seen = {}
340 for k, v in ipairs(nixio.getifaddrs()) do
341 if v.name and not seen[v.name] then
342 seen[v.name] = true
343 devs[#devs+1] = v.name
344 end
345 end
346 return devs
347 end
348
349 function net.duid_to_mac(duid)
350 local b1, b2, b3, b4, b5, b6
351
352 if type(duid) == "string" then
353 -- DUID-LLT / Ethernet
354 if #duid == 28 then
355 b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
356
357 -- DUID-LL / Ethernet
358 elseif #duid == 20 then
359 b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
360
361 -- DUID-LL / Ethernet (Without Header)
362 elseif #duid == 12 then
363 b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
364 end
365 end
366
367 return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
368 end
369
370 process = {}
371
372 function process.info(key)
373 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
374 return not key and s or s[key]
375 end
376
377 function process.list()
378 local data = {}
379 local k
380 local ps = luci.util.execi("/bin/busybox top -bn1")
381
382 if not ps then
383 return
384 end
385
386 for line in ps do
387 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
388 "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+m?) +(%d+%%) +(%d+%%) +(.+)"
389 )
390
391 local idx = tonumber(pid)
392 if idx and not cmd:match("top %-bn1") then
393 data[idx] = {
394 ['PID'] = pid,
395 ['PPID'] = ppid,
396 ['USER'] = user,
397 ['STAT'] = stat,
398 ['VSZ'] = vsz,
399 ['%MEM'] = mem,
400 ['%CPU'] = cpu,
401 ['COMMAND'] = cmd
402 }
403 end
404 end
405
406 return data
407 end
408
409 function process.setgroup(gid)
410 return nixio.setgid(gid)
411 end
412
413 function process.setuser(uid)
414 return nixio.setuid(uid)
415 end
416
417 process.signal = nixio.kill
418
419 local function xclose(fd)
420 if fd and fd:fileno() > 2 then
421 fd:close()
422 end
423 end
424
425 function process.exec(command, stdout, stderr, nowait)
426 local out_r, out_w, err_r, err_w
427 if stdout then out_r, out_w = nixio.pipe() end
428 if stderr then err_r, err_w = nixio.pipe() end
429
430 local pid = nixio.fork()
431 if pid == 0 then
432 nixio.chdir("/")
433
434 local null = nixio.open("/dev/null", "w+")
435 if null then
436 nixio.dup(out_w or null, nixio.stdout)
437 nixio.dup(err_w or null, nixio.stderr)
438 nixio.dup(null, nixio.stdin)
439 xclose(out_w)
440 xclose(out_r)
441 xclose(err_w)
442 xclose(err_r)
443 xclose(null)
444 end
445
446 nixio.exec(unpack(command))
447 os.exit(-1)
448 end
449
450 local _, pfds, rv = nil, {}, { code = -1, pid = pid }
451
452 xclose(out_w)
453 xclose(err_w)
454
455 if out_r then
456 pfds[#pfds+1] = {
457 fd = out_r,
458 cb = type(stdout) == "function" and stdout,
459 name = "stdout",
460 events = nixio.poll_flags("in", "err", "hup")
461 }
462 end
463
464 if err_r then
465 pfds[#pfds+1] = {
466 fd = err_r,
467 cb = type(stderr) == "function" and stderr,
468 name = "stderr",
469 events = nixio.poll_flags("in", "err", "hup")
470 }
471 end
472
473 while #pfds > 0 do
474 local nfds, err = nixio.poll(pfds, -1)
475 if not nfds and err ~= nixio.const.EINTR then
476 break
477 end
478
479 local i
480 for i = #pfds, 1, -1 do
481 local rfd = pfds[i]
482 if rfd.revents > 0 then
483 local chunk, err = rfd.fd:read(4096)
484 if chunk and #chunk > 0 then
485 if rfd.cb then
486 rfd.cb(chunk)
487 else
488 rfd.buf = rfd.buf or {}
489 rfd.buf[#rfd.buf + 1] = chunk
490 end
491 else
492 table.remove(pfds, i)
493 if rfd.buf then
494 rv[rfd.name] = table.concat(rfd.buf, "")
495 end
496 rfd.fd:close()
497 end
498 end
499 end
500 end
501
502 if not nowait then
503 _, _, rv.code = nixio.waitpid(pid)
504 end
505
506 return rv
507 end
508
509
510 user = {}
511
512 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
513 user.getuser = nixio.getpw
514
515 function user.getpasswd(username)
516 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
517 local pwh = pwe and (pwe.pwdp or pwe.passwd)
518 if not pwh or #pwh < 1 then
519 return nil, pwe
520 else
521 return pwh, pwe
522 end
523 end
524
525 function user.checkpasswd(username, pass)
526 local pwh, pwe = user.getpasswd(username)
527 if pwe then
528 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
529 end
530 return false
531 end
532
533 function user.setpasswd(username, password)
534 return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
535 luci.util.shellquote(password),
536 luci.util.shellquote(password),
537 luci.util.shellquote(username)
538 })
539 end
540
541
542 wifi = {}
543
544 function wifi.getiwinfo(ifname)
545 local ntm = require "luci.model.network"
546
547 ntm.init()
548
549 local wnet = ntm:get_wifinet(ifname)
550 if wnet and wnet.iwinfo then
551 return wnet.iwinfo
552 end
553
554 local wdev = ntm:get_wifidev(ifname)
555 if wdev and wdev.iwinfo then
556 return wdev.iwinfo
557 end
558
559 return { ifname = ifname }
560 end
561
562
563 init = {}
564 init.dir = "/etc/init.d/"
565
566 function init.names()
567 local names = { }
568 for name in fs.glob(init.dir.."*") do
569 names[#names+1] = fs.basename(name)
570 end
571 return names
572 end
573
574 function init.index(name)
575 name = fs.basename(name)
576 if fs.access(init.dir..name) then
577 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
578 %{ init.dir, name })
579 end
580 end
581
582 local function init_action(action, name)
583 name = fs.basename(name)
584 if fs.access(init.dir..name) then
585 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
586 end
587 end
588
589 function init.enabled(name)
590 return (init_action("enabled", name) == 0)
591 end
592
593 function init.enable(name)
594 return (init_action("enable", name) == 0)
595 end
596
597 function init.disable(name)
598 return (init_action("disable", name) == 0)
599 end
600
601 function init.start(name)
602 return (init_action("start", name) == 0)
603 end
604
605 function init.stop(name)
606 return (init_action("stop", name) == 0)
607 end
608
609 function init.restart(name)
610 return (init_action("restart", name) == 0)
611 end
612
613 function init.reload(name)
614 return (init_action("reload", name) == 0)
615 end