* libs/web: Reworked authentication
[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 --- LuCI Linux and POSIX system utilities.
28 module("luci.sys", package.seeall)
29 require("posix")
30 require("luci.bits")
31 require("luci.util")
32 require("luci.fs")
33 require("luci.ip")
34
35 --- Invoke the luci-flash executable to write an image to the flash memory.
36 -- @param kpattern      Pattern of files to keep over flash process
37 -- @return                      Return value of os.execute()
38 function flash(image, kpattern)
39         local cmd = "luci-flash "
40         if kpattern then
41                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
42         end
43         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
44
45         return os.execute(cmd)
46 end
47
48 --- Retrieve environment variables. If no variable is given then a table
49 -- containing the whole environment is returned otherwise this function returns
50 -- the corresponding string value for the given name or nil if no such variable
51 -- exists.
52 -- @class               function
53 -- @name                getenv
54 -- @param var   Name of the environment variable to retrieve (optional)
55 -- @return              String containg the value of the specified variable
56 -- @return              Table containing all variables if no variable name is given
57 getenv = posix.getenv
58
59 --- Determine the current hostname.
60 -- @return              String containing the system hostname
61 function hostname()
62         return io.lines("/proc/sys/kernel/hostname")()
63 end
64
65 --- Returns the contents of a documented referred by an URL.
66 -- @param url    The URL to retrieve
67 -- @param stream Return a stream instead of a buffer
68 -- @return              String containing the contents of given the URL
69 function httpget(url, stream)
70         local source = stream and io.open or luci.util.exec
71         return source("wget -qO- '"..url:gsub("'", "").."'")
72 end
73
74 --- Returns the system load average values.
75 -- @return      String containing the average load value 1 minute ago
76 -- @return      String containing the average load value 5 minutes ago
77 -- @return      String containing the average load value 15 minutes ago
78 -- @return      String containing the active and total number of processes
79 -- @return      String containing the last used pid
80 function loadavg()
81         local loadavg = io.lines("/proc/loadavg")()
82         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
83 end
84
85 --- Initiate a system reboot.
86 -- @return      Return value of os.execute()
87 function reboot()
88         return os.execute("reboot >/dev/null 2>&1")
89 end
90
91 --- Returns the system type, cpu name and installed physical memory.
92 -- @return      String containing the system or platform identifier
93 -- @return      String containing hardware model information
94 -- @return      String containing the total memory amount in kB
95 -- @return      String containing the memory used for caching in kB
96 -- @return      String containing the memory used for buffering in kB
97 -- @return      String containing the free memory amount in kB
98 -- @return      Number containing free memory in percent
99 -- @return      Number containing buffer memory in percent
100 -- @return      Number containing cache memory in percent
101 function sysinfo()
102         local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
103         local c2 = "uname -m 2>/dev/null"
104         local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
105         local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
106         local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null"
107         local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null"
108         local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null"
109         local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null"
110
111         local system = luci.util.trim(luci.util.exec(c1))
112         local model = ""
113         local memtotal = luci.util.trim(luci.util.exec(c5))
114         local memcached = luci.util.trim(luci.util.exec(c6))
115         local memfree = luci.util.trim(luci.util.exec(c7))
116         local membuffers = luci.util.trim(luci.util.exec(c8))
117         local perc_memfree = math.floor((memfree/memtotal)*100)
118         local perc_membuffers = math.floor((membuffers/memtotal)*100)
119         local perc_memcached = math.floor((memcached/memtotal)*100)
120
121         if system == "" then
122                 system = luci.util.trim(luci.util.exec(c2))
123                 model = luci.util.trim(luci.util.exec(c3))
124         else
125                 model = luci.util.trim(luci.util.exec(c4))
126         end
127
128         return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached
129 end
130
131 --- Retrieves the output of the "logread" command.
132 -- @return      String containing the current log buffer
133 function syslog()
134         return luci.util.exec("logread")
135 end
136
137 --- Generates a random id with specified length.
138 -- @param bytes Number of bytes for the unique id
139 -- @return              String containing hex encoded id
140 function uniqueid(bytes)
141         local fp    = io.open("/dev/urandom")
142         local chunk = { fp:read(bytes):byte(1, bytes) }
143         fp:close()
144
145         local hex = ""
146
147         local pattern = "%02X"
148         for i, byte in ipairs(chunk) do
149                 hex = hex .. pattern:format(byte)
150         end
151
152         return hex
153 end
154
155 --- Returns the current system uptime stats.
156 -- @return      String containing total uptime in seconds
157 -- @return      String containing idle time in seconds
158 function uptime()
159         local loadavg = io.lines("/proc/uptime")()
160         return loadavg:match("^(.-) (.-)$")
161 end
162
163 --- LuCI system utilities / POSIX user group related functions.
164 -- @class       module
165 -- @name        luci.sys.group
166 group = {}
167
168 --- Returns information about a POSIX user group.
169 -- @param group Group ID or name of a system user group
170 -- @return      Table with information about the requested group
171 group.getgroup = posix.getgroup
172
173
174 --- LuCI system utilities / network related functions.
175 -- @class       module
176 -- @name        luci.sys.net
177 net = {}
178
179 --- Returns the current arp-table entries as two-dimensional table.
180 -- @return      Table of table containing the current arp entries.
181 --                      The following fields are defined for arp entry objects:
182 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
183 function net.arptable()
184         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
185 end
186
187 --- Determine the current default route.
188 -- @return      Table with the properties of the current default route.
189 --                      The following fields are defined:
190 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
191 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
192 function net.defaultroute()
193         local routes = net.routes()
194         local route = nil
195
196         for i, r in pairs(luci.sys.net.routes()) do
197                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
198                         route = r
199                 end
200         end
201
202         return route
203 end
204
205 --- Determine the names of available network interfaces.
206 -- @return      Table containing all current interface names
207 function net.devices()
208         local devices = {}
209         for line in io.lines("/proc/net/dev") do
210                 table.insert(devices, line:match(" *(.-):"))
211         end
212         return devices
213 end
214
215 -- Determine the MAC address belonging to the given IP address.
216 -- @param ip    IPv4 address
217 -- @return              String containing the MAC address or nil if it cannot be found
218 function net.ip4mac(ip)
219         local mac = nil
220
221         for i, l in ipairs(net.arptable()) do
222                 if l["IP address"] == ip then
223                         mac = l["HW address"]
224                 end
225         end
226
227         return mac
228 end
229
230 --- Returns the current kernel routing table entries.
231 -- @return      Table of tables with properties of the corresponding routes.
232 --                      The following fields are defined for route entry tables:
233 --                      { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT",
234 --                        "MTU", "Gateway", "Destination", "Metric", "Use" }
235 function net.routes()
236         return _parse_delimited_table(io.lines("/proc/net/route"))
237 end
238
239
240 --- Tests whether the given host responds to ping probes.
241 -- @param host  String containing a hostname or IPv4 address
242 -- @return              Number containing 0 on success and >= 1 on error
243 function net.pingtest(host)
244         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
245 end
246
247
248 --- LuCI system utilities / process related functions.
249 -- @class       module
250 -- @name        luci.sys.process
251 process = {}
252
253 --- Get the current process id.
254 -- @return      Number containing the current pid
255 process.info = posix.getpid
256
257 --- Set the gid of a process identified by given pid.
258 -- @param pid   Number containing the process id
259 -- @param gid   Number containing the Unix group id
260 -- @return              Boolean indicating successful operation
261 -- @return              String containing the error message if failed
262 -- @return              Number containing the error code if failed
263 function process.setgroup(pid, gid)
264         return posix.setpid("g", pid, gid)
265 end
266
267 --- Set the uid of a process identified by given pid.
268 -- @param pid   Number containing the process id
269 -- @param uid   Number containing the Unix user id
270 -- @return              Boolean indicating successful operation
271 -- @return              String containing the error message if failed
272 -- @return              Number containing the error code if failed
273 function process.setuser(pid, uid)
274         return posix.setpid("u", pid, uid)
275 end
276
277
278 --- LuCI system utilities / user related functions.
279 -- @class       module
280 -- @name        luci.sys.user
281 user = {}
282
283 --- Retrieve user informations for given uid.
284 -- @class               function
285 -- @name                getuser
286 -- @param uid   Number containing the Unix user id
287 -- @return              Table containing the following fields:
288 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
289 user.getuser = posix.getpasswd
290
291 --- Test whether given string matches the password of a given system user.
292 -- @param username      String containing the Unix user name
293 -- @param password      String containing the password to compare
294 -- @return                      Boolean indicating wheather the passwords are equal
295 function user.checkpasswd(username, password)
296         local account = user.getuser(username)
297
298         if account then
299                 if account.passwd == "!" then
300                         return true
301                 else
302                         return (account.passwd == posix.crypt(password, account.passwd))
303                 end
304         end
305 end
306
307 --- Change the password of given user.
308 -- @param username      String containing the Unix user name
309 -- @param password      String containing the password to compare
310 -- @return                      Number containing 0 on success and >= 1 on error
311 function user.setpasswd(username, password)
312         if password then
313                 password = password:gsub("'", "")
314         end
315
316         if username then
317                 username = username:gsub("'", "")
318         end
319
320         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
321         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
322         return os.execute(cmd)
323 end
324
325
326 --- LuCI system utilities / wifi related functions.
327 -- @class       module
328 -- @name        luci.sys.wifi
329 wifi = {}
330
331 --- Get iwconfig output for all wireless devices.
332 -- @return      Table of tables containing the iwconfing output for each wifi device
333 function wifi.getiwconfig()
334         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
335         local iwc = {}
336
337         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
338                 local k = l:match("^(.-) ")
339                 l = l:gsub("^(.-) +", "", 1)
340                 if k then
341                         iwc[k] = _parse_mixed_record(l)
342                 end
343         end
344
345         return iwc
346 end
347
348 --- Get iwlist scan output from all wireless devices.
349 -- @return      Table of tables contaiing all scan results
350 function wifi.iwscan()
351         local cnt = luci.util.exec("iwlist scan 2>/dev/null")
352         local iws = {}
353
354         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
355                 local k = l:match("^(.-) ")
356                 l = l:gsub("^[^\n]+", "", 1)
357                 l = luci.util.trim(l)
358                 if k then
359                         iws[k] = {}
360                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
361                                 c = c:gsub("^(.-)- ", "", 1)
362                                 c = luci.util.split(c, "\n", 7)
363                                 c = table.concat(c, "\n", 1)
364                                 table.insert(iws[k], _parse_mixed_record(c))
365                         end
366                 end
367         end
368
369         return iws
370 end
371
372
373 -- Internal functions
374
375 function _parse_delimited_table(iter, delimiter)
376         delimiter = delimiter or "%s+"
377
378         local data  = {}
379         local trim  = luci.util.trim
380         local split = luci.util.split
381
382         local keys = split(trim(iter()), delimiter, nil, true)
383         for i, j in pairs(keys) do
384                 keys[i] = trim(keys[i])
385         end
386
387         for line in iter do
388                 local row = {}
389                 line = trim(line)
390                 if #line > 0 then
391                         for i, j in pairs(split(line, delimiter, nil, true)) do
392                                 if keys[i] then
393                                         row[keys[i]] = j
394                                 end
395                         end
396                 end
397                 table.insert(data, row)
398         end
399
400         return data
401 end
402
403 function _parse_mixed_record(cnt)
404         local data = {}
405
406         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
407         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
408                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
409
410             if k then
411                                 if x == "" then
412                                         table.insert(data, k)
413                                 else
414                         data[k] = v
415                                 end
416             end
417         end
418         end
419
420     return data
421 end