141f91031823043e1476503ea1a5a196e968b836
[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
126         local function _add(i, ...)
127                 local k = select(i, ...)
128                 if k then
129                         if not hosts[k] then hosts[k] = { } end
130                         hosts[k][1] = select(1, ...) or hosts[k][1]
131                         hosts[k][2] = select(2, ...) or hosts[k][2]
132                         hosts[k][3] = select(3, ...) or hosts[k][3]
133                         hosts[k][4] = select(4, ...) or hosts[k][4]
134                 end
135         end
136
137         luci.ip.neighbors(nil, function(neigh)
138                 if neigh.mac and neigh.family == 4 then
139                         _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
140                 elseif neigh.mac and neigh.family == 6 then
141                         _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
142                 end
143         end)
144
145         if fs.access("/etc/ethers") then
146                 for e in io.lines("/etc/ethers") do
147                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
148                         if mac and ip then
149                                 _add(what, mac:upper(), ip, nil, nil)
150                         end
151                 end
152         end
153
154         cur:foreach("dhcp", "dnsmasq",
155                 function(s)
156                         if s.leasefile and fs.access(s.leasefile) then
157                                 for e in io.lines(s.leasefile) do
158                                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
159                                         if mac and ip then
160                                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
161                                         end
162                                 end
163                         end
164                 end
165         )
166
167         cur:foreach("dhcp", "host",
168                 function(s)
169                         for mac in luci.util.imatch(s.mac) do
170                                 _add(what, mac:upper(), s.ip, nil, s.name)
171                         end
172                 end)
173
174         for _, e in ipairs(nixio.getifaddrs()) do
175                 if e.name ~= "lo" then
176                         ifn[e.name] = ifn[e.name] or { }
177                         if e.family == "packet" and e.addr and #e.addr == 17 then
178                                 ifn[e.name][1] = e.addr:upper()
179                         elseif e.family == "inet" then
180                                 ifn[e.name][2] = e.addr
181                         elseif e.family == "inet6" then
182                                 ifn[e.name][3] = e.addr
183                         end
184                 end
185         end
186
187         for _, e in pairs(ifn) do
188                 if e[what] and (e[2] or e[3]) then
189                         _add(what, e[1], e[2], e[3], e[4])
190                 end
191         end
192
193         for _, e in luci.util.kspairs(hosts) do
194                 callback(e[1], e[2], e[3], e[4])
195         end
196 end
197
198 --          Each entry contains the values in the following order:
199 --          [ "mac", "name" ]
200 function net.mac_hints(callback)
201         if callback then
202                 _nethints(1, function(mac, v4, v6, name)
203                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
204                         if name and name ~= mac then
205                                 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
206                         end
207                 end)
208         else
209                 local rv = { }
210                 _nethints(1, function(mac, v4, v6, name)
211                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
212                         if name and name ~= mac then
213                                 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
214                         end
215                 end)
216                 return rv
217         end
218 end
219
220 --          Each entry contains the values in the following order:
221 --          [ "ip", "name" ]
222 function net.ipv4_hints(callback)
223         if callback then
224                 _nethints(2, function(mac, v4, v6, name)
225                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
226                         if name and name ~= v4 then
227                                 callback(v4, name)
228                         end
229                 end)
230         else
231                 local rv = { }
232                 _nethints(2, function(mac, v4, v6, name)
233                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
234                         if name and name ~= v4 then
235                                 rv[#rv+1] = { v4, name }
236                         end
237                 end)
238                 return rv
239         end
240 end
241
242 --          Each entry contains the values in the following order:
243 --          [ "ip", "name" ]
244 function net.ipv6_hints(callback)
245         if callback then
246                 _nethints(3, function(mac, v4, v6, name)
247                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
248                         if name and name ~= v6 then
249                                 callback(v6, name)
250                         end
251                 end)
252         else
253                 local rv = { }
254                 _nethints(3, function(mac, v4, v6, name)
255                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
256                         if name and name ~= v6 then
257                                 rv[#rv+1] = { v6, name }
258                         end
259                 end)
260                 return rv
261         end
262 end
263
264 function net.host_hints(callback)
265         if callback then
266                 _nethints(1, function(mac, v4, v6, name)
267                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
268                                 callback(mac, v4, v6, name)
269                         end
270                 end)
271         else
272                 local rv = { }
273                 _nethints(1, function(mac, v4, v6, name)
274                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
275                                 local e = { }
276                                 if v4   then e.ipv4 = v4   end
277                                 if v6   then e.ipv6 = v6   end
278                                 if name then e.name = name end
279                                 rv[mac] = e
280                         end
281                 end)
282                 return rv
283         end
284 end
285
286 function net.conntrack(callback)
287         local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
288         if not ok or not nfct then
289                 return nil
290         end
291
292         local line, connt = nil, (not callback) and { }
293         for line in nfct do
294                 local fam, l3, l4, timeout, tuples =
295                         line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
296
297                 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
298                         l4 = nixio.getprotobynumber(l4)
299
300                         local entry = {
301                                 bytes = 0,
302                                 packets = 0,
303                                 layer3 = fam,
304                                 layer4 = l4 and l4.name or "unknown",
305                                 timeout = tonumber(timeout, 10)
306                         }
307
308                         local key, val
309                         for key, val in tuples:gmatch("(%w+)=(%S+)") do
310                                 if key == "bytes" or key == "packets" then
311                                         entry[key] = entry[key] + tonumber(val, 10)
312                                 elseif key == "src" or key == "dst" then
313                                         if entry[key] == nil then
314                                                 entry[key] = luci.ip.new(val):string()
315                                         end
316                                 elseif key == "sport" or key == "dport" then
317                                         if entry[key] == nil then
318                                                 entry[key] = val
319                                         end
320                                 elseif val then
321                                         entry[key] = val
322                                 end
323                         end
324
325                         if callback then
326                                 callback(entry)
327                         else
328                                 connt[#connt+1] = entry
329                         end
330                 end
331         end
332
333         return callback and true or connt
334 end
335
336 function net.devices()
337         local devs = {}
338         for k, v in ipairs(nixio.getifaddrs()) do
339                 if v.family == "packet" then
340                         devs[#devs+1] = v.name
341                 end
342         end
343         return devs
344 end
345
346
347 process = {}
348
349 function process.info(key)
350         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
351         return not key and s or s[key]
352 end
353
354 function process.list()
355         local data = {}
356         local k
357         local ps = luci.util.execi("/bin/busybox top -bn1")
358
359         if not ps then
360                 return
361         end
362
363         for line in ps do
364                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
365                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
366                 )
367
368                 local idx = tonumber(pid)
369                 if idx then
370                         data[idx] = {
371                                 ['PID']     = pid,
372                                 ['PPID']    = ppid,
373                                 ['USER']    = user,
374                                 ['STAT']    = stat,
375                                 ['VSZ']     = vsz,
376                                 ['%MEM']    = mem,
377                                 ['%CPU']    = cpu,
378                                 ['COMMAND'] = cmd
379                         }
380                 end
381         end
382
383         return data
384 end
385
386 function process.setgroup(gid)
387         return nixio.setgid(gid)
388 end
389
390 function process.setuser(uid)
391         return nixio.setuid(uid)
392 end
393
394 process.signal = nixio.kill
395
396
397 user = {}
398
399 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
400 user.getuser = nixio.getpw
401
402 function user.getpasswd(username)
403         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
404         local pwh = pwe and (pwe.pwdp or pwe.passwd)
405         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
406                 return nil, pwe
407         else
408                 return pwh, pwe
409         end
410 end
411
412 function user.checkpasswd(username, pass)
413         local pwh, pwe = user.getpasswd(username)
414         if pwe then
415                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
416         end
417         return false
418 end
419
420 function user.setpasswd(username, password)
421         if password then
422                 password = password:gsub("'", [['"'"']])
423         end
424
425         if username then
426                 username = username:gsub("'", [['"'"']])
427         end
428
429         return os.execute(
430                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
431                 "passwd '" .. username .. "' >/dev/null 2>&1"
432         )
433 end
434
435
436 wifi = {}
437
438 function wifi.getiwinfo(ifname)
439         local stat, iwinfo = pcall(require, "iwinfo")
440
441         if ifname then
442                 local d, n = ifname:match("^(%w+)%.network(%d+)")
443                 local wstate = luci.util.ubus("network.wireless", "status") or { }
444
445                 d = d or ifname
446                 n = n and tonumber(n) or 1
447
448                 if type(wstate[d]) == "table" and
449                    type(wstate[d].interfaces) == "table" and
450                    type(wstate[d].interfaces[n]) == "table" and
451                    type(wstate[d].interfaces[n].ifname) == "string"
452                 then
453                         ifname = wstate[d].interfaces[n].ifname
454                 else
455                         ifname = d
456                 end
457
458                 local t = stat and iwinfo.type(ifname)
459                 local x = t and iwinfo[t] or { }
460                 return setmetatable({}, {
461                         __index = function(t, k)
462                                 if k == "ifname" then
463                                         return ifname
464                                 elseif x[k] then
465                                         return x[k](ifname)
466                                 end
467                         end
468                 })
469         end
470 end
471
472
473 init = {}
474 init.dir = "/etc/init.d/"
475
476 function init.names()
477         local names = { }
478         for name in fs.glob(init.dir.."*") do
479                 names[#names+1] = fs.basename(name)
480         end
481         return names
482 end
483
484 function init.index(name)
485         if fs.access(init.dir..name) then
486                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
487                         %{ init.dir, name })
488         end
489 end
490
491 local function init_action(action, name)
492         if fs.access(init.dir..name) then
493                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
494         end
495 end
496
497 function init.enabled(name)
498         return (init_action("enabled", name) == 0)
499 end
500
501 function init.enable(name)
502         return (init_action("enable", name) == 1)
503 end
504
505 function init.disable(name)
506         return (init_action("disable", name) == 0)
507 end
508
509 function init.start(name)
510         return (init_action("start", name) == 0)
511 end
512
513 function init.stop(name)
514         return (init_action("stop", name) == 0)
515 end