Merge pull request #1221 from Cye3s/master
[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 =
16         tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
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 function mounts()
28         local data = {}
29         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
30         local ps = luci.util.execi("df")
31
32         if not ps then
33                 return
34         else
35                 ps()
36         end
37
38         for line in ps do
39                 local row = {}
40
41                 local j = 1
42                 for value in line:gmatch("[^%s]+") do
43                         row[k[j]] = value
44                         j = j + 1
45                 end
46
47                 if row[k[1]] then
48
49                         -- this is a rather ugly workaround to cope with wrapped lines in
50                         -- the df output:
51                         --
52                         --      /dev/scsi/host0/bus0/target0/lun0/part3
53                         --                   114382024  93566472  15005244  86% /mnt/usb
54                         --
55
56                         if not row[k[2]] then
57                                 j = 2
58                                 line = ps()
59                                 for value in line:gmatch("[^%s]+") do
60                                         row[k[j]] = value
61                                         j = j + 1
62                                 end
63                         end
64
65                         table.insert(data, row)
66                 end
67         end
68
69         return data
70 end
71
72 -- containing the whole environment is returned otherwise this function returns
73 -- the corresponding string value for the given name or nil if no such variable
74 -- exists.
75 getenv = nixio.getenv
76
77 function hostname(newname)
78         if type(newname) == "string" and #newname > 0 then
79                 fs.writefile( "/proc/sys/kernel/hostname", newname )
80                 return newname
81         else
82                 return nixio.uname().nodename
83         end
84 end
85
86 function httpget(url, stream, target)
87         if not target then
88                 local source = stream and io.popen or luci.util.exec
89                 return source("wget -qO- '"..url:gsub("'", "").."'")
90         else
91                 return os.execute("wget -qO '%s' '%s'" %
92                         {target:gsub("'", ""), url:gsub("'", "")})
93         end
94 end
95
96 function reboot()
97         return os.execute("reboot >/dev/null 2>&1")
98 end
99
100 function syslog()
101         return luci.util.exec("logread")
102 end
103
104 function dmesg()
105         return luci.util.exec("dmesg")
106 end
107
108 function uniqueid(bytes)
109         local rand = fs.readfile("/dev/urandom", bytes)
110         return rand and nixio.bin.hexlify(rand)
111 end
112
113 function uptime()
114         return nixio.sysinfo().uptime
115 end
116
117
118 net = {}
119
120 local function _nethints(what, callback)
121         local _, k, e, mac, ip, name
122         local cur = uci.cursor()
123         local ifn = { }
124         local hosts = { }
125         local lookup = { }
126
127         local function _add(i, ...)
128                 local k = select(i, ...)
129                 if k then
130                         if not hosts[k] then hosts[k] = { } end
131                         hosts[k][1] = select(1, ...) or hosts[k][1]
132                         hosts[k][2] = select(2, ...) or hosts[k][2]
133                         hosts[k][3] = select(3, ...) or hosts[k][3]
134                         hosts[k][4] = select(4, ...) or hosts[k][4]
135                 end
136         end
137
138         luci.ip.neighbors(nil, function(neigh)
139                 if neigh.mac and neigh.family == 4 then
140                         _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
141                 elseif neigh.mac and neigh.family == 6 then
142                         _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
143                 end
144         end)
145
146         if fs.access("/etc/ethers") then
147                 for e in io.lines("/etc/ethers") do
148                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
149                         if mac and ip then
150                                 _add(what, mac:upper(), ip, nil, nil)
151                         end
152                 end
153         end
154
155         cur:foreach("dhcp", "dnsmasq",
156                 function(s)
157                         if s.leasefile and fs.access(s.leasefile) then
158                                 for e in io.lines(s.leasefile) do
159                                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
160                                         if mac and ip then
161                                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
162                                         end
163                                 end
164                         end
165                 end
166         )
167
168         cur:foreach("dhcp", "host",
169                 function(s)
170                         for mac in luci.util.imatch(s.mac) do
171                                 _add(what, mac:upper(), s.ip, nil, s.name)
172                         end
173                 end)
174
175         for _, e in ipairs(nixio.getifaddrs()) do
176                 if e.name ~= "lo" then
177                         ifn[e.name] = ifn[e.name] or { }
178                         if e.family == "packet" and e.addr and #e.addr == 17 then
179                                 ifn[e.name][1] = e.addr:upper()
180                         elseif e.family == "inet" then
181                                 ifn[e.name][2] = e.addr
182                         elseif e.family == "inet6" then
183                                 ifn[e.name][3] = e.addr
184                         end
185                 end
186         end
187
188         for _, e in pairs(ifn) do
189                 if e[what] and (e[2] or e[3]) then
190                         _add(what, e[1], e[2], e[3], e[4])
191                 end
192         end
193
194         for _, e in pairs(hosts) do
195                 lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
196         end
197
198         if #lookup > 0 then
199                 lookup = luci.util.ubus("network.rrdns", "lookup", {
200                         addrs   = lookup,
201                         timeout = 250,
202                         limit   = 1000
203                 }) or { }
204         end
205
206         for _, e in luci.util.kspairs(hosts) do
207                 callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
208         end
209 end
210
211 --          Each entry contains the values in the following order:
212 --          [ "mac", "name" ]
213 function net.mac_hints(callback)
214         if callback then
215                 _nethints(1, function(mac, v4, v6, name)
216                         name = name or v4
217                         if name and name ~= mac then
218                                 callback(mac, name or v4)
219                         end
220                 end)
221         else
222                 local rv = { }
223                 _nethints(1, function(mac, v4, v6, name)
224                         name = name or v4
225                         if name and name ~= mac then
226                                 rv[#rv+1] = { mac, name or v4 }
227                         end
228                 end)
229                 return rv
230         end
231 end
232
233 --          Each entry contains the values in the following order:
234 --          [ "ip", "name" ]
235 function net.ipv4_hints(callback)
236         if callback then
237                 _nethints(2, function(mac, v4, v6, name)
238                         name = name or mac
239                         if name and name ~= v4 then
240                                 callback(v4, name)
241                         end
242                 end)
243         else
244                 local rv = { }
245                 _nethints(2, function(mac, v4, v6, name)
246                         name = name or mac
247                         if name and name ~= v4 then
248                                 rv[#rv+1] = { v4, name }
249                         end
250                 end)
251                 return rv
252         end
253 end
254
255 --          Each entry contains the values in the following order:
256 --          [ "ip", "name" ]
257 function net.ipv6_hints(callback)
258         if callback then
259                 _nethints(3, function(mac, v4, v6, name)
260                         name = name or mac
261                         if name and name ~= v6 then
262                                 callback(v6, name)
263                         end
264                 end)
265         else
266                 local rv = { }
267                 _nethints(3, function(mac, v4, v6, name)
268                         name = name or mac
269                         if name and name ~= v6 then
270                                 rv[#rv+1] = { v6, name }
271                         end
272                 end)
273                 return rv
274         end
275 end
276
277 function net.host_hints(callback)
278         if callback then
279                 _nethints(1, function(mac, v4, v6, name)
280                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
281                                 callback(mac, v4, v6, name)
282                         end
283                 end)
284         else
285                 local rv = { }
286                 _nethints(1, function(mac, v4, v6, name)
287                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
288                                 local e = { }
289                                 if v4   then e.ipv4 = v4   end
290                                 if v6   then e.ipv6 = v6   end
291                                 if name then e.name = name end
292                                 rv[mac] = e
293                         end
294                 end)
295                 return rv
296         end
297 end
298
299 function net.conntrack(callback)
300         local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
301         if not ok or not nfct then
302                 return nil
303         end
304
305         local line, connt = nil, (not callback) and { }
306         for line in nfct do
307                 local fam, l3, l4, timeout, tuples =
308                         line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
309
310                 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
311                         l4 = nixio.getprotobynumber(l4)
312
313                         local entry = {
314                                 bytes = 0,
315                                 packets = 0,
316                                 layer3 = fam,
317                                 layer4 = l4 and l4.name or "unknown",
318                                 timeout = tonumber(timeout, 10)
319                         }
320
321                         local key, val
322                         for key, val in tuples:gmatch("(%w+)=(%S+)") do
323                                 if key == "bytes" or key == "packets" then
324                                         entry[key] = entry[key] + tonumber(val, 10)
325                                 elseif key == "src" or key == "dst" then
326                                         if entry[key] == nil then
327                                                 entry[key] = luci.ip.new(val):string()
328                                         end
329                                 elseif key == "sport" or key == "dport" then
330                                         if entry[key] == nil then
331                                                 entry[key] = val
332                                         end
333                                 elseif val then
334                                         entry[key] = val
335                                 end
336                         end
337
338                         if callback then
339                                 callback(entry)
340                         else
341                                 connt[#connt+1] = entry
342                         end
343                 end
344         end
345
346         return callback and true or connt
347 end
348
349 function net.devices()
350         local devs = {}
351         for k, v in ipairs(nixio.getifaddrs()) do
352                 if v.family == "packet" then
353                         devs[#devs+1] = v.name
354                 end
355         end
356         return devs
357 end
358
359
360 process = {}
361
362 function process.info(key)
363         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
364         return not key and s or s[key]
365 end
366
367 function process.list()
368         local data = {}
369         local k
370         local ps = luci.util.execi("/bin/busybox top -bn1")
371
372         if not ps then
373                 return
374         end
375
376         for line in ps do
377                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
378                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
379                 )
380
381                 local idx = tonumber(pid)
382                 if idx then
383                         data[idx] = {
384                                 ['PID']     = pid,
385                                 ['PPID']    = ppid,
386                                 ['USER']    = user,
387                                 ['STAT']    = stat,
388                                 ['VSZ']     = vsz,
389                                 ['%MEM']    = mem,
390                                 ['%CPU']    = cpu,
391                                 ['COMMAND'] = cmd
392                         }
393                 end
394         end
395
396         return data
397 end
398
399 function process.setgroup(gid)
400         return nixio.setgid(gid)
401 end
402
403 function process.setuser(uid)
404         return nixio.setuid(uid)
405 end
406
407 process.signal = nixio.kill
408
409
410 user = {}
411
412 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
413 user.getuser = nixio.getpw
414
415 function user.getpasswd(username)
416         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
417         local pwh = pwe and (pwe.pwdp or pwe.passwd)
418         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
419                 return nil, pwe
420         else
421                 return pwh, pwe
422         end
423 end
424
425 function user.checkpasswd(username, pass)
426         local pwh, pwe = user.getpasswd(username)
427         if pwe then
428                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
429         end
430         return false
431 end
432
433 function user.setpasswd(username, password)
434         if password then
435                 password = password:gsub("'", [['"'"']])
436         end
437
438         if username then
439                 username = username:gsub("'", [['"'"']])
440         end
441
442         return os.execute(
443                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
444                 "passwd '" .. username .. "' >/dev/null 2>&1"
445         )
446 end
447
448
449 wifi = {}
450
451 function wifi.getiwinfo(ifname)
452         local stat, iwinfo = pcall(require, "iwinfo")
453
454         if ifname then
455                 local d, n = ifname:match("^(%w+)%.network(%d+)")
456                 local wstate = luci.util.ubus("network.wireless", "status") or { }
457
458                 d = d or ifname
459                 n = n and tonumber(n) or 1
460
461                 if type(wstate[d]) == "table" and
462                    type(wstate[d].interfaces) == "table" and
463                    type(wstate[d].interfaces[n]) == "table" and
464                    type(wstate[d].interfaces[n].ifname) == "string"
465                 then
466                         ifname = wstate[d].interfaces[n].ifname
467                 else
468                         ifname = d
469                 end
470
471                 local t = stat and iwinfo.type(ifname)
472                 local x = t and iwinfo[t] or { }
473                 return setmetatable({}, {
474                         __index = function(t, k)
475                                 if k == "ifname" then
476                                         return ifname
477                                 elseif x[k] then
478                                         return x[k](ifname)
479                                 end
480                         end
481                 })
482         end
483 end
484
485
486 init = {}
487 init.dir = "/etc/init.d/"
488
489 function init.names()
490         local names = { }
491         for name in fs.glob(init.dir.."*") do
492                 names[#names+1] = fs.basename(name)
493         end
494         return names
495 end
496
497 function init.index(name)
498         if fs.access(init.dir..name) then
499                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
500                         %{ init.dir, name })
501         end
502 end
503
504 local function init_action(action, name)
505         if fs.access(init.dir..name) then
506                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
507         end
508 end
509
510 function init.enabled(name)
511         return (init_action("enabled", name) == 0)
512 end
513
514 function init.enable(name)
515         return (init_action("enable", name) == 1)
516 end
517
518 function init.disable(name)
519         return (init_action("disable", name) == 0)
520 end
521
522 function init.start(name)
523         return (init_action("start", name) == 0)
524 end
525
526 function init.stop(name)
527         return (init_action("stop", name) == 0)
528 end