libs/sys: introduce net.mac_hints(), net.ipv4_hints() and net.ipv6_hints() functions...
[project/luci.git] / libs / sys / luasrc / sys.lua
1 --[[
2 LuCI - System library
3
4 Description:
5 Utilities for interaction with the Linux system
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27
28 local io     = require "io"
29 local os     = require "os"
30 local table  = require "table"
31 local nixio  = require "nixio"
32 local fs     = require "nixio.fs"
33 local uci    = require "luci.model.uci"
34
35 local luci  = {}
36 luci.util   = require "luci.util"
37 luci.ip     = require "luci.ip"
38
39 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select =
40         tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
41
42
43 --- LuCI Linux and POSIX system utilities.
44 module "luci.sys"
45
46 --- Execute a given shell command and return the error code
47 -- @class               function
48 -- @name                call
49 -- @param               ...             Command to call
50 -- @return              Error code of the command
51 function call(...)
52         return os.execute(...) / 256
53 end
54
55 --- Execute a given shell command and capture its standard output
56 -- @class               function
57 -- @name                exec
58 -- @param command       Command to call
59 -- @return                      String containg the return the output of the command
60 exec = luci.util.exec
61
62 --- Retrieve information about currently mounted file systems.
63 -- @return      Table containing mount information
64 function mounts()
65         local data = {}
66         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
67         local ps = luci.util.execi("df")
68
69         if not ps then
70                 return
71         else
72                 ps()
73         end
74
75         for line in ps do
76                 local row = {}
77
78                 local j = 1
79                 for value in line:gmatch("[^%s]+") do
80                         row[k[j]] = value
81                         j = j + 1
82                 end
83
84                 if row[k[1]] then
85
86                         -- this is a rather ugly workaround to cope with wrapped lines in
87                         -- the df output:
88                         --
89                         --      /dev/scsi/host0/bus0/target0/lun0/part3
90                         --                   114382024  93566472  15005244  86% /mnt/usb
91                         --
92
93                         if not row[k[2]] then
94                                 j = 2
95                                 line = ps()
96                                 for value in line:gmatch("[^%s]+") do
97                                         row[k[j]] = value
98                                         j = j + 1
99                                 end
100                         end
101
102                         table.insert(data, row)
103                 end
104         end
105
106         return data
107 end
108
109 --- Retrieve environment variables. If no variable is given then a table
110 -- containing the whole environment is returned otherwise this function returns
111 -- the corresponding string value for the given name or nil if no such variable
112 -- exists.
113 -- @class               function
114 -- @name                getenv
115 -- @param var   Name of the environment variable to retrieve (optional)
116 -- @return              String containg the value of the specified variable
117 -- @return              Table containing all variables if no variable name is given
118 getenv = nixio.getenv
119
120 --- Get or set the current hostname.
121 -- @param               String containing a new hostname to set (optional)
122 -- @return              String containing the system hostname
123 function hostname(newname)
124         if type(newname) == "string" and #newname > 0 then
125                 fs.writefile( "/proc/sys/kernel/hostname", newname )
126                 return newname
127         else
128                 return nixio.uname().nodename
129         end
130 end
131
132 --- Returns the contents of a documented referred by an URL.
133 -- @param url    The URL to retrieve
134 -- @param stream Return a stream instead of a buffer
135 -- @param target Directly write to target file name
136 -- @return              String containing the contents of given the URL
137 function httpget(url, stream, target)
138         if not target then
139                 local source = stream and io.popen or luci.util.exec
140                 return source("wget -qO- '"..url:gsub("'", "").."'")
141         else
142                 return os.execute("wget -qO '%s' '%s'" %
143                         {target:gsub("'", ""), url:gsub("'", "")})
144         end
145 end
146
147 --- Returns the system load average values.
148 -- @return      String containing the average load value 1 minute ago
149 -- @return      String containing the average load value 5 minutes ago
150 -- @return      String containing the average load value 15 minutes ago
151 function loadavg()
152         local info = nixio.sysinfo()
153         return info.loads[1], info.loads[2], info.loads[3]
154 end
155
156 --- Initiate a system reboot.
157 -- @return      Return value of os.execute()
158 function reboot()
159         return os.execute("reboot >/dev/null 2>&1")
160 end
161
162 --- Returns the system type, cpu name and installed physical memory.
163 -- @return      String containing the system or platform identifier
164 -- @return      String containing hardware model information
165 -- @return      String containing the total memory amount in kB
166 -- @return      String containing the memory used for caching in kB
167 -- @return      String containing the memory used for buffering in kB
168 -- @return      String containing the free memory amount in kB
169 -- @return      String containing the cpu bogomips (number)
170 function sysinfo()
171         local cpuinfo = fs.readfile("/proc/cpuinfo")
172         local meminfo = fs.readfile("/proc/meminfo")
173
174         local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
175         local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
176         local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
177         local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
178         local bogomips = tonumber(cpuinfo:match("[Bb]ogo[Mm][Ii][Pp][Ss].-: ([^\n]+)")) or 0
179
180         local system =
181                 cpuinfo:match("system type\t+: ([^\n]+)") or
182                 cpuinfo:match("Processor\t+: ([^\n]+)") or
183                 cpuinfo:match("model name\t+: ([^\n]+)")
184
185         local model =
186                 luci.util.pcdata(fs.readfile("/tmp/sysinfo/model")) or
187                 cpuinfo:match("machine\t+: ([^\n]+)") or
188                 cpuinfo:match("Hardware\t+: ([^\n]+)") or
189                 luci.util.pcdata(fs.readfile("/proc/diag/model")) or
190                 nixio.uname().machine or
191                 system
192
193         return system, model, memtotal, memcached, membuffers, memfree, bogomips
194 end
195
196 --- Retrieves the output of the "logread" command.
197 -- @return      String containing the current log buffer
198 function syslog()
199         return luci.util.exec("logread")
200 end
201
202 --- Retrieves the output of the "dmesg" command.
203 -- @return      String containing the current log buffer
204 function dmesg()
205         return luci.util.exec("dmesg")
206 end
207
208 --- Generates a random id with specified length.
209 -- @param bytes Number of bytes for the unique id
210 -- @return              String containing hex encoded id
211 function uniqueid(bytes)
212         local rand = fs.readfile("/dev/urandom", bytes)
213         return rand and nixio.bin.hexlify(rand)
214 end
215
216 --- Returns the current system uptime stats.
217 -- @return      String containing total uptime in seconds
218 function uptime()
219         return nixio.sysinfo().uptime
220 end
221
222
223 --- LuCI system utilities / network related functions.
224 -- @class       module
225 -- @name        luci.sys.net
226 net = {}
227
228 --- Returns the current arp-table entries as two-dimensional table.
229 -- @return      Table of table containing the current arp entries.
230 --                      The following fields are defined for arp entry objects:
231 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
232 function net.arptable(callback)
233         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
234 end
235
236 local function _nethints(what, callback)
237         local _, k, e, mac, ip, name
238         local ifn = { }
239         local hosts = { }
240
241         local function _add(i, ...)
242                 local k = select(i, ...)
243                 if k then
244                         if not hosts[k] then hosts[k] = { } end
245                         hosts[k][1] = select(1, ...) or hosts[k][1]
246                         hosts[k][2] = select(2, ...) or hosts[k][2]
247                         hosts[k][3] = select(3, ...) or hosts[k][3]
248                         hosts[k][4] = select(4, ...) or hosts[k][4]
249                 end
250         end
251
252         if fs.access("/proc/net/arp") then
253                 for e in io.lines("/proc/net/arp") do
254                         ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
255                         if ip and mac then
256                                 _add(what, mac:upper(), ip, nil, nil)
257                         end
258                 end
259         end
260
261         if fs.access("/etc/ethers") then
262                 for e in io.lines("/etc/ethers") do
263                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
264                         if mac and ip then
265                                 _add(what, mac:upper(), ip, nil, nil)
266                         end
267                 end
268         end
269
270         if fs.access("/var/dhcp.leases") then
271                 for e in io.lines("/var/dhcp.leases") do
272                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
273                         if mac and ip then
274                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
275                         end
276                 end
277         end
278
279         for _, e in ipairs(nixio.getifaddrs()) do
280                 if e.name ~= "lo" then
281                         ifn[e.name] = ifn[e.name] or { }
282                         if e.family == "packet" and e.addr and #e.addr == 17 then
283                                 ifn[e.name][1] = e.addr:upper()
284                         elseif e.family == "inet" then
285                                 ifn[e.name][2] = e.addr
286                         elseif e.family == "inet6" then
287                                 ifn[e.name][3] = e.addr
288                         end
289                 end
290         end
291
292         for _, e in pairs(ifn) do
293                 if e[what] and (e[2] or e[3]) then
294                         _add(what, e[1], e[2], e[3], e[4])
295                 end
296         end
297
298         for _, e in luci.util.kspairs(hosts) do
299                 callback(e[1], e[2], e[3], e[4])
300         end
301 end
302
303 --- Returns a two-dimensional table of mac address hints.
304 -- @return  Table of table containing known hosts from various sources.
305 --          Each entry contains the values in the following order:
306 --          [ "mac", "name" ]
307 function net.mac_hints(callback)
308         if callback then
309                 _nethints(1, function(mac, v4, v6, name)
310                         name = name or nixio.getnameinfo(v4 or v6) or v4
311                         if name and name ~= mac then
312                                 callback(mac, name or nixio.getnameinfo(v4 or v6) or v4)
313                         end
314                 end)
315         else
316                 local rv = { }
317                 _nethints(1, function(mac, v4, v6, name)
318                         name = name or nixio.getnameinfo(v4 or v6) or v4
319                         if name and name ~= mac then
320                                 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6) or v4 }
321                         end
322                 end)
323                 return rv
324         end
325 end
326
327 --- Returns a two-dimensional table of IPv4 address hints.
328 -- @return  Table of table containing known hosts from various sources.
329 --          Each entry contains the values in the following order:
330 --          [ "ip", "name" ]
331 function net.ipv4_hints(callback)
332         if callback then
333                 _nethints(2, function(mac, v4, v6, name)
334                         name = name or nixio.getnameinfo(v4) or mac
335                         if name and name ~= v4 then
336                                 callback(v4, name)
337                         end
338                 end)
339         else
340                 local rv = { }
341                 _nethints(2, function(mac, v4, v6, name)
342                         name = name or nixio.getnameinfo(v4) or mac
343                         if name and name ~= v4 then
344                                 rv[#rv+1] = { v4, name }
345                         end
346                 end)
347                 return rv
348         end
349 end
350
351 --- Returns a two-dimensional table of IPv6 address hints.
352 -- @return  Table of table containing known hosts from various sources.
353 --          Each entry contains the values in the following order:
354 --          [ "ip", "name" ]
355 function net.ipv6_hints(callback)
356         if callback then
357                 _nethints(3, function(mac, v4, v6, name)
358                         name = name or nixio.getnameinfo(v6) or mac
359                         if name and name ~= v6 then
360                                 callback(v6, name)
361                         end
362                 end)
363         else
364                 local rv = { }
365                 _nethints(3, function(mac, v4, v6, name)
366                         name = name or nixio.getnameinfo(v6) or mac
367                         if name and name ~= v6 then
368                                 rv[#rv+1] = { v6, name }
369                         end
370                 end)
371                 return rv
372         end
373 end
374
375 --- Returns conntrack information
376 -- @return      Table with the currently tracked IP connections
377 function net.conntrack(callback)
378         local connt = {}
379         if fs.access("/proc/net/nf_conntrack", "r") then
380                 for line in io.lines("/proc/net/nf_conntrack") do
381                         line = line:match "^(.-( [^ =]+=).-)%2"
382                         local entry, flags = _parse_mixed_record(line, " +")
383                         if flags[6] ~= "TIME_WAIT" then
384                                 entry.layer3 = flags[1]
385                                 entry.layer4 = flags[3]
386                                 for i=1, #entry do
387                                         entry[i] = nil
388                                 end
389
390                                 if callback then
391                                         callback(entry)
392                                 else
393                                         connt[#connt+1] = entry
394                                 end
395                         end
396                 end
397         elseif fs.access("/proc/net/ip_conntrack", "r") then
398                 for line in io.lines("/proc/net/ip_conntrack") do
399                         line = line:match "^(.-( [^ =]+=).-)%2"
400                         local entry, flags = _parse_mixed_record(line, " +")
401                         if flags[4] ~= "TIME_WAIT" then
402                                 entry.layer3 = "ipv4"
403                                 entry.layer4 = flags[1]
404                                 for i=1, #entry do
405                                         entry[i] = nil
406                                 end
407
408                                 if callback then
409                                         callback(entry)
410                                 else
411                                         connt[#connt+1] = entry
412                                 end
413                         end
414                 end
415         else
416                 return nil
417         end
418         return connt
419 end
420
421 --- Determine the current IPv4 default route. If multiple default routes exist,
422 -- return the one with the lowest metric.
423 -- @return      Table with the properties of the current default route.
424 --                      The following fields are defined:
425 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
426 --                        "flags", "device" }
427 function net.defaultroute()
428         local route
429
430         net.routes(function(rt)
431                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
432                         route = rt
433                 end
434         end)
435
436         return route
437 end
438
439 --- Determine the current IPv6 default route. If multiple default routes exist,
440 -- return the one with the lowest metric.
441 -- @return      Table with the properties of the current default route.
442 --                      The following fields are defined:
443 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
444 --                        "flags", "device" }
445 function net.defaultroute6()
446         local route
447
448         net.routes6(function(rt)
449                 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
450                    (not route or route.metric > rt.metric)
451                 then
452                         route = rt
453                 end
454         end)
455
456         if not route then
457                 local global_unicast = luci.ip.IPv6("2000::/3")
458                 net.routes6(function(rt)
459                         if rt.dest:equal(global_unicast) and
460                            (not route or route.metric > rt.metric)
461                         then
462                                 route = rt
463                         end
464                 end)
465         end
466
467         return route
468 end
469
470 --- Determine the names of available network interfaces.
471 -- @return      Table containing all current interface names
472 function net.devices()
473         local devs = {}
474         for k, v in ipairs(nixio.getifaddrs()) do
475                 if v.family == "packet" then
476                         devs[#devs+1] = v.name
477                 end
478         end
479         return devs
480 end
481
482
483 --- Return information about available network interfaces.
484 -- @return      Table containing all current interface names and their information
485 function net.deviceinfo()
486         local devs = {}
487         for k, v in ipairs(nixio.getifaddrs()) do
488                 if v.family == "packet" then
489                         local d = v.data
490                         d[1] = d.rx_bytes
491                         d[2] = d.rx_packets
492                         d[3] = d.rx_errors
493                         d[4] = d.rx_dropped
494                         d[5] = 0
495                         d[6] = 0
496                         d[7] = 0
497                         d[8] = d.multicast
498                         d[9] = d.tx_bytes
499                         d[10] = d.tx_packets
500                         d[11] = d.tx_errors
501                         d[12] = d.tx_dropped
502                         d[13] = 0
503                         d[14] = d.collisions
504                         d[15] = 0
505                         d[16] = 0
506                         devs[v.name] = d
507                 end
508         end
509         return devs
510 end
511
512
513 -- Determine the MAC address belonging to the given IP address.
514 -- @param ip    IPv4 address
515 -- @return              String containing the MAC address or nil if it cannot be found
516 function net.ip4mac(ip)
517         local mac = nil
518         net.arptable(function(e)
519                 if e["IP address"] == ip then
520                         mac = e["HW address"]
521                 end
522         end)
523         return mac
524 end
525
526 --- Returns the current kernel routing table entries.
527 -- @return      Table of tables with properties of the corresponding routes.
528 --                      The following fields are defined for route entry tables:
529 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
530 --                        "flags", "device" }
531 function net.routes(callback)
532         local routes = { }
533
534         for line in io.lines("/proc/net/route") do
535
536                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
537                           dst_mask, mtu, win, irtt = line:match(
538                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
539                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
540                 )
541
542                 if dev then
543                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
544                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
545                         dst_ip   = luci.ip.Hex(
546                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
547                         )
548
549                         local rt = {
550                                 dest     = dst_ip,
551                                 gateway  = gateway,
552                                 metric   = tonumber(metric),
553                                 refcount = tonumber(refcnt),
554                                 usecount = tonumber(usecnt),
555                                 mtu      = tonumber(mtu),
556                                 window   = tonumber(window),
557                                 irtt     = tonumber(irtt),
558                                 flags    = tonumber(flags, 16),
559                                 device   = dev
560                         }
561
562                         if callback then
563                                 callback(rt)
564                         else
565                                 routes[#routes+1] = rt
566                         end
567                 end
568         end
569
570         return routes
571 end
572
573 --- Returns the current ipv6 kernel routing table entries.
574 -- @return      Table of tables with properties of the corresponding routes.
575 --                      The following fields are defined for route entry tables:
576 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
577 --                        "flags", "device" }
578 function net.routes6(callback)
579         if fs.access("/proc/net/ipv6_route", "r") then
580                 local routes = { }
581
582                 for line in io.lines("/proc/net/ipv6_route") do
583
584                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
585                                   metric, refcnt, usecnt, flags, dev = line:match(
586                                 "([a-f0-9]+) ([a-f0-9]+) " ..
587                                 "([a-f0-9]+) ([a-f0-9]+) " ..
588                                 "([a-f0-9]+) ([a-f0-9]+) " ..
589                                 "([a-f0-9]+) ([a-f0-9]+) " ..
590                                 "([a-f0-9]+) +([^%s]+)"
591                         )
592
593                         if dst_ip and dst_prefix and
594                            src_ip and src_prefix and
595                            nexthop and metric and
596                            refcnt and usecnt and
597                            flags and dev
598                         then
599                                 src_ip = luci.ip.Hex(
600                                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
601                                 )
602
603                                 dst_ip = luci.ip.Hex(
604                                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
605                                 )
606
607                                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
608
609                                 local rt = {
610                                         source   = src_ip,
611                                         dest     = dst_ip,
612                                         nexthop  = nexthop,
613                                         metric   = tonumber(metric, 16),
614                                         refcount = tonumber(refcnt, 16),
615                                         usecount = tonumber(usecnt, 16),
616                                         flags    = tonumber(flags, 16),
617                                         device   = dev,
618
619                                         -- lua number is too small for storing the metric
620                                         -- add a metric_raw field with the original content
621                                         metric_raw = metric
622                                 }
623
624                                 if callback then
625                                         callback(rt)
626                                 else
627                                         routes[#routes+1] = rt
628                                 end
629                         end
630                 end
631
632                 return routes
633         end
634 end
635
636 --- Tests whether the given host responds to ping probes.
637 -- @param host  String containing a hostname or IPv4 address
638 -- @return              Number containing 0 on success and >= 1 on error
639 function net.pingtest(host)
640         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
641 end
642
643
644 --- LuCI system utilities / process related functions.
645 -- @class       module
646 -- @name        luci.sys.process
647 process = {}
648
649 --- Get the current process id.
650 -- @class function
651 -- @name  process.info
652 -- @return      Number containing the current pid
653 function process.info(key)
654         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
655         return not key and s or s[key]
656 end
657
658 --- Retrieve information about currently running processes.
659 -- @return      Table containing process information
660 function process.list()
661         local data = {}
662         local k
663         local ps = luci.util.execi("top -bn1")
664
665         if not ps then
666                 return
667         end
668
669         while true do
670                 local line = ps()
671                 if not line then
672                         return
673                 end
674
675                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
676                 if k[6] == "%VSZ" then
677                         k[6] = "%MEM"
678                 end
679                 if k[1] == "PID" then
680                         break
681                 end
682         end
683
684         for line in ps do
685                 local row = {}
686
687                 line = luci.util.trim(line)
688                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
689                         row[k[i]] = value
690                 end
691
692                 local pid = tonumber(row[k[1]])
693                 if pid then
694                         data[pid] = row
695                 end
696         end
697
698         return data
699 end
700
701 --- Set the gid of a process identified by given pid.
702 -- @param gid   Number containing the Unix group id
703 -- @return              Boolean indicating successful operation
704 -- @return              String containing the error message if failed
705 -- @return              Number containing the error code if failed
706 function process.setgroup(gid)
707         return nixio.setgid(gid)
708 end
709
710 --- Set the uid of a process identified by given pid.
711 -- @param uid   Number containing the Unix user id
712 -- @return              Boolean indicating successful operation
713 -- @return              String containing the error message if failed
714 -- @return              Number containing the error code if failed
715 function process.setuser(uid)
716         return nixio.setuid(uid)
717 end
718
719 --- Send a signal to a process identified by given pid.
720 -- @class function
721 -- @name  process.signal
722 -- @param pid   Number containing the process id
723 -- @param sig   Signal to send (default: 15 [SIGTERM])
724 -- @return              Boolean indicating successful operation
725 -- @return              Number containing the error code if failed
726 process.signal = nixio.kill
727
728
729 --- LuCI system utilities / user related functions.
730 -- @class       module
731 -- @name        luci.sys.user
732 user = {}
733
734 --- Retrieve user informations for given uid.
735 -- @class               function
736 -- @name                getuser
737 -- @param uid   Number containing the Unix user id
738 -- @return              Table containing the following fields:
739 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
740 user.getuser = nixio.getpw
741
742 --- Retrieve the current user password hash.
743 -- @param username      String containing the username to retrieve the password for
744 -- @return                      String containing the hash or nil if no password is set.
745 -- @return                      Password database entry
746 function user.getpasswd(username)
747         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
748         local pwh = pwe and (pwe.pwdp or pwe.passwd)
749         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
750                 return nil, pwe
751         else
752                 return pwh, pwe
753         end
754 end
755
756 --- Test whether given string matches the password of a given system user.
757 -- @param username      String containing the Unix user name
758 -- @param pass          String containing the password to compare
759 -- @return                      Boolean indicating wheather the passwords are equal
760 function user.checkpasswd(username, pass)
761         local pwh, pwe = user.getpasswd(username)
762         if pwe then
763                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
764         end
765         return false
766 end
767
768 --- Change the password of given user.
769 -- @param username      String containing the Unix user name
770 -- @param password      String containing the password to compare
771 -- @return                      Number containing 0 on success and >= 1 on error
772 function user.setpasswd(username, password)
773         if password then
774                 password = password:gsub("'", [['"'"']])
775         end
776
777         if username then
778                 username = username:gsub("'", [['"'"']])
779         end
780
781         return os.execute(
782                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
783                 "passwd '" .. username .. "' >/dev/null 2>&1"
784         )
785 end
786
787
788 --- LuCI system utilities / wifi related functions.
789 -- @class       module
790 -- @name        luci.sys.wifi
791 wifi = {}
792
793 --- Get wireless information for given interface.
794 -- @param ifname        String containing the interface name
795 -- @return              A wrapped iwinfo object instance
796 function wifi.getiwinfo(ifname)
797         local stat, iwinfo = pcall(require, "iwinfo")
798
799         if ifname then
800                 local c = 0
801                 local u = uci.cursor_state()
802                 local d, n = ifname:match("^(%w+)%.network(%d+)")
803                 if d and n then
804                         n = tonumber(n)
805                         u:foreach("wireless", "wifi-iface",
806                                 function(s)
807                                         if s.device == d then
808                                                 c = c + 1
809                                                 if c == n then
810                                                         ifname = s.ifname or s.device
811                                                         return false
812                                                 end
813                                         end
814                                 end)
815                 elseif u:get("wireless", ifname) == "wifi-device" then
816                         u:foreach("wireless", "wifi-iface",
817                                 function(s)
818                                         if s.device == ifname and s.ifname then
819                                                 ifname = s.ifname
820                                                 return false
821                                         end
822                                 end)
823                 end
824
825                 local t = stat and iwinfo.type(ifname)
826                 local x = t and iwinfo[t] or { }
827                 return setmetatable({}, {
828                         __index = function(t, k)
829                                 if k == "ifname" then
830                                         return ifname
831                                 elseif x[k] then
832                                         return x[k](ifname)
833                                 end
834                         end
835                 })
836         end
837 end
838
839 --- Get iwconfig output for all wireless devices.
840 -- @return      Table of tables containing the iwconfing output for each wifi device
841 function wifi.getiwconfig()
842         local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
843         local iwc = {}
844
845         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
846                 local k = l:match("^(.-) ")
847                 l = l:gsub("^(.-) +", "", 1)
848                 if k then
849                         local entry, flags = _parse_mixed_record(l)
850                         if entry then
851                                 entry.flags = flags
852                         end
853                         iwc[k] = entry
854                 end
855         end
856
857         return iwc
858 end
859
860 --- Get iwlist scan output from all wireless devices.
861 -- @return      Table of tables contaiing all scan results
862 function wifi.iwscan(iface)
863         local siface = iface or ""
864         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
865         local iws = {}
866
867         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
868                 local k = l:match("^(.-) ")
869                 l = l:gsub("^[^\n]+", "", 1)
870                 l = luci.util.trim(l)
871                 if k then
872                         iws[k] = {}
873                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
874                                 c = c:gsub("^(.-)- ", "", 1)
875                                 c = luci.util.split(c, "\n", 7)
876                                 c = table.concat(c, "\n", 1)
877                                 local entry, flags = _parse_mixed_record(c)
878                                 if entry then
879                                         entry.flags = flags
880                                 end
881                                 table.insert(iws[k], entry)
882                         end
883                 end
884         end
885
886         return iface and (iws[iface] or {}) or iws
887 end
888
889 --- Get available channels from given wireless iface.
890 -- @param iface Wireless interface (optional)
891 -- @return              Table of available channels
892 function wifi.channels(iface)
893         local stat, iwinfo = pcall(require, "iwinfo")
894         local cns
895
896         if stat then
897                 local t = iwinfo.type(iface or "")
898                 if iface and t and iwinfo[t] then
899                         cns = iwinfo[t].freqlist(iface)
900                 end
901         end
902
903         if not cns or #cns == 0 then
904                 cns = {
905                         {channel =  1, mhz = 2412},
906                         {channel =  2, mhz = 2417},
907                         {channel =  3, mhz = 2422},
908                         {channel =  4, mhz = 2427},
909                         {channel =  5, mhz = 2432},
910                         {channel =  6, mhz = 2437},
911                         {channel =  7, mhz = 2442},
912                         {channel =  8, mhz = 2447},
913                         {channel =  9, mhz = 2452},
914                         {channel = 10, mhz = 2457},
915                         {channel = 11, mhz = 2462}
916                 }
917         end
918
919         return cns
920 end
921
922
923 --- LuCI system utilities / init related functions.
924 -- @class       module
925 -- @name        luci.sys.init
926 init = {}
927 init.dir = "/etc/init.d/"
928
929 --- Get the names of all installed init scripts
930 -- @return      Table containing the names of all inistalled init scripts
931 function init.names()
932         local names = { }
933         for name in fs.glob(init.dir.."*") do
934                 names[#names+1] = fs.basename(name)
935         end
936         return names
937 end
938
939 --- Get the index of he given init script
940 -- @param name  Name of the init script
941 -- @return              Numeric index value
942 function init.index(name)
943         if fs.access(init.dir..name) then
944                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
945                         %{ init.dir, name })
946         end
947 end
948
949 local function init_action(action, name)
950         if fs.access(init.dir..name) then
951                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
952         end
953 end
954
955 --- Test whether the given init script is enabled
956 -- @param name  Name of the init script
957 -- @return              Boolean indicating whether init is enabled
958 function init.enabled(name)
959         return (init_action("enabled", name) == 0)
960 end
961
962 --- Enable the given init script
963 -- @param name  Name of the init script
964 -- @return              Boolean indicating success
965 function init.enable(name)
966         return (init_action("enable", name) == 1)
967 end
968
969 --- Disable the given init script
970 -- @param name  Name of the init script
971 -- @return              Boolean indicating success
972 function init.disable(name)
973         return (init_action("disable", name) == 0)
974 end
975
976 --- Start the given init script
977 -- @param name  Name of the init script
978 -- @return              Boolean indicating success
979 function init.start(name)
980         return (init_action("start", name) == 0)
981 end
982
983 --- Stop the given init script
984 -- @param name  Name of the init script
985 -- @return              Boolean indicating success
986 function init.stop(name)
987         return (init_action("stop", name) == 0)
988 end
989
990
991 -- Internal functions
992
993 function _parse_delimited_table(iter, delimiter, callback)
994         delimiter = delimiter or "%s+"
995
996         local data  = {}
997         local trim  = luci.util.trim
998         local split = luci.util.split
999
1000         local keys = split(trim(iter()), delimiter, nil, true)
1001         for i, j in pairs(keys) do
1002                 keys[i] = trim(keys[i])
1003         end
1004
1005         for line in iter do
1006                 local row = {}
1007                 line = trim(line)
1008                 if #line > 0 then
1009                         for i, j in pairs(split(line, delimiter, nil, true)) do
1010                                 if keys[i] then
1011                                         row[keys[i]] = j
1012                                 end
1013                         end
1014                 end
1015
1016                 if callback then
1017                         callback(row)
1018                 else
1019                         data[#data+1] = row
1020                 end
1021         end
1022
1023         return data
1024 end
1025
1026 function _parse_mixed_record(cnt, delimiter)
1027         delimiter = delimiter or "  "
1028         local data = {}
1029         local flags = {}
1030
1031         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
1032                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
1033                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
1034
1035                         if k then
1036                                 if x == "" then
1037                                         table.insert(flags, k)
1038                                 else
1039                                         data[k] = v
1040                                 end
1041                         end
1042                 end
1043         end
1044
1045         return data, flags
1046 end