luci-base: harden cookie sysauth=
[project/luci.git] / modules / luci-base / luasrc / sys.lua
index 292cc96..823e207 100644 (file)
@@ -1,29 +1,5 @@
---[[
-LuCI - System library
-
-Description:
-Utilities for interaction with the Linux system
-
-FileId:
-$Id$
-
-License:
-Copyright 2008 Steven Barth <steven@midlink.org>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-]]--
-
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
 
 local io     = require "io"
 local os     = require "os"
@@ -31,6 +7,7 @@ local table  = require "table"
 local nixio  = require "nixio"
 local fs     = require "nixio.fs"
 local uci    = require "luci.model.uci"
+local ntm    = require "luci.model.network"
 
 local luci  = {}
 luci.util   = require "luci.util"
@@ -40,27 +17,14 @@ local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
        tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
 
 
---- LuCI Linux and POSIX system utilities.
 module "luci.sys"
 
---- Execute a given shell command and return the error code
--- @class              function
--- @name               call
--- @param              ...             Command to call
--- @return             Error code of the command
 function call(...)
        return os.execute(...) / 256
 end
 
---- Execute a given shell command and capture its standard output
--- @class              function
--- @name               exec
--- @param command      Command to call
--- @return                     String containg the return the output of the command
 exec = luci.util.exec
 
---- Retrieve information about currently mounted file systems.
--- @return     Table containing mount information
 function mounts()
        local data = {}
        local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
@@ -106,20 +70,11 @@ function mounts()
        return data
 end
 
---- Retrieve environment variables. If no variable is given then a table
 -- containing the whole environment is returned otherwise this function returns
 -- the corresponding string value for the given name or nil if no such variable
 -- exists.
--- @class              function
--- @name               getenv
--- @param var  Name of the environment variable to retrieve (optional)
--- @return             String containg the value of the specified variable
--- @return             Table containing all variables if no variable name is given
 getenv = nixio.getenv
 
---- Get or set the current hostname.
--- @param              String containing a new hostname to set (optional)
--- @return             String containing the system hostname
 function hostname(newname)
        if type(newname) == "string" and #newname > 0 then
                fs.writefile( "/proc/sys/kernel/hostname", newname )
@@ -129,100 +84,46 @@ function hostname(newname)
        end
 end
 
---- Returns the contents of a documented referred by an URL.
--- @param url   The URL to retrieve
--- @param stream Return a stream instead of a buffer
--- @param target Directly write to target file name
--- @return             String containing the contents of given the URL
 function httpget(url, stream, target)
        if not target then
                local source = stream and io.popen or luci.util.exec
-               return source("wget -qO- '"..url:gsub("'", "").."'")
+               return source("wget -qO- %s" % luci.util.shellquote(url))
        else
-               return os.execute("wget -qO '%s' '%s'" %
-                       {target:gsub("'", ""), url:gsub("'", "")})
+               return os.execute("wget -qO %s %s" %
+                       {luci.util.shellquote(target), luci.util.shellquote(url)})
        end
 end
 
---- Initiate a system reboot.
--- @return     Return value of os.execute()
 function reboot()
        return os.execute("reboot >/dev/null 2>&1")
 end
 
---- Retrieves the output of the "logread" command.
--- @return     String containing the current log buffer
 function syslog()
        return luci.util.exec("logread")
 end
 
---- Retrieves the output of the "dmesg" command.
--- @return     String containing the current log buffer
 function dmesg()
        return luci.util.exec("dmesg")
 end
 
---- Generates a random id with specified length.
--- @param bytes        Number of bytes for the unique id
--- @return             String containing hex encoded id
 function uniqueid(bytes)
        local rand = fs.readfile("/dev/urandom", bytes)
        return rand and nixio.bin.hexlify(rand)
 end
 
---- Returns the current system uptime stats.
--- @return     String containing total uptime in seconds
 function uptime()
        return nixio.sysinfo().uptime
 end
 
 
---- LuCI system utilities / network related functions.
--- @class      module
--- @name       luci.sys.net
 net = {}
 
