* libs/web: Switched from HTTP-Basic-Auth to Session-Auth
[project/luci.git] / libs / core / 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 module("luci.sys", package.seeall)
28 require("posix")
29 require("luci.bits")
30 require("luci.util")
31 require("luci.fs")
32
33 -- Returns whether a system is bigendian
34 function bigendian()
35         local fp = io.open("/bin/sh")
36         fp:seek("set", 5)
37         local be = (fp:read(1):byte() ~= 1)
38         fp:close()
39         return be
40 end
41
42 -- Runs "command" and returns its output
43 function exec(command)
44         local pp   = io.popen(command)
45         local data = pp:read("*a")
46         pp:close()
47         
48         return data
49 end
50
51 -- Runs "command" and returns its output as a array of lines
52 function execl(command)
53         local pp   = io.popen(command)  
54         local line = ""
55         local data = {}
56         
57         while true do
58                 line = pp:read()
59                 if (line == nil) then break end
60                 table.insert(data, line)
61         end 
62         pp:close()      
63         
64         return data
65 end
66
67 -- Uses "luci-flash" to flash a new image file to the system
68 function flash(image, kpattern)
69         local cmd = "luci-flash "
70         if kpattern then
71                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
72         end
73         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
74         
75         return os.execute(cmd)
76 end
77
78 -- Returns the enivornment
79 getenv = posix.getenv
80
81 -- Returns the hostname
82 function hostname()
83         return io.lines("/proc/sys/kernel/hostname")()
84 end
85
86 -- Returns the contents of a documented referred by an URL
87 function httpget(url)
88         return exec("wget -qO- '"..url:gsub("'", "").."'")
89 end
90
91 -- Returns the FFLuci-Basedir
92 function libpath()
93         return luci.fs.dirname(require("luci.debug").__file__)
94 end
95
96 -- Returns the load average
97 function loadavg()
98         local loadavg = io.lines("/proc/loadavg")()
99         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
100 end
101
102 -- Reboots the system
103 function reboot()
104         return os.execute("reboot >/dev/null 2>&1")
105 end
106
107 -- Returns the system type, cpu name, and installed physical memory
108 function sysinfo()
109         local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null"
110         local c2 = "uname -m 2>/dev/null"
111         local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null"
112         local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null"
113         local c5 = "cat /proc/meminfo|grep MemTotal|cut -d: -f2 2>/dev/null"
114         
115         local s = luci.util.trim(exec(c1))
116         local m = ""
117         local r = ""
118         
119         if s == "" then
120                 s = luci.util.trim(exec(c2))
121                 m = luci.util.trim(exec(c3))
122         else
123                 m = luci.util.trim(exec(c4))
124         end
125         
126         r = luci.util.trim(exec(c5))
127         
128         return s, m, r
129 end
130
131 -- Reads the syslog
132 function syslog()
133         return exec("logread")
134 end
135
136
137 -- Generates a random key of length BYTES
138 function uniqueid(bytes)
139         local fp    = io.open("/dev/urandom")
140         local chunk = { fp:read(bytes):byte(1, bytes) }
141         fp:close()
142         
143         local hex = ""
144         
145         local pattern = "%02X" 
146         for i, byte in ipairs(chunk) do
147                 hex = hex .. pattern:format(byte)
148         end
149         
150         return hex
151 end
152
153
154 group = {}
155 group.getgroup = posix.getgroup
156
157 net = {}
158 -- Returns the ARP-Table
159 function net.arptable()
160         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
161 end
162
163 -- Returns whether an IP-Adress belongs to a certain net
164 function net.belongs(ip, ipnet, prefix)
165         return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix))
166 end
167
168 -- Detect the default route
169 function net.defaultroute()
170         local routes = net.routes()
171         local route = nil
172         
173         for i, r in pairs(luci.sys.net.routes()) do
174                 if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then
175                         route = r
176                 end
177         end
178         
179         return route
180 end
181
182 -- Returns all available network interfaces
183 function net.devices()
184         local devices = {}
185         for line in io.lines("/proc/net/dev") do
186                 table.insert(devices, line:match(" *(.-):"))
187         end
188         return devices
189 end
190
191 -- Returns the MAC-Address belonging to the given IP-Address
192 function net.ip4mac(ip)
193         local mac = nil
194         
195         for i, l in ipairs(net.arptable()) do
196                 if l["IP address"] == ip then
197                         mac = l["HW address"]
198                 end
199         end
200         
201         return mac
202 end
203
204 -- Returns the prefix to a given netmask
205 function net.mask4prefix(mask)
206         local bin = net.ip4bin(mask)
207         
208         if not bin then
209                 return nil
210         end
211         
212         return #luci.util.split(bin, "1")-1
213 end
214
215 -- Returns the kernel routing table
216 function net.routes()
217         return _parse_delimited_table(io.lines("/proc/net/route"))
218 end
219
220 -- Returns the numeric IP to a given hexstring
221 function net.hexip4(hex, be)
222         if #hex ~= 8 then
223                 return nil
224         end
225         
226         be = be or bigendian()
227         
228         local hexdec = luci.bits.Hex2Dec
229         
230         local ip = ""
231         if be then
232                 ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "."
233                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
234                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
235                 ip = ip .. tostring(hexdec(hex:sub(7,8)))
236         else
237                 ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "."
238                 ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "."
239                 ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "."
240                 ip = ip .. tostring(hexdec(hex:sub(1,2)))
241         end
242         
243         return ip
244 end
245
246 -- Returns the binary IP to a given IP
247 function net.ip4bin(ip)
248         local parts = luci.util.split(ip, '.')
249         if #parts ~= 4 then
250                 return nil
251         end
252         
253         local decbin = luci.bits.Dec2Bin
254         
255         local bin = ""
256         bin = bin .. decbin(parts[1], 8)
257         bin = bin .. decbin(parts[2], 8)
258         bin = bin .. decbin(parts[3], 8)
259         bin = bin .. decbin(parts[4], 8)
260         
261         return bin
262 end
263
264 -- Tests whether a host is pingable
265 function net.pingtest(host)
266         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
267 end
268
269
270 process = {}
271 process.info = posix.getpid 
272
273 -- Sets the gid of a process
274 function process.setgroup(pid, gid)
275         return posix.setpid("g", pid, gid)
276 end
277
278 -- Sets the uid of a process
279 function process.setuser(pid, uid)
280         return posix.setpid("u", pid, uid)
281 end
282
283 user = {}
284 -- returns user information to a given uid
285 user.getuser = posix.getpasswd
286
287 -- checks whether a string matches the password of a certain system user
288 function user.checkpasswd(username, password)
289         local account = user.getuser(username)
290         
291         -- FIXME: detect testing environment
292         if luci.fs.isfile("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then
293                 return true
294         elseif account then
295                 if account.passwd == "!" then
296                         return true
297                 else
298                         return (account.passwd == posix.crypt(account.passwd, password))
299                 end
300         end
301 end
302         
303 -- Changes the user password of given user
304 function user.setpasswd(user, pwd)
305         if pwd then
306                 pwd = pwd:gsub("'", "")
307         end
308         
309         if user then
310                 user = user:gsub("'", "")
311         end
312         
313         local cmd = "(echo '"..pwd.."';sleep 1;echo '"..pwd.."')|"
314         cmd = cmd .. "passwd '"..user.."' >/dev/null 2>&1"
315         return os.execute(cmd)
316 end
317
318
319 wifi = {}
320
321 function wifi.getiwconfig()
322         local cnt = exec("/usr/sbin/iwconfig 2>/dev/null")
323         local iwc = {}
324         
325         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
326                 local k = l:match("^(.-) ")
327                 l = l:gsub("^(.-) +", "", 1)
328                 if k then
329                         iwc[k] = _parse_mixed_record(l)
330                 end
331         end
332         
333         return iwc      
334 end
335
336 function wifi.iwscan()
337         local cnt = exec("iwlist scan 2>/dev/null")
338         local iws = {}
339         
340         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
341                 local k = l:match("^(.-) ")
342                 l = l:gsub("^[^\n]+", "", 1)
343                 l = luci.util.trim(l)
344                 if k then
345                         iws[k] = {}
346                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
347                                 c = c:gsub("^(.-)- ", "", 1)
348                                 c = luci.util.split(c, "\n", 7)
349                                 c = table.concat(c, "\n", 1)
350                                 table.insert(iws[k], _parse_mixed_record(c))
351                         end
352                 end
353         end
354         
355         return iws      
356 end
357
358
359 -- Internal functions
360
361 function _parse_delimited_table(iter, delimiter)
362         delimiter = delimiter or "%s+"
363         
364         local data  = {}
365         local trim  = luci.util.trim
366         local split = luci.util.split
367         
368         local keys = split(trim(iter()), delimiter, nil, true)
369         for i, j in pairs(keys) do
370                 keys[i] = trim(keys[i])
371         end
372         
373         for line in iter do
374                 local row = {}
375                 line = trim(line)
376                 if #line > 0 then
377                         for i, j in pairs(split(line, delimiter, nil, true)) do
378                                 if keys[i] then
379                                         row[keys[i]] = j
380                                 end
381                         end
382                 end
383                 table.insert(data, row)
384         end
385         
386         return data
387 end
388
389 function _parse_mixed_record(cnt)
390         local data = {}
391         
392         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
393         for j, f in pairs(luci.util.split(luci.util.trim(l), "  ")) do
394                 local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*')
395
396             if k then
397                                 if x == "" then
398                                         table.insert(data, k)                           
399                                 else
400                         data[k] = v
401                                 end
402             end
403         end
404         end
405                 
406     return data
407 end