libs/sys: implement luci.sys.net.routes6()
[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 posix = require "posix"
31 local table = require "table"
32
33 local luci  = {}
34 luci.util   = require "luci.util"
35 luci.fs     = require "luci.fs"
36 luci.ip     = require "luci.ip"
37
38 local tonumber, ipairs, pairs, pcall = tonumber, ipairs, pairs, pcall
39
40
41 --- LuCI Linux and POSIX system utilities.
42 module "luci.sys"
43
44 --- Execute a given shell command and return the error code
45 -- @class               function
46 -- @name                call
47 -- @param               ...             Command to call
48 -- @return              Error code of the command
49 function call(...)
50         return os.execute(...) / 256
51 end
52
53 --- Execute a given shell command and capture its standard output
54 -- @class               function
55 -- @name                exec
56 -- @param command       Command to call
57 -- @return                      String containg the return the output of the command
58 exec = luci.util.exec
59
60 --- Invoke the luci-flash executable to write an image to the flash memory.
61 -- @param image         Local path or URL to image file
62 -- @param kpattern      Pattern of files to keep over flash process
63 -- @return                      Return value of os.execute()
64 function flash(image, kpattern)
65         local cmd = "luci-flash "
66         if kpattern then
67                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
68         end
69         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
70
71         return os.execute(cmd)
72 end
73
74 --- Retrieve information about currently mounted file systems.
75 -- @return      Table containing mount information
76 function mounts()
77         local data = {}
78         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
79         local ps = luci.util.execi("df")
80
81         if not ps then
82                 return
83         else
84                 ps()
85         end
86
87         for line in ps do
88                 local row = {}
89
90                 local j = 1
91                 for value in line:gmatch("[^%s]+") do
92                         row[k[j]] = value
93                         j = j + 1
94                 end
95
96                 if row[k[1]] then
97
98                         -- this is a rather ugly workaround to cope with wrapped lines in
99                         -- the df output:
100                         --
101                         --      /dev/scsi/host0/bus0/target0/lun0/part3
102                         --                   114382024  93566472  15005244  86% /mnt/usb
103                         --
104
105                         if not row[k[2]] then
106                                 j = 2
107                                 line = ps()
108                                 for value in line:gmatch("[^%s]+") do
109                                         row[k[j]] = value
110                                         j = j + 1
111                                 end
112                         end
113
114                         table.insert(data, row)
115                 end
116         end
117
118         return data
119 end
120
121 --- Retrieve environment variables. If no variable is given then a table
122 -- containing the whole environment is returned otherwise this function returns
123 -- the corresponding string value for the given name or nil if no such variable
124 -- exists.
125 -- @class               function
126 -- @name                getenv
127 -- @param var   Name of the environment variable to retrieve (optional)
128 -- @return              String containg the value of the specified variable
129 -- @return              Table containing all variables if no variable name is given
130 getenv = posix.getenv
131
132 --- Determine the current hostname.
133 -- @return              String containing the system hostname
134 function hostname()
135         return posix.uname("%n")
136 end
137
138 --- Returns the contents of a documented referred by an URL.
139 -- @param url    The URL to retrieve
140 -- @param stream Return a stream instead of a buffer
141 -- @param target Directly write to target file name
142 -- @return              String containing the contents of given the URL
143 function httpget(url, stream, target)
144         if not target then
145                 local source = stream and io.popen or luci.util.exec
146                 return source("wget -qO- '"..url:gsub("'", "").."'")
147         else
148                 return os.execute("wget -qO '%s' '%s'" %
149                         {target:gsub("'", ""), url:gsub("'", "")})
150         end
151 end
152
153 --- Returns the system load average values.
154 -- @return      String containing the average load value 1 minute ago
155 -- @return      String containing the average load value 5 minutes ago
156 -- @return      String containing the average load value 15 minutes ago
157 -- @return      String containing the active and total number of processes
158 -- @return      String containing the last used pid
159 function loadavg()
160         local loadavg = io.lines("/proc/loadavg")()
161         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
162 end
163
164 --- Initiate a system reboot.
165 -- @return      Return value of os.execute()
166 function reboot()
167         return os.execute("reboot >/dev/null 2>&1")
168 end
169
170 --- Returns the system type, cpu name and installed physical memory.
171 -- @return      String containing the system or platform identifier
172 -- @return      String containing hardware model information
173 -- @return      String containing the total memory amount in kB
174 -- @return      String containing the memory used for caching in kB
175 -- @return      String containing the memory used for buffering in kB
176 -- @return      String containing the free memory amount in kB
177 function sysinfo()
178         local cpuinfo = luci.fs.readfile("/proc/cpuinfo")
179         local meminfo = luci.fs.readfile("/proc/meminfo")
180
181         local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
182         local model = ""
183         local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
184         local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
185         local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
186         local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
187
188         if not system then
189                 system = posix.uname("%m")
190                 model = cpuinfo:match("model name.-:%s*([^\n]+)")
191                 if not model then
192                         model = cpuinfo:match("Processor.-:%s*([^\n]+)")
193                 end
194         else
195                 model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
196         end
197
198         return system, model, memtotal, memcached, membuffers, memfree
199 end
200
201 --- Retrieves the output of the "logread" command.
202 -- @return      String containing the current log buffer
203 function syslog()
204         return luci.util.exec("logread")
205 end
206
207 --- Retrieves the output of the "dmesg" command.
208 -- @return      String containing the current log buffer
209 function dmesg()
210         return luci.util.exec("dmesg")
211 end
212
213 --- Generates a random id with specified length.
214 -- @param bytes Number of bytes for the unique id
215 -- @return              String containing hex encoded id
216 function uniqueid(bytes)
217         local fp    = io.open("/dev/urandom")
218         local chunk = { fp:read(bytes):byte(1, bytes) }
219         fp:close()
220
221         local hex = ""
222
223         local pattern = "%02X"
224         for i, byte in ipairs(chunk) do
225                 hex = hex .. pattern:format(byte)
226         end
227
228         return hex
229 end
230
231 --- Returns the current system uptime stats.
232 -- @return      String containing total uptime in seconds
233 -- @return      String containing idle time in seconds
234 function uptime()
235         local loadavg = io.lines("/proc/uptime")()
236         return loadavg:match("^(.-) (.-)$")
237 end
238
239 --- LuCI system utilities / POSIX user group related functions.
240 -- @class       module
241 -- @name        luci.sys.group
242 group = {}
243
244 --- Returns information about a POSIX user group.
245 -- @class function
246 -- @name                getgroup
247 -- @param group Group ID or name of a system user group
248 -- @return      Table with information about the requested group
249 group.getgroup = posix.getgroup
250
251
252 --- LuCI system utilities / network related functions.
253 -- @class       module
254 -- @name        luci.sys.net
255 net = {}
256
257 --- Returns the current arp-table entries as two-dimensional table.
258 -- @return      Table of table containing the current arp entries.
259 --                      The following fields are defined for arp entry objects:
260 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
261 function net.arptable()
262         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
263 end
264
265 --- Returns conntrack information
266 -- @return      Table with the currently tracked IP connections
267 function net.conntrack()
268         local connt = {}
269         if luci.fs.access("/proc/net/nf_conntrack") then
270                 for line in io.lines("/proc/net/nf_conntrack") do
271                         local entry, flags = _parse_mixed_record(line, " +")
272                         entry.layer3 = flags[1]
273                         entry.layer4 = flags[2]
274                         for i=1, #entry do
275                                 entry[i] = nil
276                         end
277
278                         connt[#connt+1] = entry
279                 end
280         elseif luci.fs.access("/proc/net/ip_conntrack") then
281                 for line in io.lines("/proc/net/ip_conntrack") do
282                         local entry, flags = _parse_mixed_record(line, " +")
283                         entry.layer3 = "ipv4"
284                         entry.layer4 = flags[1]
285                         for i=1, #entry do
286                                 entry[i] = nil
287                         end
288
289                         connt[#connt+1] = entry
290                 end
291         else
292                 return nil
293         end
294         return connt
295 end
296
297 --- Determine the current default route.
298 -- @return      Table with the properties of the current default route.
299 --                      The following fields are defined:
300 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
301 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
302 function net.defaultroute()
303         local routes = net.routes()
304         local route = nil
305
306         for i, r in pairs(luci.sys.net.routes()) do
307                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
308                         route = r
309                 end
310         end
311
312         return route
313 end
314
315 --- Determine the names of available network interfaces.
316 -- @return      Table containing all current interface names
317 function net.devices()
318         local devices = {}
319         for line in io.lines("/proc/net/dev") do
320                 table.insert(devices, line:match(" *(.-):"))
321         end
322         return devices
323 end
324
325
326 --- Return information about available network interfaces.
327 -- @return      Table containing all current interface names and their information
328 function net.deviceinfo()
329         local devices = {}
330         for line in io.lines("/proc/net/dev") do
331                 local name, data = line:match("^ *(.-): *(.*)$")
332                 if name and data then
333                         devices[name] = luci.util.split(data, " +", nil, true)
334                 end
335         end
336         return devices
337 end
338
339
340 -- Determine the MAC address belonging to the given IP address.
341 -- @param ip    IPv4 address
342 -- @return              String containing the MAC address or nil if it cannot be found
343 function net.ip4mac(ip)
344         local mac = nil
345
346         for i, l in ipairs(net.arptable()) do
347                 if l["IP address"] == ip then
348                         mac = l["HW address"]
349                 end
350         end
351
352         return mac
353 end
354
355 --- Returns the current kernel routing table entries.
356 -- @return      Table of tables with properties of the corresponding routes.
357 --                      The following fields are defined for route entry tables:
358 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
359 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
360 function net.routes()
361         return _parse_delimited_table(io.lines("/proc/net/route"))
362 end
363
364 --- Returns the current ipv6 kernel routing table entries.
365 -- @return      Table of tables with properties of the corresponding routes.
366 --                      The following fields are defined for route entry tables:
367 --                      { "src_ip", "src_prefix", "dst_ip", "dst_prefix", "nexthop_ip",
368 --            "metric", "refcount", "usecount", "flags", "device" }
369 function net.routes6()
370     local routes = { }
371
372     for line in io.lines("/proc/net/ipv6_route") do
373
374         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
375               metric, refcnt, usecnt, flags, dev = line:match(
376             "([a-f0-9]+) ([a-f0-9]+) " ..
377             "([a-f0-9]+) ([a-f0-9]+) " ..
378             "([a-f0-9]+) ([a-f0-9]+) " ..
379             "(%d+) (%d+) ([^%s]+) +([^%s]+)"
380         )
381         
382         src_ip = luci.ip.Hex(
383             src_ip, tonumber(src_prefix, 16),
384             luci.ip.FAMILY_INET6, false
385         )
386
387         dst_ip = luci.ip.Hex(
388             dst_ip, tonumber(dst_prefix, 16),
389             luci.ip.FAMILY_INET6, false
390         )
391
392         nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
393
394         routes[#routes+1] = {
395             src_ip     = src_ip:host():string(),
396             src_prefix = src_ip:prefix(),
397             dst_ip     = dst_ip:host():string(),
398             dst_prefix = dst_ip:prefix(),
399             nexthop_ip = nexthop:string(),
400             metric     = tonumber(metric, 16),
401             refcount   = tonumber(refcnt),
402             usecount   = tonumber(usecnt),
403             flags      = tonumber(flags), -- hex?
404             device     = dev
405         }
406     end
407
408         return routes
409 end
410
411 --- Tests whether the given host responds to ping probes.
412 -- @param host  String containing a hostname or IPv4 address
413 -- @return              Number containing 0 on success and >= 1 on error
414 function net.pingtest(host)
415         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
416 end
417
418
419 --- LuCI system utilities / process related functions.
420 -- @class       module
421 -- @name        luci.sys.process
422 process = {}
423
424 --- Get the current process id.
425 -- @class function
426 -- @name  process.info
427 -- @return      Number containing the current pid
428 process.info = posix.getpid
429
430 --- Retrieve information about currently running processes.
431 -- @return      Table containing process information
432 function process.list()
433         local data = {}
434         local k
435         local ps = luci.util.execi("top -bn1")
436
437         if not ps then
438                 return
439         end
440
441         while true do
442                 local line = ps()
443                 if not line then
444                         return
445                 end
446
447                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
448                 if k[1] == "PID" then
449                         break
450                 end
451         end
452
453         for line in ps do
454                 local row = {}
455
456                 line = luci.util.trim(line)
457                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
458                         row[k[i]] = value
459                 end
460
461                 local pid = tonumber(row[k[1]])
462                 if pid then
463                         data[pid] = row
464                 end
465         end
466
467         return data
468 end
469
470 --- Set the gid of a process identified by given pid.
471 -- @param pid   Number containing the process id
472 -- @param gid   Number containing the Unix group id
473 -- @return              Boolean indicating successful operation
474 -- @return              String containing the error message if failed
475 -- @return              Number containing the error code if failed
476 function process.setgroup(pid, gid)
477         return posix.setpid("g", pid, gid)
478 end
479
480 --- Set the uid of a process identified by given pid.
481 -- @param pid   Number containing the process id
482 -- @param uid   Number containing the Unix user id
483 -- @return              Boolean indicating successful operation
484 -- @return              String containing the error message if failed
485 -- @return              Number containing the error code if failed
486 function process.setuser(pid, uid)
487         return posix.setpid("u", pid, uid)
488 end
489
490 --- Send a signal to a process identified by given pid.
491 -- @class function
492 -- @name  process.signal
493 -- @param pid   Number containing the process id
494 -- @param sig   Signal to send (default: 15 [SIGTERM])
495 -- @return              Boolean indicating successful operation
496 -- @return              Number containing the error code if failed
497 process.signal = posix.kill
498
499
500 --- LuCI system utilities / user related functions.
501 -- @class       module
502 -- @name        luci.sys.user
503 user = {}
504
505 --- Retrieve user informations for given uid.
506 -- @class               function
507 -- @name                getuser
508 -- @param uid   Number containing the Unix user id
509 -- @return              Table containing the following fields:
510 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
511 user.getuser = posix.getpasswd
512
513 --- Test whether given string matches the password of a given system user.
514 -- @param username      String containing the Unix user name
515 -- @param password      String containing the password to compare
516 -- @return                      Boolean indicating wheather the passwords are equal
517 function user.checkpasswd(username, password)
518         local account = user.getuser(username)
519
520         if account then
521                 local pwd = account.passwd
522                 local shadowpw
523                 if #pwd == 1 then
524                         if luci.fs.stat("/etc/shadow") then
525                                 if not pcall(function()
526                                         for l in io.lines("/etc/shadow") do
527                                                 shadowpw = l:match("^%s:([^:]+)" % username)
528                                                 if shadowpw then
529                                                         pwd = shadowpw
530                                                         break
531                                                 end
532                                         end
533                                 end) then
534                                         return nil, "Unable to access shadow-file"
535                                 end
536                         end
537
538                         if pwd == "!" then
539                                 return true
540                         end
541                 end
542
543                 if pwd and #pwd > 0 and password and #password > 0 then
544                         return (pwd == posix.crypt(password, pwd))
545                 end
546         end
547
548         return false
549 end
550
551 --- Change the password of given user.
552 -- @param username      String containing the Unix user name
553 -- @param password      String containing the password to compare
554 -- @return                      Number containing 0 on success and >= 1 on error
555 function user.setpasswd(username, password)
556         if password then
557                 password = password:gsub("'", "")
558         end
559
560         if username then
561                 username = username:gsub("'", "")
562         end
563
564         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
565         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
566         return os.execute(cmd)
567 end
568
569
570 --- LuCI system utilities / wifi related functions.
571 -- @class       module
572 -- @name        luci.sys.wifi
573 wifi = {}
574
575 --- Get iwconfig output for all wireless devices.
576 -- @return      Table of tables containing the iwconfing output for each wifi device
577 function wifi.getiwconfig()
578         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
579         local iwc = {}
580
581         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
582                 local k = l:match("^(.-) ")
583                 l = l:gsub("^(.-) +", "", 1)
584                 if k then
585                         local entry, flags = _parse_mixed_record(l)
586                         if entry then
587                                 entry.flags = flags
588                         end
589                         iwc[k] = entry
590                 end
591         end
592
593         return iwc
594 end
595
596 --- Get iwlist scan output from all wireless devices.
597 -- @return      Table of tables contaiing all scan results
598 function wifi.iwscan(iface)
599         local siface = iface or ""
600         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
601         local iws = {}
602
603         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
604                 local k = l:match("^(.-) ")
605                 l = l:gsub("^[^\n]+", "", 1)
606                 l = luci.util.trim(l)
607                 if k then
608                         iws[k] = {}
609                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
610                                 c = c:gsub("^(.-)- ", "", 1)
611                                 c = luci.util.split(c, "\n", 7)
612                                 c = table.concat(c, "\n", 1)
613                                 local entry, flags = _parse_mixed_record(c)
614                                 if entry then
615                                         entry.flags = flags
616                                 end
617                                 table.insert(iws[k], entry)
618                         end
619                 end
620         end
621
622         return iface and (iws[iface] or {}) or iws
623 end
624
625
626 --- LuCI system utilities / init related functions.
627 -- @class       module
628 -- @name        luci.sys.init
629 init = {}
630 init.dir = "/etc/init.d/"
631
632 --- Get the names of all installed init scripts
633 -- @return      Table containing the names of all inistalled init scripts
634 function init.names()
635         local names = { }
636         for _, name in ipairs(luci.fs.glob(init.dir.."*")) do
637                 names[#names+1] = luci.fs.basename(name)
638         end
639         return names
640 end
641
642 --- Test whether the given init script is enabled
643 -- @param name  Name of the init script
644 -- @return              Boolean indicating whether init is enabled
645 function init.enabled(name)
646         if luci.fs.access(init.dir..name) then
647                 return ( call(init.dir..name.." enabled") == 0 )
648         end
649         return false
650 end
651
652 --- Get the index of he given init script
653 -- @param name  Name of the init script
654 -- @return              Numeric index value
655 function init.index(name)
656         if luci.fs.access(init.dir..name) then
657                 return call("source "..init.dir..name.."; exit $START")
658         end
659 end
660
661 --- Enable the given init script
662 -- @param name  Name of the init script
663 -- @return              Boolean indicating success
664 function init.enable(name)
665         if luci.fs.access(init.dir..name) then
666                 return ( call(init.dir..name.." enable") == 1 )
667         end
668 end
669
670 --- Disable the given init script
671 -- @param name  Name of the init script
672 -- @return              Boolean indicating success
673 function init.disable(name)
674         if luci.fs.access(init.dir..name) then
675                 return ( call(init.dir..name.." disable") == 0 )
676         end
677 end
678
679
680 -- Internal functions
681
682 function _parse_delimited_table(iter, delimiter)
683         delimiter = delimiter or "%s+"
684
685         local data  = {}
686         local trim  = luci.util.trim
687         local split = luci.util.split
688
689         local keys = split(trim(iter()), delimiter, nil, true)
690         for i, j in pairs(keys) do
691                 keys[i] = trim(keys[i])
692         end
693
694         for line in iter do
695                 local row = {}
696                 line = trim(line)
697                 if #line > 0 then
698                         for i, j in pairs(split(line, delimiter, nil, true)) do
699                                 if keys[i] then
700                                         row[keys[i]] = j
701                                 end
702                         end
703                 end
704                 table.insert(data, row)
705         end
706
707         return data
708 end
709
710 function _parse_mixed_record(cnt, delimiter)
711         delimiter = delimiter or "  "
712         local data = {}
713         local flags = {}
714
715         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
716                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
717                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
718
719             if k then
720                                 if x == "" then
721                                         table.insert(flags, k)
722                                 else
723                         data[k] = v
724                                 end
725             end
726         end
727         end
728
729     return data, flags
730 end