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