---- Returns the current arp-table entries as two-dimensional table.
--- @return     Table of table containing the current arp entries.
---                     The following fields are defined for arp entry objects:
---                     { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
-function net.arptable(callback)
-       local arp = (not callback) and {} or nil
-       local e, r, v
-       if fs.access("/proc/net/arp") then
-               for e in io.lines("/proc/net/arp") do
-                       local r = { }, v
-                       for v in e:gmatch("%S+") do
-                               r[#r+1] = v
-                       end
-
-                       if r[1] ~= "IP" then
-                               local x = {
-                                       ["IP address"] = r[1],
-                                       ["HW type"]    = r[2],
-                                       ["Flags"]      = r[3],
-                                       ["HW address"] = r[4],
-                                       ["Mask"]       = r[5],
-                                       ["Device"]     = r[6]
-                               }
-
-                               if callback then
-                                       callback(x)
-                               else
-                                       arp = arp or { }
-                                       arp[#arp+1] = x
-                               end
-                       end
-               end
-       end
-       return arp
-end
-
 local function _nethints(what, callback)
        local _, k, e, mac, ip, name
        local cur = uci.cursor()
        local ifn = { }
        local hosts = { }
+       local lookup = { }
 
        local function _add(i, ...)
                local k = select(i, ...)
@@ -235,37 +136,49 @@ local function _nethints(what, callback)
                end
        end
 
-       if fs.access("/proc/net/arp") then
-               for e in io.lines("/proc/net/arp") do
-                       ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
-                       if ip and mac then
-                               _add(what, mac:upper(), ip, nil, nil)
-                       end
+       luci.ip.neighbors(nil, function(neigh)
+               if neigh.mac and neigh.family == 4 then
+                       _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
+               elseif neigh.mac and neigh.family == 6 then
+                       _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
                end
-       end
+       end)
 
        if fs.access("/etc/ethers") then
                for e in io.lines("/etc/ethers") do
-                       mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
-                       if mac and ip then
-                               _add(what, mac:upper(), ip, nil, nil)
+                       mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
+                       mac = luci.ip.checkmac(mac)
+                       if mac and name then
+                               if luci.ip.checkip4(name) then
+                                       _add(what, mac, name, nil, nil)
+                               else
+                                       _add(what, mac, nil, nil, name)
+                               end
                        end
                end
        end
 
-       if fs.access("/var/dhcp.leases") then
-               for e in io.lines("/var/dhcp.leases") do
-                       mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
-                       if mac and ip then
-                               _add(what, mac:upper(), ip, nil, name ~= "*" and name)
+       cur:foreach("dhcp", "dnsmasq",
+               function(s)
+                       if s.leasefile and fs.access(s.leasefile) then
+                               for e in io.lines(s.leasefile) do
+                                       mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
+                                       mac = luci.ip.checkmac(mac)
+                                       if mac and ip then
+                                               _add(what, mac, ip, nil, name ~= "*" and name)
+                                       end
+                               end
                        end
                end
-       end
+       )
 
        cur:foreach("dhcp", "host",
                function(s)
                        for mac in luci.util.imatch(s.mac) do
-                               _add(what, mac:upper(), s.ip, nil, s.name)
+                               mac = luci.ip.checkmac(mac)
+                               if mac then
+                                       _add(what, mac, s.ip, nil, s.name)
+                               end
                        end
                end)
 
@@ -288,43 +201,51 @@ local function _nethints(what, callback)
                end
        end
 
+       for _, e in pairs(hosts) do
+               lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
+       end
+
+       if #lookup > 0 then
+               lookup = luci.util.ubus("network.rrdns", "lookup", {
+                       addrs   = lookup,
+                       timeout = 250,
+                       limit   = 1000
+               }) or { }
+       end
+
        for _, e in luci.util.kspairs(hosts) do
-               callback(e[1], e[2], e[3], e[4])
+               callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
        end
 end
 
---- Returns a two-dimensional table of mac address hints.
--- @return  Table of table containing known hosts from various sources.
 --          Each entry contains the values in the following order:
 --          [ "mac", "name" ]
 function net.mac_hints(callback)
        if callback then
                _nethints(1, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
+                       name = name or v4
                        if name and name ~= mac then
-                               callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
+                               callback(mac, name or v4)
                        end
                end)
        else
                local rv = { }
                _nethints(1, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
+                       name = name or v4
                        if name and name ~= mac then
-                               rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
+                               rv[#rv+1] = { mac, name or v4 }
                        end
                end)
                return rv
        end
 end
 
---- Returns a two-dimensional table of IPv4 address hints.
--- @return  Table of table containing known hosts from various sources.
 --          Each entry contains the values in the following order:
 --          [ "ip", "name" ]
 function net.ipv4_hints(callback)
        if callback then
                _nethints(2, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v4, nil, 100) or mac
+                       name = name or mac
                        if name and name ~= v4 then
                                callback(v4, name)
                        end
@@ -332,7 +253,7 @@ function net.ipv4_hints(callback)
        else
                local rv = { }
                _nethints(2, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v4, nil, 100) or mac
+                       name = name or mac
                        if name and name ~= v4 then
                                rv[#rv+1] = { v4, name }
                        end
@@ -341,14 +262,12 @@ function net.ipv4_hints(callback)
        end
 end
 
---- Returns a two-dimensional table of IPv6 address hints.
--- @return  Table of table containing known hosts from various sources.
 --          Each entry contains the values in the following order:
 --          [ "ip", "name" ]
 function net.ipv6_hints(callback)
        if callback then
                _nethints(3, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v6, nil, 100) or mac
+                       name = name or mac
                        if name and name ~= v6 then
                                callback(v6, name)
                        end
@@ -356,7 +275,7 @@ function net.ipv6_hints(callback)
        else
                local rv = { }
                _nethints(3, function(mac, v4, v6, name)
-                       name = name or nixio.getnameinfo(v6, nil, 100) or mac
+                       name = name or mac
                        if name and name ~= v6 then
                                rv[#rv+1] = { v6, name }
                        end
@@ -365,107 +284,84 @@ function net.ipv6_hints(callback)
        end
 end
 
---- Returns conntrack information
--- @return     Table with the currently tracked IP connections
-function net.conntrack(callback)
-       local connt = {}
-       if fs.access("/proc/net/nf_conntrack", "r") then
-               for line in io.lines("/proc/net/nf_conntrack") do
-                       line = line:match "^(.-( [^ =]+=).-)%2"
-                       local entry, flags = _parse_mixed_record(line, " +")
-                       if flags[6] ~= "TIME_WAIT" then
-                               entry.layer3 = flags[1]
-                               entry.layer4 = flags[3]
-                               for i=1, #entry do
-                                       entry[i] = nil
-                               end
-
-                               if callback then
-                                       callback(entry)
-                               else
-                                       connt[#connt+1] = entry
-                               end
-                       end
-               end
-       elseif fs.access("/proc/net/ip_conntrack", "r") then
-               for line in io.lines("/proc/net/ip_conntrack") do
-                       line = line:match "^(.-( [^ =]+=).-)%2"
-                       local entry, flags = _parse_mixed_record(line, " +")
-                       if flags[4] ~= "TIME_WAIT" then
-                               entry.layer3 = "ipv4"
-                               entry.layer4 = flags[1]
-                               for i=1, #entry do
-                                       entry[i] = nil
-                               end
-
-                               if callback then
-                                       callback(entry)
-                               else
-                                       connt[#connt+1] = entry
-                               end
+function net.host_hints(callback)
+       if callback then
+               _nethints(1, function(mac, v4, v6, name)
+                       if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
+                               callback(mac, v4, v6, name)
                        end
-               end
+               end)
        else
-               return nil
+               local rv = { }
+               _nethints(1, function(mac, v4, v6, name)
+                       if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
+                               local e = { }
+                               if v4   then e.ipv4 = v4   end
+                               if v6   then e.ipv6 = v6   end
+                               if name then e.name = name end
+                               rv[mac] = e
+                       end
+               end)
+               return rv
        end
-       return connt
 end
 
---- Determine the current IPv4 default route. If multiple default routes exist,
--- return the one with the lowest metric.
--- @return     Table with the properties of the current default route.
---                     The following fields are defined:
---                     { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
---                       "flags", "device" }
-function net.defaultroute()
-       local route
+function net.conntrack(callback)
+       local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
+       if not ok or not nfct then
+               return nil
+       end
 
-       net.routes(function(rt)
-               if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
-                       route = rt
-               end
-       end)
+       local line, connt = nil, (not callback) and { }
+       for line in nfct do
+               local fam, l3, l4, timeout, tuples =
+                       line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
 
-       return route
-end
+               if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
+                       l4 = nixio.getprotobynumber(l4)
 
---- Determine the current IPv6 default route. If multiple default routes exist,
--- return the one with the lowest metric.
--- @return     Table with the properties of the current default route.
---                     The following fields are defined:
---                     { "source", "dest", "nexthop", "metric", "refcount", "usecount",
---                       "flags", "device" }
-function net.defaultroute6()
-       local route
+                       local entry = {
+                               bytes = 0,
+                               packets = 0,
+                               layer3 = fam,
+                               layer4 = l4 and l4.name or "unknown",
+                               timeout = tonumber(timeout, 10)
+                       }
 
-       net.routes6(function(rt)
-               if rt.dest:prefix() == 0 and rt.device ~= "lo" and
-                  (not route or route.metric > rt.metric)
-               then
-                       route = rt
-               end
-       end)
+                       local key, val
+                       for key, val in tuples:gmatch("(%w+)=(%S+)") do
+                               if key == "bytes" or key == "packets" then
+                                       entry[key] = entry[key] + tonumber(val, 10)
+                               elseif key == "src" or key == "dst" then
+                                       if entry[key] == nil then
+                                               entry[key] = luci.ip.new(val):string()
+                                       end
+                               elseif key == "sport" or key == "dport" then
+                                       if entry[key] == nil then
+                                               entry[key] = val
+                                       end
+                               elseif val then
+                                       entry[key] = val
+                               end
+                       end
 
-       if not route then
-               local global_unicast = luci.ip.IPv6("2000::/3")
-               net.routes6(function(rt)
-                       if rt.dest:equal(global_unicast) and
-                          (not route or route.metric > rt.metric)
-                       then
-                               route = rt
+                       if callback then
+                               callback(entry)
+                       else
+                               connt[#connt+1] = entry
                        end
-               end)
+               end
        end
 
-       return route
+       return callback and true or connt
 end
 
---- Determine the names of available network interfaces.
--- @return     Table containing all current interface names
 function net.devices()
        local devs = {}
+       local seen = {}
        for k, v in ipairs(nixio.getifaddrs()) do
-               if v.family == "packet" then
+               if v.name and not seen[v.name] then
+                       seen[v.name] = true
                        devs[#devs+1] = v.name
                end
        end
@@ -473,183 +369,13 @@ function net.devices()
 end
 
 
---- Return information about available network interfaces.
--- @return     Table containing all current interface names and their information
-function net.deviceinfo()
-       local devs = {}
-       for k, v in ipairs(nixio.getifaddrs()) do
-               if v.family == "packet" then
-                       local d = v.data
-                       d[1] = d.rx_bytes
-                       d[2] = d.rx_packets
-                       d[3] = d.rx_errors
-                       d[4] = d.rx_dropped
-                       d[5] = 0
-                       d[6] = 0
-                       d[7] = 0
-                       d[8] = d.multicast
-                       d[9] = d.tx_bytes
-                       d[10] = d.tx_packets
-                       d[11] = d.tx_errors
-                       d[12] = d.tx_dropped
-                       d[13] = 0
-                       d[14] = d.collisions
-                       d[15] = 0
-                       d[16] = 0
-                       devs[v.name] = d
-               end
-       end
-       return devs
-end
-
-
--- Determine the MAC address belonging to the given IP address.
--- @param ip   IPv4 address
--- @return             String containing the MAC address or nil if it cannot be found
-function net.ip4mac(ip)
-       local mac = nil
-       net.arptable(function(e)
-               if e["IP address"] == ip then
-                       mac = e["HW address"]
-               end
-       end)
-       return mac
-end
-
---- Returns the current kernel routing table entries.
--- @return     Table of tables with properties of the corresponding routes.
---                     The following fields are defined for route entry tables:
---                     { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
---                       "flags", "device" }
-function net.routes(callback)
-       local routes = { }
-
-       for line in io.lines("/proc/net/route") do
-
-               local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
-                         dst_mask, mtu, win, irtt = line:match(
-                       "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
-                       "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
-               )
-
-               if dev then
-                       gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
-                       dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
-                       dst_ip   = luci.ip.Hex(
-                               dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
-                       )
-
-                       local rt = {
-                               dest     = dst_ip,
-                               gateway  = gateway,
-                               metric   = tonumber(metric),
-                               refcount = tonumber(refcnt),
-                               usecount = tonumber(usecnt),
-                               mtu      = tonumber(mtu),
-                               window   = tonumber(window),
-                               irtt     = tonumber(irtt),
-                               flags    = tonumber(flags, 16),
-                               device   = dev
-                       }
-
-                       if callback then
-                               callback(rt)
-                       else
-                               routes[#routes+1] = rt
-                       end
-               end
-       end
-
-       return routes
-end
-
---- Returns the current ipv6 kernel routing table entries.
--- @return     Table of tables with properties of the corresponding routes.
---                     The following fields are defined for route entry tables:
---                     { "source", "dest", "nexthop", "metric", "refcount", "usecount",
---                       "flags", "device" }
-function net.routes6(callback)
-       if fs.access("/proc/net/ipv6_route", "r") then
-               local routes = { }
-
-               for line in io.lines("/proc/net/ipv6_route") do
-
-                       local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
-                                 metric, refcnt, usecnt, flags, dev = line:match(
-                               "([a-f0-9]+) ([a-f0-9]+) " ..
-                               "([a-f0-9]+) ([a-f0-9]+) " ..
-                               "([a-f0-9]+) ([a-f0-9]+) " ..
-                               "([a-f0-9]+) ([a-f0-9]+) " ..
-                               "([a-f0-9]+) +([^%s]+)"
-                       )
-
-                       if dst_ip and dst_prefix and
-                          src_ip and src_prefix and
-                          nexthop and metric and
-                          refcnt and usecnt and
-                          flags and dev
-                       then
-                               src_ip = luci.ip.Hex(
-                                       src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
-                               )
-
-                               dst_ip = luci.ip.Hex(
-                                       dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
-                               )
-
-                               nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
-
-                               local rt = {
-                                       source   = src_ip,
-                                       dest     = dst_ip,
-                                       nexthop  = nexthop,
-                                       metric   = tonumber(metric, 16),
-                                       refcount = tonumber(refcnt, 16),
-                                       usecount = tonumber(usecnt, 16),
-                                       flags    = tonumber(flags, 16),
-                                       device   = dev,
-
-                                       -- lua number is too small for storing the metric
-                                       -- add a metric_raw field with the original content
-                                       metric_raw = metric
-                               }
-
-                               if callback then
-                                       callback(rt)
-                               else
-                                       routes[#routes+1] = rt
-                               end
-                       end
-               end
-
-               return routes
-       end
-end
-
---- Tests whether the given host responds to ping probes.
--- @param host String containing a hostname or IPv4 address
--- @return             Number containing 0 on success and >= 1 on error
-function net.pingtest(host)
-       return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
-end
-
-
---- LuCI system utilities / process related functions.
--- @class      module
--- @name       luci.sys.process
 process = {}
 
---- Get the current process id.
--- @class function
--- @name  process.info
--- @return     Number containing the current pid
 function process.info(key)
        local s = {uid = nixio.getuid(), gid = nixio.getgid()}
        return not key and s or s[key]
 end
 
---- Retrieve information about currently running processes.
--- @return     Table containing process information
 function process.list()
        local data = {}
        local k
@@ -682,51 +408,22 @@ function process.list()
        return data
 end
 
---- Set the gid of a process identified by given pid.
--- @param gid  Number containing the Unix group id
--- @return             Boolean indicating successful operation
--- @return             String containing the error message if failed
--- @return             Number containing the error code if failed
 function process.setgroup(gid)
        return nixio.setgid(gid)
 end
 
---- Set the uid of a process identified by given pid.
--- @param uid  Number containing the Unix user id
--- @return             Boolean indicating successful operation
--- @return             String containing the error message if failed
--- @return             Number containing the error code if failed
 function process.setuser(uid)
        return nixio.setuid(uid)
 end
 
---- Send a signal to a process identified by given pid.
--- @class function
--- @name  process.signal
--- @param pid  Number containing the process id
--- @param sig  Signal to send (default: 15 [SIGTERM])
--- @return             Boolean indicating successful operation
--- @return             Number containing the error code if failed
 process.signal = nixio.kill
 
 
---- LuCI system utilities / user related functions.
--- @class      module
--- @name       luci.sys.user
 user = {}
 
---- Retrieve user informations for given uid.
--- @class              function
--- @name               getuser
--- @param uid  Number containing the Unix user id
--- @return             Table containing the following fields:
 --                             { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
 user.getuser = nixio.getpw
 
---- Retrieve the current user password hash.
--- @param username     String containing the username to retrieve the password for
--- @return                     String containing the hash or nil if no password is set.
--- @return                     Password database entry
 function user.getpasswd(username)
        local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
        local pwh = pwe and (pwe.pwdp or pwe.passwd)
@@ -737,10 +434,6 @@ function user.getpasswd(username)
        end
 end
 
---- Test whether given string matches the password of a given system user.
--- @param username     String containing the Unix user name
--- @param pass         String containing the password to compare
--- @return                     Boolean indicating wheather the passwords are equal
 function user.checkpasswd(username, pass)
        local pwh, pwe = user.getpasswd(username)
        if pwe then
@@ -749,87 +442,37 @@ function user.checkpasswd(username, pass)
        return false
 end
 
---- Change the password of given user.
--- @param username     String containing the Unix user name
--- @param password     String containing the password to compare
--- @return                     Number containing 0 on success and >= 1 on error
 function user.setpasswd(username, password)
-       if password then
-               password = password:gsub("'", [['"'"']])
-       end
-
-       if username then
-               username = username:gsub("'", [['"'"']])
-       end
-
-       return os.execute(
-               "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
-               "passwd '" .. username .. "' >/dev/null 2>&1"
-       )
+       return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
+               luci.util.shellquote(password),
+               luci.util.shellquote(password),
+               luci.util.shellquote(username)
+       })
 end
 
 
---- LuCI system utilities / wifi related functions.
--- @class      module
--- @name       luci.sys.wifi
 wifi = {}
 
---- Get wireless information for given interface.
--- @param ifname        String containing the interface name
--- @return              A wrapped iwinfo object instance
 function wifi.getiwinfo(ifname)
-       local stat, iwinfo = pcall(require, "iwinfo")
-
-       if ifname then
-               local c = 0
-               local u = uci.cursor_state()
-               local d, n = ifname:match("^(%w+)%.network(%d+)")
-               if d and n then
-                       ifname = d
-                       n = tonumber(n)
-                       u:foreach("wireless", "wifi-iface",
-                               function(s)
-                                       if s.device == d then
-                                               c = c + 1
-                                               if c == n then
-                                                       ifname = s.ifname or s.device
-                                                       return false
-                                               end
-                                       end
-                               end)
-               elseif u:get("wireless", ifname) == "wifi-device" then
-                       u:foreach("wireless", "wifi-iface",
-                               function(s)
-                                       if s.device == ifname and s.ifname then
-                                               ifname = s.ifname
-                                               return false
-                                       end
-                               end)
-               end
+       ntm.init()
 
-               local t = stat and iwinfo.type(ifname)
-               local x = t and iwinfo[t] or { }
-               return setmetatable({}, {
-                       __index = function(t, k)
-                               if k == "ifname" then
-                                       return ifname
-                               elseif x[k] then
-                                       return x[k](ifname)
-                               end
-                       end
-               })
+       local wnet = ntm:get_wifinet(ifname)
+       if wnet and wnet.iwinfo then
+               return wnet.iwinfo
        end
+
+       local wdev = ntm:get_wifidev(ifname)
+       if wdev and wdev.iwinfo then
+               return wdev.iwinfo
+       end
+
+       return { ifname = ifname }
 end
 
 
---- LuCI system utilities / init related functions.
--- @class      module
--- @name       luci.sys.init
 init = {}
 init.dir = "/etc/init.d/"
 
---- Get the names of all installed init scripts
--- @return     Table containing the names of all inistalled init scripts
 function init.names()
        local names = { }
        for name in fs.glob(init.dir.."*") do
@@ -838,9 +481,6 @@ function init.names()
        return names
 end
 
---- Get the index of he given init script
--- @param name Name of the init script
--- @return             Numeric index value
 function init.index(name)
        if fs.access(init.dir..name) then
                return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
@@ -854,62 +494,22 @@ local function init_action(action, name)
        end
 end
 
---- Test whether the given init script is enabled
--- @param name Name of the init script
--- @return             Boolean indicating whether init is enabled
 function init.enabled(name)
        return (init_action("enabled", name) == 0)
 end
 
---- Enable the given init script
--- @param name Name of the init script
--- @return             Boolean indicating success
 function init.enable(name)
        return (init_action("enable", name) == 1)
 end
 
---- Disable the given init script
--- @param name Name of the init script
--- @return             Boolean indicating success
 function init.disable(name)
        return (init_action("disable", name) == 0)
 end
 
---- Start the given init script
--- @param name Name of the init script
--- @return             Boolean indicating success
 function init.start(name)
        return (init_action("start", name) == 0)
 end
 
---- Stop the given init script
--- @param name Name of the init script
--- @return             Boolean indicating success
 function init.stop(name)
        return (init_action("stop", name) == 0)
 end
-
-
--- Internal functions
-
-function _parse_mixed_record(cnt, delimiter)
-       delimiter = delimiter or "  "
-       local data = {}
-       local flags = {}
-
-       for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
-               for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
-                       local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
-
-                       if k then
-                               if x == "" then
-                                       table.insert(flags, k)
-                               else
-                                       data[k] = v
-                               end
-                       end
-               end
-       end
-
-       return data, flags
-end