luci-base: properly style login alert message
[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" then
347                                         if entry[key] == nil then
348                                                 entry[key] = luci.ip.new(val):string()
349                                         end
350                                 elseif key == "sport" or key == "dport" then
351                                         if entry[key] == nil then
352                                                 entry[key] = val
353                                         end
354                                 elseif val then
355                                         entry[key] = val
356                                 end
357                         end
358
359                         if callback then
360                                 callback(entry)
361                         else
362                                 connt[#connt+1] = entry
363                         end
364                 end
365         end
366
367         return callback and true or connt
368 end
369
370 function net.devices()
371         local devs = {}
372         for k, v in ipairs(nixio.getifaddrs()) do
373                 if v.family == "packet" then
374                         devs[#devs+1] = v.name
375                 end
376         end
377         return devs
378 end
379
380
381 function net.deviceinfo()
382         local devs = {}
383         for k, v in ipairs(nixio.getifaddrs()) do
384                 if v.family == "packet" then
385                         local d = v.data
386                         d[1] = d.rx_bytes
387                         d[2] = d.rx_packets
388                         d[3] = d.rx_errors
389                         d[4] = d.rx_dropped
390                         d[5] = 0
391                         d[6] = 0
392                         d[7] = 0
393                         d[8] = d.multicast
394                         d[9] = d.tx_bytes
395                         d[10] = d.tx_packets
396                         d[11] = d.tx_errors
397                         d[12] = d.tx_dropped
398                         d[13] = 0
399                         d[14] = d.collisions
400                         d[15] = 0
401                         d[16] = 0
402                         devs[v.name] = d
403                 end
404         end
405         return devs
406 end
407
408
409 --                      The following fields are defined for route entry tables:
410 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
411 --                        "flags", "device" }
412 function net.routes(callback)
413         local routes = { }
414
415         for line in io.lines("/proc/net/route") do
416
417                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
418                           dst_mask, mtu, win, irtt = line:match(
419                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
420                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
421                 )
422
423                 if dev then
424                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
425                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
426                         dst_ip   = luci.ip.Hex(
427                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
428                         )
429
430                         local rt = {
431                                 dest     = dst_ip,
432                                 gateway  = gateway,
433                                 metric   = tonumber(metric),
434                                 refcount = tonumber(refcnt),
435                                 usecount = tonumber(usecnt),
436                                 mtu      = tonumber(mtu),
437                                 window   = tonumber(window),
438                                 irtt     = tonumber(irtt),
439                                 flags    = tonumber(flags, 16),
440                                 device   = dev
441                         }
442
443                         if callback then
444                                 callback(rt)
445                         else
446                                 routes[#routes+1] = rt
447                         end
448                 end
449         end
450
451         return routes
452 end
453
454 --                      The following fields are defined for route entry tables:
455 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
456 --                        "flags", "device" }
457 function net.routes6(callback)
458         if fs.access("/proc/net/ipv6_route", "r") then
459                 local routes = { }
460
461                 for line in io.lines("/proc/net/ipv6_route") do
462
463                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
464                                   metric, refcnt, usecnt, flags, dev = line:match(
465                                 "([a-f0-9]+) ([a-f0-9]+) " ..
466                                 "([a-f0-9]+) ([a-f0-9]+) " ..
467                                 "([a-f0-9]+) ([a-f0-9]+) " ..
468                                 "([a-f0-9]+) ([a-f0-9]+) " ..
469                                 "([a-f0-9]+) +([^%s]+)"
470                         )
471
472                         if dst_ip and dst_prefix and
473                            src_ip and src_prefix and
474                            nexthop and metric and
475                            refcnt and usecnt and
476                            flags and dev
477                         then
478                                 src_ip = luci.ip.Hex(
479                                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
480                                 )
481
482                                 dst_ip = luci.ip.Hex(
483                                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
484                                 )
485
486                                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
487
488                                 local rt = {
489                                         source   = src_ip,
490                                         dest     = dst_ip,
491                                         nexthop  = nexthop,
492                                         metric   = tonumber(metric, 16),
493                                         refcount = tonumber(refcnt, 16),
494                                         usecount = tonumber(usecnt, 16),
495                                         flags    = tonumber(flags, 16),
496                                         device   = dev,
497
498                                         -- lua number is too small for storing the metric
499                                         -- add a metric_raw field with the original content
500                                         metric_raw = metric
501                                 }
502
503                                 if callback then
504                                         callback(rt)
505                                 else
506                                         routes[#routes+1] = rt
507                                 end
508                         end
509                 end
510
511                 return routes
512         end
513 end
514
515 function net.pingtest(host)
516         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
517 end
518
519
520 process = {}
521
522 function process.info(key)
523         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
524         return not key and s or s[key]
525 end
526
527 function process.list()
528         local data = {}
529         local k
530         local ps = luci.util.execi("/bin/busybox top -bn1")
531
532         if not ps then
533                 return
534         end
535
536         for line in ps do
537                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
538                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
539                 )
540
541                 local idx = tonumber(pid)
542                 if idx then
543                         data[idx] = {
544                                 ['PID']     = pid,
545                                 ['PPID']    = ppid,
546                                 ['USER']    = user,
547                                 ['STAT']    = stat,
548                                 ['VSZ']     = vsz,
549                                 ['%MEM']    = mem,
550                                 ['%CPU']    = cpu,
551                                 ['COMMAND'] = cmd
552                         }
553                 end
554         end
555
556         return data
557 end
558
559 function process.setgroup(gid)
560         return nixio.setgid(gid)
561 end
562
563 function process.setuser(uid)
564         return nixio.setuid(uid)
565 end
566
567 process.signal = nixio.kill
568
569
570 user = {}
571
572 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
573 user.getuser = nixio.getpw
574
575 function user.getpasswd(username)
576         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
577         local pwh = pwe and (pwe.pwdp or pwe.passwd)
578         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
579                 return nil, pwe
580         else
581                 return pwh, pwe
582         end
583 end
584
585 function user.checkpasswd(username, pass)
586         local pwh, pwe = user.getpasswd(username)
587         if pwe then
588                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
589         end
590         return false
591 end
592
593 function user.setpasswd(username, password)
594         if password then
595                 password = password:gsub("'", [['"'"']])
596         end
597
598         if username then
599                 username = username:gsub("'", [['"'"']])
600         end
601
602         return os.execute(
603                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
604                 "passwd '" .. username .. "' >/dev/null 2>&1"
605         )
606 end
607
608
609 wifi = {}
610
611 function wifi.getiwinfo(ifname)
612         local stat, iwinfo = pcall(require, "iwinfo")
613
614         if ifname then
615                 local d, n = ifname:match("^(%w+)%.network(%d+)")
616                 local wstate = luci.util.ubus("network.wireless", "status") or { }
617
618                 d = d or ifname
619                 n = n and tonumber(n) or 1
620
621                 if type(wstate[d]) == "table" and
622                    type(wstate[d].interfaces) == "table" and
623                    type(wstate[d].interfaces[n]) == "table" and
624                    type(wstate[d].interfaces[n].ifname) == "string"
625                 then
626                         ifname = wstate[d].interfaces[n].ifname
627                 else
628                         ifname = d
629                 end
630
631                 local t = stat and iwinfo.type(ifname)
632                 local x = t and iwinfo[t] or { }
633                 return setmetatable({}, {
634                         __index = function(t, k)
635                                 if k == "ifname" then
636                                         return ifname
637                                 elseif x[k] then
638                                         return x[k](ifname)
639                                 end
640                         end
641                 })
642         end
643 end
644
645
646 init = {}
647 init.dir = "/etc/init.d/"
648
649 function init.names()
650         local names = { }
651         for name in fs.glob(init.dir.."*") do
652                 names[#names+1] = fs.basename(name)
653         end
654         return names
655 end
656
657 function init.index(name)
658         if fs.access(init.dir..name) then
659                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
660                         %{ init.dir, name })
661         end
662 end
663
664 local function init_action(action, name)
665         if fs.access(init.dir..name) then
666                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
667         end
668 end
669
670 function init.enabled(name)
671         return (init_action("enabled", name) == 0)
672 end
673
674 function init.enable(name)
675         return (init_action("enable", name) == 1)
676 end
677
678 function init.disable(name)
679         return (init_action("disable", name) == 0)
680 end
681
682 function init.start(name)
683         return (init_action("start", name) == 0)
684 end
685
686 function init.stop(name)
687         return (init_action("stop", name) == 0)
688 end