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