luci-0.11: merge r9571 - r9573
[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 local arp, e, r, v
234 if fs.access("/proc/net/arp") then
235 for e in io.lines("/proc/net/arp") do
236 local r = { }, v
237 for v in e:gmatch("%S+") do
238 r[#r+1] = v
239 end
240
241 if r[1] ~= "IP" then
242 local x = {
243 ["IP address"] = r[1],
244 ["HW type"] = r[2],
245 ["Flags"] = r[3],
246 ["HW address"] = r[4],
247 ["Mask"] = r[5],
248 ["Device"] = r[6]
249 }
250
251 if callback then
252 callback(x)
253 else
254 arp = arp or { }
255 arp[#arp+1] = x
256 end
257 end
258 end
259 end
260 return arp
261 end
262
263 local function _nethints(what, callback)
264 local _, k, e, mac, ip, name
265 local cur = uci.cursor()
266 local ifn = { }
267 local hosts = { }
268
269 local function _add(i, ...)
270 local k = select(i, ...)
271 if k then
272 if not hosts[k] then hosts[k] = { } end
273 hosts[k][1] = select(1, ...) or hosts[k][1]
274 hosts[k][2] = select(2, ...) or hosts[k][2]
275 hosts[k][3] = select(3, ...) or hosts[k][3]
276 hosts[k][4] = select(4, ...) or hosts[k][4]
277 end
278 end
279
280 if fs.access("/proc/net/arp") then
281 for e in io.lines("/proc/net/arp") do
282 ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
283 if ip and mac then
284 _add(what, mac:upper(), ip, nil, nil)
285 end
286 end
287 end
288
289 if fs.access("/etc/ethers") then
290 for e in io.lines("/etc/ethers") do
291 mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
292 if mac and ip then
293 _add(what, mac:upper(), ip, nil, nil)
294 end
295 end
296 end
297
298 if fs.access("/var/dhcp.leases") then
299 for e in io.lines("/var/dhcp.leases") do
300 mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
301 if mac and ip then
302 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
303 end
304 end
305 end
306
307 cur:foreach("dhcp", "host",
308 function(s)
309 for mac in luci.util.imatch(s.mac) do
310 _add(what, mac:upper(), s.ip, nil, s.name)
311 end
312 end)
313
314 for _, e in ipairs(nixio.getifaddrs()) do
315 if e.name ~= "lo" then
316 ifn[e.name] = ifn[e.name] or { }
317 if e.family == "packet" and e.addr and #e.addr == 17 then
318 ifn[e.name][1] = e.addr:upper()
319 elseif e.family == "inet" then
320 ifn[e.name][2] = e.addr
321 elseif e.family == "inet6" then
322 ifn[e.name][3] = e.addr
323 end
324 end
325 end
326
327 for _, e in pairs(ifn) do
328 if e[what] and (e[2] or e[3]) then
329 _add(what, e[1], e[2], e[3], e[4])
330 end
331 end
332
333 for _, e in luci.util.kspairs(hosts) do
334 callback(e[1], e[2], e[3], e[4])
335 end
336 end
337
338 --- Returns a two-dimensional table of mac address hints.
339 -- @return Table of table containing known hosts from various sources.
340 -- Each entry contains the values in the following order:
341 -- [ "mac", "name" ]
342 function net.mac_hints(callback)
343 if callback then
344 _nethints(1, function(mac, v4, v6, name)
345 name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
346 if name and name ~= mac then
347 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
348 end
349 end)
350 else
351 local rv = { }
352 _nethints(1, function(mac, v4, v6, name)
353 name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
354 if name and name ~= mac then
355 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
356 end
357 end)
358 return rv
359 end
360 end
361
362 --- Returns a two-dimensional table of IPv4 address hints.
363 -- @return Table of table containing known hosts from various sources.
364 -- Each entry contains the values in the following order:
365 -- [ "ip", "name" ]
366 function net.ipv4_hints(callback)
367 if callback then
368 _nethints(2, function(mac, v4, v6, name)
369 name = name or nixio.getnameinfo(v4, nil, 100) or mac
370 if name and name ~= v4 then
371 callback(v4, name)
372 end
373 end)
374 else
375 local rv = { }
376 _nethints(2, function(mac, v4, v6, name)
377 name = name or nixio.getnameinfo(v4, nil, 100) or mac
378 if name and name ~= v4 then
379 rv[#rv+1] = { v4, name }
380 end
381 end)
382 return rv
383 end
384 end
385
386 --- Returns a two-dimensional table of IPv6 address hints.
387 -- @return Table of table containing known hosts from various sources.
388 -- Each entry contains the values in the following order:
389 -- [ "ip", "name" ]
390 function net.ipv6_hints(callback)
391 if callback then
392 _nethints(3, function(mac, v4, v6, name)
393 name = name or nixio.getnameinfo(v6, nil, 100) or mac
394 if name and name ~= v6 then
395 callback(v6, name)
396 end
397 end)
398 else
399 local rv = { }
400 _nethints(3, function(mac, v4, v6, name)
401 name = name or nixio.getnameinfo(v6, nil, 100) or mac
402 if name and name ~= v6 then
403 rv[#rv+1] = { v6, name }
404 end
405 end)
406 return rv
407 end
408 end
409
410 --- Returns conntrack information
411 -- @return Table with the currently tracked IP connections
412 function net.conntrack(callback)
413 local connt = {}
414 if fs.access("/proc/net/nf_conntrack", "r") then
415 for line in io.lines("/proc/net/nf_conntrack") do
416 line = line:match "^(.-( [^ =]+=).-)%2"
417 local entry, flags = _parse_mixed_record(line, " +")
418 if flags[6] ~= "TIME_WAIT" then
419 entry.layer3 = flags[1]
420 entry.layer4 = flags[3]
421 for i=1, #entry do
422 entry[i] = nil
423 end
424
425 if callback then
426 callback(entry)
427 else
428 connt[#connt+1] = entry
429 end
430 end
431 end
432 elseif fs.access("/proc/net/ip_conntrack", "r") then
433 for line in io.lines("/proc/net/ip_conntrack") do
434 line = line:match "^(.-( [^ =]+=).-)%2"
435 local entry, flags = _parse_mixed_record(line, " +")
436 if flags[4] ~= "TIME_WAIT" then
437 entry.layer3 = "ipv4"
438 entry.layer4 = flags[1]
439 for i=1, #entry do
440 entry[i] = nil
441 end
442
443 if callback then
444 callback(entry)
445 else
446 connt[#connt+1] = entry
447 end
448 end
449 end
450 else
451 return nil
452 end
453 return connt
454 end
455
456 --- Determine the current IPv4 default route. If multiple default routes exist,
457 -- return the one with the lowest metric.
458 -- @return Table with the properties of the current default route.
459 -- The following fields are defined:
460 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
461 -- "flags", "device" }
462 function net.defaultroute()
463 local route
464
465 net.routes(function(rt)
466 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
467 route = rt
468 end
469 end)
470
471 return route
472 end
473
474 --- Determine the current IPv6 default route. If multiple default routes exist,
475 -- return the one with the lowest metric.
476 -- @return Table with the properties of the current default route.
477 -- The following fields are defined:
478 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
479 -- "flags", "device" }
480 function net.defaultroute6()
481 local route
482
483 net.routes6(function(rt)
484 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
485 (not route or route.metric > rt.metric)
486 then
487 route = rt
488 end
489 end)
490
491 if not route then
492 local global_unicast = luci.ip.IPv6("2000::/3")
493 net.routes6(function(rt)
494 if rt.dest:equal(global_unicast) and
495 (not route or route.metric > rt.metric)
496 then
497 route = rt
498 end
499 end)
500 end
501
502 return route
503 end
504
505 --- Determine the names of available network interfaces.
506 -- @return Table containing all current interface names
507 function net.devices()
508 local devs = {}
509 for k, v in ipairs(nixio.getifaddrs()) do
510 if v.family == "packet" then
511 devs[#devs+1] = v.name
512 end
513 end
514 return devs
515 end
516
517
518 --- Return information about available network interfaces.
519 -- @return Table containing all current interface names and their information
520 function net.deviceinfo()
521 local devs = {}
522 for k, v in ipairs(nixio.getifaddrs()) do
523 if v.family == "packet" then
524 local d = v.data
525 d[1] = d.rx_bytes
526 d[2] = d.rx_packets
527 d[3] = d.rx_errors
528 d[4] = d.rx_dropped
529 d[5] = 0
530 d[6] = 0
531 d[7] = 0
532 d[8] = d.multicast
533 d[9] = d.tx_bytes
534 d[10] = d.tx_packets
535 d[11] = d.tx_errors
536 d[12] = d.tx_dropped
537 d[13] = 0
538 d[14] = d.collisions
539 d[15] = 0
540 d[16] = 0
541 devs[v.name] = d
542 end
543 end
544 return devs
545 end
546
547
548 -- Determine the MAC address belonging to the given IP address.
549 -- @param ip IPv4 address
550 -- @return String containing the MAC address or nil if it cannot be found
551 function net.ip4mac(ip)
552 local mac = nil
553 net.arptable(function(e)
554 if e["IP address"] == ip then
555 mac = e["HW address"]
556 end
557 end)
558 return mac
559 end
560
561 --- Returns the current kernel routing table entries.
562 -- @return Table of tables with properties of the corresponding routes.
563 -- The following fields are defined for route entry tables:
564 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
565 -- "flags", "device" }
566 function net.routes(callback)
567 local routes = { }
568
569 for line in io.lines("/proc/net/route") do
570
571 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
572 dst_mask, mtu, win, irtt = line:match(
573 "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
574 "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
575 )
576
577 if dev then
578 gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
579 dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
580 dst_ip = luci.ip.Hex(
581 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
582 )
583
584 local rt = {
585 dest = dst_ip,
586 gateway = gateway,
587 metric = tonumber(metric),
588 refcount = tonumber(refcnt),
589 usecount = tonumber(usecnt),
590 mtu = tonumber(mtu),
591 window = tonumber(window),
592 irtt = tonumber(irtt),
593 flags = tonumber(flags, 16),
594 device = dev
595 }
596
597 if callback then
598 callback(rt)
599 else
600 routes[#routes+1] = rt
601 end
602 end
603 end
604
605 return routes
606 end
607
608 --- Returns the current ipv6 kernel routing table entries.
609 -- @return Table of tables with properties of the corresponding routes.
610 -- The following fields are defined for route entry tables:
611 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
612 -- "flags", "device" }
613 function net.routes6(callback)
614 if fs.access("/proc/net/ipv6_route", "r") then
615 local routes = { }
616
617 for line in io.lines("/proc/net/ipv6_route") do
618
619 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
620 metric, refcnt, usecnt, flags, dev = line:match(
621 "([a-f0-9]+) ([a-f0-9]+) " ..
622 "([a-f0-9]+) ([a-f0-9]+) " ..
623 "([a-f0-9]+) ([a-f0-9]+) " ..
624 "([a-f0-9]+) ([a-f0-9]+) " ..
625 "([a-f0-9]+) +([^%s]+)"
626 )
627
628 if dst_ip and dst_prefix and
629 src_ip and src_prefix and
630 nexthop and metric and
631 refcnt and usecnt and
632 flags and dev
633 then
634 src_ip = luci.ip.Hex(
635 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
636 )
637
638 dst_ip = luci.ip.Hex(
639 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
640 )
641
642 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
643
644 local rt = {
645 source = src_ip,
646 dest = dst_ip,
647 nexthop = nexthop,
648 metric = tonumber(metric, 16),
649 refcount = tonumber(refcnt, 16),
650 usecount = tonumber(usecnt, 16),
651 flags = tonumber(flags, 16),
652 device = dev,
653
654 -- lua number is too small for storing the metric
655 -- add a metric_raw field with the original content
656 metric_raw = metric
657 }
658
659 if callback then
660 callback(rt)
661 else
662 routes[#routes+1] = rt
663 end
664 end
665 end
666
667 return routes
668 end
669 end
670
671 --- Tests whether the given host responds to ping probes.
672 -- @param host String containing a hostname or IPv4 address
673 -- @return Number containing 0 on success and >= 1 on error
674 function net.pingtest(host)
675 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
676 end
677
678
679 --- LuCI system utilities / process related functions.
680 -- @class module
681 -- @name luci.sys.process
682 process = {}
683
684 --- Get the current process id.
685 -- @class function
686 -- @name process.info
687 -- @return Number containing the current pid
688 function process.info(key)
689 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
690 return not key and s or s[key]
691 end
692
693 --- Retrieve information about currently running processes.
694 -- @return Table containing process information
695 function process.list()
696 local data = {}
697 local k
698 local ps = luci.util.execi("top -bn1")
699
700 if not ps then
701 return
702 end
703
704 while true do
705 local line = ps()
706 if not line then
707 return
708 end
709
710 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
711 if k[6] == "%VSZ" then
712 k[6] = "%MEM"
713 end
714 if k[1] == "PID" then
715 break
716 end
717 end
718
719 for line in ps do
720 local row = {}
721
722 line = luci.util.trim(line)
723 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
724 row[k[i]] = value
725 end
726
727 local pid = tonumber(row[k[1]])
728 if pid then
729 data[pid] = row
730 end
731 end
732
733 return data
734 end
735
736 --- Set the gid of a process identified by given pid.
737 -- @param gid Number containing the Unix group id
738 -- @return Boolean indicating successful operation
739 -- @return String containing the error message if failed
740 -- @return Number containing the error code if failed
741 function process.setgroup(gid)
742 return nixio.setgid(gid)
743 end
744
745 --- Set the uid of a process identified by given pid.
746 -- @param uid Number containing the Unix user id
747 -- @return Boolean indicating successful operation
748 -- @return String containing the error message if failed
749 -- @return Number containing the error code if failed
750 function process.setuser(uid)
751 return nixio.setuid(uid)
752 end
753
754 --- Send a signal to a process identified by given pid.
755 -- @class function
756 -- @name process.signal
757 -- @param pid Number containing the process id
758 -- @param sig Signal to send (default: 15 [SIGTERM])
759 -- @return Boolean indicating successful operation
760 -- @return Number containing the error code if failed
761 process.signal = nixio.kill
762
763
764 --- LuCI system utilities / user related functions.
765 -- @class module
766 -- @name luci.sys.user
767 user = {}
768
769 --- Retrieve user informations for given uid.
770 -- @class function
771 -- @name getuser
772 -- @param uid Number containing the Unix user id
773 -- @return Table containing the following fields:
774 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
775 user.getuser = nixio.getpw
776
777 --- Retrieve the current user password hash.
778 -- @param username String containing the username to retrieve the password for
779 -- @return String containing the hash or nil if no password is set.
780 -- @return Password database entry
781 function user.getpasswd(username)
782 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
783 local pwh = pwe and (pwe.pwdp or pwe.passwd)
784 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
785 return nil, pwe
786 else
787 return pwh, pwe
788 end
789 end
790
791 --- Test whether given string matches the password of a given system user.
792 -- @param username String containing the Unix user name
793 -- @param pass String containing the password to compare
794 -- @return Boolean indicating wheather the passwords are equal
795 function user.checkpasswd(username, pass)
796 local pwh, pwe = user.getpasswd(username)
797 if pwe then
798 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
799 end
800 return false
801 end
802
803 --- Change the password of given user.
804 -- @param username String containing the Unix user name
805 -- @param password String containing the password to compare
806 -- @return Number containing 0 on success and >= 1 on error
807 function user.setpasswd(username, password)
808 if password then
809 password = password:gsub("'", [['"'"']])
810 end
811
812 if username then
813 username = username:gsub("'", [['"'"']])
814 end
815
816 return os.execute(
817 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
818 "passwd '" .. username .. "' >/dev/null 2>&1"
819 )
820 end
821
822
823 --- LuCI system utilities / wifi related functions.
824 -- @class module
825 -- @name luci.sys.wifi
826 wifi = {}
827
828 --- Get wireless information for given interface.
829 -- @param ifname String containing the interface name
830 -- @return A wrapped iwinfo object instance
831 function wifi.getiwinfo(ifname)
832 local stat, iwinfo = pcall(require, "iwinfo")
833
834 if ifname then
835 local c = 0
836 local u = uci.cursor_state()
837 local d, n = ifname:match("^(%w+)%.network(%d+)")
838 if d and n then
839 ifname = d
840 n = tonumber(n)
841 u:foreach("wireless", "wifi-iface",
842 function(s)
843 if s.device == d then
844 c = c + 1
845 if c == n then
846 ifname = s.ifname or s.device
847 return false
848 end
849 end
850 end)
851 elseif u:get("wireless", ifname) == "wifi-device" then
852 u:foreach("wireless", "wifi-iface",
853 function(s)
854 if s.device == ifname and s.ifname then
855 ifname = s.ifname
856 return false
857 end
858 end)
859 end
860
861 local t = stat and iwinfo.type(ifname)
862 local x = t and iwinfo[t] or { }
863 return setmetatable({}, {
864 __index = function(t, k)
865 if k == "ifname" then
866 return ifname
867 elseif x[k] then
868 return x[k](ifname)
869 end
870 end
871 })
872 end
873 end
874
875
876 --- LuCI system utilities / init related functions.
877 -- @class module
878 -- @name luci.sys.init
879 init = {}
880 init.dir = "/etc/init.d/"
881
882 --- Get the names of all installed init scripts
883 -- @return Table containing the names of all inistalled init scripts
884 function init.names()
885 local names = { }
886 for name in fs.glob(init.dir.."*") do
887 names[#names+1] = fs.basename(name)
888 end
889 return names
890 end
891
892 --- Get the index of he given init script
893 -- @param name Name of the init script
894 -- @return Numeric index value
895 function init.index(name)
896 if fs.access(init.dir..name) then
897 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
898 %{ init.dir, name })
899 end
900 end
901
902 local function init_action(action, name)
903 if fs.access(init.dir..name) then
904 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
905 end
906 end
907
908 --- Test whether the given init script is enabled
909 -- @param name Name of the init script
910 -- @return Boolean indicating whether init is enabled
911 function init.enabled(name)
912 return (init_action("enabled", name) == 0)
913 end
914
915 --- Enable the given init script
916 -- @param name Name of the init script
917 -- @return Boolean indicating success
918 function init.enable(name)
919 return (init_action("enable", name) == 1)
920 end
921
922 --- Disable the given init script
923 -- @param name Name of the init script
924 -- @return Boolean indicating success
925 function init.disable(name)
926 return (init_action("disable", name) == 0)
927 end
928
929 --- Start the given init script
930 -- @param name Name of the init script
931 -- @return Boolean indicating success
932 function init.start(name)
933 return (init_action("start", name) == 0)
934 end
935
936 --- Stop the given init script
937 -- @param name Name of the init script
938 -- @return Boolean indicating success
939 function init.stop(name)
940 return (init_action("stop", name) == 0)
941 end
942
943
944 -- Internal functions
945
946 function _parse_mixed_record(cnt, delimiter)
947 delimiter = delimiter or " "
948 local data = {}
949 local flags = {}
950
951 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
952 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
953 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
954
955 if k then
956 if x == "" then
957 table.insert(flags, k)
958 else
959 data[k] = v
960 end
961 end
962 end
963 end
964
965 return data, flags
966 end