luci-app-adblock: Add support for optional config values
[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 --                      The following fields are defined for arp entry objects:
121 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
122 function net.arptable(callback)
123         local arp = (not callback) and {} or nil
124         local e, r, v
125         if fs.access("/proc/net/arp") then
126                 for e in io.lines("/proc/net/arp") do
127                         local r = { }, v
128                         for v in e:gmatch("%S+") do
129                                 r[#r+1] = v
130                         end
131
132                         if r[1] ~= "IP" then
133                                 local x = {
134                                         ["IP address"] = r[1],
135                                         ["HW type"]    = r[2],
136                                         ["Flags"]      = r[3],
137                                         ["HW address"] = r[4],
138                                         ["Mask"]       = r[5],
139                                         ["Device"]     = r[6]
140                                 }
141
142                                 if callback then
143                                         callback(x)
144                                 else
145                                         arp = arp or { }
146                                         arp[#arp+1] = x
147                                 end
148                         end
149                 end
150         end
151         return arp
152 end
153
154 local function _nethints(what, callback)
155         local _, k, e, mac, ip, name
156         local cur = uci.cursor()
157         local ifn = { }
158         local hosts = { }
159
160         local function _add(i, ...)
161                 local k = select(i, ...)
162                 if k then
163                         if not hosts[k] then hosts[k] = { } end
164                         hosts[k][1] = select(1, ...) or hosts[k][1]
165                         hosts[k][2] = select(2, ...) or hosts[k][2]
166                         hosts[k][3] = select(3, ...) or hosts[k][3]
167                         hosts[k][4] = select(4, ...) or hosts[k][4]
168                 end
169         end
170
171         luci.ip.neighbors(nil, function(neigh)
172                 if neigh.mac and neigh.family == 4 then
173                         _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
174                 elseif neigh.mac and neigh.family == 6 then
175                         _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
176                 end
177         end)
178
179         if fs.access("/etc/ethers") then
180                 for e in io.lines("/etc/ethers") do
181                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
182                         if mac and ip then
183                                 _add(what, mac:upper(), ip, nil, nil)
184                         end
185                 end
186         end
187
188         cur:foreach("dhcp", "dnsmasq",
189                 function(s)
190                         if s.leasefile and fs.access(s.leasefile) then
191                                 for e in io.lines(s.leasefile) do
192                                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
193                                         if mac and ip then
194                                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
195                                         end
196                                 end
197                         end
198                 end
199         )
200
201         cur:foreach("dhcp", "host",
202                 function(s)
203                         for mac in luci.util.imatch(s.mac) do
204                                 _add(what, mac:upper(), s.ip, nil, s.name)
205                         end
206                 end)
207
208         for _, e in ipairs(nixio.getifaddrs()) do
209                 if e.name ~= "lo" then
210                         ifn[e.name] = ifn[e.name] or { }
211                         if e.family == "packet" and e.addr and #e.addr == 17 then
212                                 ifn[e.name][1] = e.addr:upper()
213                         elseif e.family == "inet" then
214                                 ifn[e.name][2] = e.addr
215                         elseif e.family == "inet6" then
216                                 ifn[e.name][3] = e.addr
217                         end
218                 end
219         end
220
221         for _, e in pairs(ifn) do
222                 if e[what] and (e[2] or e[3]) then
223                         _add(what, e[1], e[2], e[3], e[4])
224                 end
225         end
226
227         for _, e in luci.util.kspairs(hosts) do
228                 callback(e[1], e[2], e[3], e[4])
229         end
230 end
231
232 --          Each entry contains the values in the following order:
233 --          [ "mac", "name" ]
234 function net.mac_hints(callback)
235         if callback then
236                 _nethints(1, function(mac, v4, v6, name)
237                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
238                         if name and name ~= mac then
239                                 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
240                         end
241                 end)
242         else
243                 local rv = { }
244                 _nethints(1, function(mac, v4, v6, name)
245                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
246                         if name and name ~= mac then
247                                 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
248                         end
249                 end)
250                 return rv
251         end
252 end
253
254 --          Each entry contains the values in the following order:
255 --          [ "ip", "name" ]
256 function net.ipv4_hints(callback)
257         if callback then
258                 _nethints(2, function(mac, v4, v6, name)
259                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
260                         if name and name ~= v4 then
261                                 callback(v4, name)
262                         end
263                 end)
264         else
265                 local rv = { }
266                 _nethints(2, function(mac, v4, v6, name)
267                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
268                         if name and name ~= v4 then
269                                 rv[#rv+1] = { v4, name }
270                         end
271                 end)
272                 return rv
273         end
274 end
275
276 --          Each entry contains the values in the following order:
277 --          [ "ip", "name" ]
278 function net.ipv6_hints(callback)
279         if callback then
280                 _nethints(3, function(mac, v4, v6, name)
281                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
282                         if name and name ~= v6 then
283                                 callback(v6, name)
284                         end
285                 end)
286         else
287                 local rv = { }
288                 _nethints(3, function(mac, v4, v6, name)
289                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
290                         if name and name ~= v6 then
291                                 rv[#rv+1] = { v6, name }
292                         end
293                 end)
294                 return rv
295         end
296 end
297
298 function net.host_hints(callback)
299         if callback then
300                 _nethints(1, function(mac, v4, v6, name)
301                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
302                                 callback(mac, v4, v6, name)
303                         end
304                 end)
305         else
306                 local rv = { }
307                 _nethints(1, function(mac, v4, v6, name)
308                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
309                                 local e = { }
310                                 if v4   then e.ipv4 = v4   end
311                                 if v6   then e.ipv6 = v6   end
312                                 if name then e.name = name end
313                                 rv[mac] = e
314                         end
315                 end)
316                 return rv
317         end
318 end
319
320 function net.conntrack(callback)
321         local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
322         if not ok or not nfct then
323                 return nil
324         end
325
326         local line, connt = nil, (not callback) and { }
327         for line in nfct do
328                 local fam, l3, l4, timeout, tuples =
329                         line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
330
331                 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
332                         l4 = nixio.getprotobynumber(l4)
333
334                         local entry = {
335                                 bytes = 0,
336                                 packets = 0,
337                                 layer3 = fam,
338                                 layer4 = l4 and l4.name or "unknown",
339                                 timeout = tonumber(timeout, 10)
340                         }
341
342                         local key, val
343                         for key, val in tuples:gmatch("(%w+)=(%S+)") do
344                                 if key == "bytes" or key == "packets" then
345                                         entry[key] = entry[key] + tonumber(val, 10)
346                                 elseif key == "src" or key == "dst" or key == "sport" or key == "dport" then
347                                         if entry[key] == nil then
348                                                 entry[key] = val
349                                         end
350                                 elseif val then
351                                         entry[key] = val
352                                 end
353                         end
354
355                         if callback then
356                                 callback(entry)
357                         else
358                                 connt[#connt+1] = entry
359                         end
360                 end
361         end
362
363         return callback and true or connt
364 end
365
366 function net.devices()
367         local devs = {}
368         for k, v in ipairs(nixio.getifaddrs()) do
369                 if v.family == "packet" then
370                         devs[#devs+1] = v.name
371                 end
372         end
373         return devs
374 end
375
376
377 function net.deviceinfo()
378         local devs = {}
379         for k, v in ipairs(nixio.getifaddrs()) do
380                 if v.family == "packet" then
381                         local d = v.data
382                         d[1] = d.rx_bytes
383                         d[2] = d.rx_packets
384                         d[3] = d.rx_errors
385                         d[4] = d.rx_dropped
386                         d[5] = 0
387                         d[6] = 0
388                         d[7] = 0
389                         d[8] = d.multicast
390                         d[9] = d.tx_bytes
391                         d[10] = d.tx_packets
392                         d[11] = d.tx_errors
393                         d[12] = d.tx_dropped
394                         d[13] = 0
395                         d[14] = d.collisions
396                         d[15] = 0
397                         d[16] = 0
398                         devs[v.name] = d
399                 end
400         end
401         return devs
402 end
403
404
405 --                      The following fields are defined for route entry tables:
406 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
407 --                        "flags", "device" }
408 function net.routes(callback)
409         local routes = { }
410
411         for line in io.lines("/proc/net/route") do
412
413                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
414                           dst_mask, mtu, win, irtt = line:match(
415                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
416                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
417                 )
418
419                 if dev then
420                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
421                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
422                         dst_ip   = luci.ip.Hex(
423                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
424                         )
425
426                         local rt = {
427                                 dest     = dst_ip,
428                                 gateway  = gateway,
429                                 metric   = tonumber(metric),
430                                 refcount = tonumber(refcnt),
431                                 usecount = tonumber(usecnt),
432                                 mtu      = tonumber(mtu),
433                                 window   = tonumber(window),
434                                 irtt     = tonumber(irtt),
435                                 flags    = tonumber(flags, 16),
436                                 device   = dev
437                         }
438
439                         if callback then
440                                 callback(rt)
441                         else
442                                 routes[#routes+1] = rt
443                         end
444                 end
445         end
446
447         return routes
448 end
449
450 --                      The following fields are defined for route entry tables:
451 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
452 --                        "flags", "device" }
453 function net.routes6(callback)
454         if fs.access("/proc/net/ipv6_route", "r") then
455                 local routes = { }
456
457                 for line in io.lines("/proc/net/ipv6_route") do
458
459                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
460                                   metric, refcnt, usecnt, flags, dev = line:match(
461                                 "([a-f0-9]+) ([a-f0-9]+) " ..
462                                 "([a-f0-9]+) ([a-f0-9]+) " ..
463                                 "([a-f0-9]+) ([a-f0-9]+) " ..
464                                 "([a-f0-9]+) ([a-f0-9]+) " ..
465                                 "([a-f0-9]+) +([^%s]+)"
466                         )
467
468                         if dst_ip and dst_prefix and
469                            src_ip and src_prefix and
470                            nexthop and metric and
471                            refcnt and usecnt and
472                            flags and dev
473                         then
474                                 src_ip = luci.ip.Hex(
475                                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
476                                 )
477
478                                 dst_ip = luci.ip.Hex(
479                                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
480                                 )
481
482                                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
483
484                                 local rt = {
485                                         source   = src_ip,
486                                         dest     = dst_ip,
487                                         nexthop  = nexthop,
488                                         metric   = tonumber(metric, 16),
489                                         refcount = tonumber(refcnt, 16),
490                                         usecount = tonumber(usecnt, 16),
491                                         flags    = tonumber(flags, 16),
492                                         device   = dev,
493
494                                         -- lua number is too small for storing the metric
495                                         -- add a metric_raw field with the original content
496                                         metric_raw = metric
497                                 }
498
499                                 if callback then
500                                         callback(rt)
501                                 else
502                                         routes[#routes+1] = rt
503                                 end
504                         end
505                 end
506
507                 return routes
508         end
509 end
510
511 function net.pingtest(host)
512         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
513 end
514
515
516 process = {}
517
518 function process.info(key)
519         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
520         return not key and s or s[key]
521 end
522
523 function process.list()
524         local data = {}
525         local k
526         local ps = luci.util.execi("/bin/busybox top -bn1")
527
528         if not ps then
529                 return
530         end
531
532         for line in ps do
533                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
534                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
535                 )
536
537                 local idx = tonumber(pid)
538                 if idx then
539                         data[idx] = {
540                                 ['PID']     = pid,
541                                 ['PPID']    = ppid,
542                                 ['USER']    = user,
543                                 ['STAT']    = stat,
544                                 ['VSZ']     = vsz,
545                                 ['%MEM']    = mem,
546                                 ['%CPU']    = cpu,
547                                 ['COMMAND'] = cmd
548                         }
549                 end
550         end
551
552         return data
553 end
554
555 function process.setgroup(gid)
556         return nixio.setgid(gid)
557 end
558
559 function process.setuser(uid)
560         return nixio.setuid(uid)
561 end
562
563 process.signal = nixio.kill
564
565
566 user = {}
567
568 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
569 user.getuser = nixio.getpw
570
571 function user.getpasswd(username)
572         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
573         local pwh = pwe and (pwe.pwdp or pwe.passwd)
574         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
575                 return nil, pwe
576         else
577                 return pwh, pwe
578         end
579 end
580
581 function user.checkpasswd(username, pass)
582         local pwh, pwe = user.getpasswd(username)
583         if pwe then
584                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
585         end
586         return false
587 end
588
589 function user.setpasswd(username, password)
590         if password then
591                 password = password:gsub("'", [['"'"']])
592         end
593
594         if username then
595                 username = username:gsub("'", [['"'"']])
596         end
597
598         return os.execute(
599                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
600                 "passwd '" .. username .. "' >/dev/null 2>&1"
601         )
602 end
603
604
605 wifi = {}
606
607 function wifi.getiwinfo(ifname)
608         local stat, iwinfo = pcall(require, "iwinfo")
609
610         if ifname then
611                 local d, n = ifname:match("^(%w+)%.network(%d+)")
612                 local wstate = luci.util.ubus("network.wireless", "status") or { }
613
614                 d = d or ifname
615                 n = n and tonumber(n) or 1
616
617                 if type(wstate[d]) == "table" and
618                    type(wstate[d].interfaces) == "table" and
619                    type(wstate[d].interfaces[n]) == "table" and
620                    type(wstate[d].interfaces[n].ifname) == "string"
621                 then
622                         ifname = wstate[d].interfaces[n].ifname
623                 else
624                         ifname = d
625                 end
626
627                 local t = stat and iwinfo.type(ifname)
628                 local x = t and iwinfo[t] or { }
629                 return setmetatable({}, {
630                         __index = function(t, k)
631                                 if k == "ifname" then
632                                         return ifname
633                                 elseif x[k] then
634                                         return x[k](ifname)
635                                 end
636                         end
637                 })
638         end
639 end
640
641
642 init = {}
643 init.dir = "/etc/init.d/"
644
645 function init.names()
646         local names = { }
647         for name in fs.glob(init.dir.."*") do
648                 names[#names+1] = fs.basename(name)
649         end
650         return names
651 end
652
653 function init.index(name)
654         if fs.access(init.dir..name) then
655                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
656                         %{ init.dir, name })
657         end
658 end
659
660 local function init_action(action, name)
661         if fs.access(init.dir..name) then
662                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
663         end
664 end
665
666 function init.enabled(name)
667         return (init_action("enabled", name) == 0)
668 end
669
670 function init.enable(name)
671         return (init_action("enable", name) == 1)
672 end
673
674 function init.disable(name)
675         return (init_action("disable", name) == 0)
676 end
677
678 function init.start(name)
679         return (init_action("start", name) == 0)
680 end
681
682 function init.stop(name)
683         return (init_action("stop", name) == 0)
684 end