sys.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. local io = require "io"
  4. local os = require "os"
  5. local table = require "table"
  6. local nixio = require "nixio"
  7. local fs = require "nixio.fs"
  8. local uci = require "luci.model.uci"
  9. local luci = {}
  10. luci.util = require "luci.util"
  11. luci.ip = require "luci.ip"
  12. local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack =
  13. tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack
  14. module "luci.sys"
  15. function call(...)
  16. return os.execute(...) / 256
  17. end
  18. exec = luci.util.exec
  19. -- containing the whole environment is returned otherwise this function returns
  20. -- the corresponding string value for the given name or nil if no such variable
  21. -- exists.
  22. getenv = nixio.getenv
  23. function hostname(newname)
  24. if type(newname) == "string" and #newname > 0 then
  25. fs.writefile( "/proc/sys/kernel/hostname", newname )
  26. return newname
  27. else
  28. return nixio.uname().nodename
  29. end
  30. end
  31. function httpget(url, stream, target)
  32. if not target then
  33. local source = stream and io.popen or luci.util.exec
  34. return source("wget -qO- %s" % luci.util.shellquote(url))
  35. else
  36. return os.execute("wget -qO %s %s" %
  37. {luci.util.shellquote(target), luci.util.shellquote(url)})
  38. end
  39. end
  40. function reboot()
  41. return os.execute("reboot >/dev/null 2>&1")
  42. end
  43. function syslog()
  44. return luci.util.exec("logread")
  45. end
  46. function dmesg()
  47. return luci.util.exec("dmesg")
  48. end
  49. function uniqueid(bytes)
  50. local rand = fs.readfile("/dev/urandom", bytes)
  51. return rand and nixio.bin.hexlify(rand)
  52. end
  53. function uptime()
  54. return nixio.sysinfo().uptime
  55. end
  56. net = {}
  57. local function _nethints(what, callback)
  58. local _, k, e, mac, ip, name, duid, iaid
  59. local cur = uci.cursor()
  60. local ifn = { }
  61. local hosts = { }
  62. local lookup = { }
  63. local function _add(i, ...)
  64. local k = select(i, ...)
  65. if k then
  66. if not hosts[k] then hosts[k] = { } end
  67. hosts[k][1] = select(1, ...) or hosts[k][1]
  68. hosts[k][2] = select(2, ...) or hosts[k][2]
  69. hosts[k][3] = select(3, ...) or hosts[k][3]
  70. hosts[k][4] = select(4, ...) or hosts[k][4]
  71. end
  72. end
  73. luci.ip.neighbors(nil, function(neigh)
  74. if neigh.mac and neigh.family == 4 then
  75. _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
  76. elseif neigh.mac and neigh.family == 6 then
  77. _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
  78. end
  79. end)
  80. if fs.access("/etc/ethers") then
  81. for e in io.lines("/etc/ethers") do
  82. mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
  83. mac = luci.ip.checkmac(mac)
  84. if mac and name then
  85. if luci.ip.checkip4(name) then
  86. _add(what, mac, name, nil, nil)
  87. else
  88. _add(what, mac, nil, nil, name)
  89. end
  90. end
  91. end
  92. end
  93. cur:foreach("dhcp", "dnsmasq",
  94. function(s)
  95. if s.leasefile and fs.access(s.leasefile) then
  96. for e in io.lines(s.leasefile) do
  97. mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
  98. mac = luci.ip.checkmac(mac)
  99. if mac and ip then
  100. _add(what, mac, ip, nil, name ~= "*" and name)
  101. end
  102. end
  103. end
  104. end
  105. )
  106. cur:foreach("dhcp", "odhcpd",
  107. function(s)
  108. if type(s.leasefile) == "string" and fs.access(s.leasefile) then
  109. for e in io.lines(s.leasefile) do
  110. duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+")
  111. mac = net.duid_to_mac(duid)
  112. if mac then
  113. if ip and iaid == "ipv4" then
  114. _add(what, mac, ip, nil, name ~= "*" and name)
  115. elseif ip then
  116. _add(what, mac, nil, ip, name ~= "*" and name)
  117. end
  118. end
  119. end
  120. end
  121. end
  122. )
  123. cur:foreach("dhcp", "host",
  124. function(s)
  125. for mac in luci.util.imatch(s.mac) do
  126. mac = luci.ip.checkmac(mac)
  127. if mac then
  128. _add(what, mac, s.ip, nil, s.name)
  129. end
  130. end
  131. end)
  132. for _, e in ipairs(nixio.getifaddrs()) do
  133. if e.name ~= "lo" then
  134. ifn[e.name] = ifn[e.name] or { }
  135. if e.family == "packet" and e.addr and #e.addr == 17 then
  136. ifn[e.name][1] = e.addr:upper()
  137. elseif e.family == "inet" then
  138. ifn[e.name][2] = e.addr
  139. elseif e.family == "inet6" then
  140. ifn[e.name][3] = e.addr
  141. end
  142. end
  143. end
  144. for _, e in pairs(ifn) do
  145. if e[what] and (e[2] or e[3]) then
  146. _add(what, e[1], e[2], e[3], e[4])
  147. end
  148. end
  149. for _, e in pairs(hosts) do
  150. lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
  151. end
  152. if #lookup > 0 then
  153. lookup = luci.util.ubus("network.rrdns", "lookup", {
  154. addrs = lookup,
  155. timeout = 250,
  156. limit = 1000
  157. }) or { }
  158. end
  159. for _, e in luci.util.kspairs(hosts) do
  160. callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
  161. end
  162. end
  163. -- Each entry contains the values in the following order:
  164. -- [ "mac", "name" ]
  165. function net.mac_hints(callback)
  166. if callback then
  167. _nethints(1, function(mac, v4, v6, name)
  168. name = name or v4
  169. if name and name ~= mac then
  170. callback(mac, name or v4)
  171. end
  172. end)
  173. else
  174. local rv = { }
  175. _nethints(1, function(mac, v4, v6, name)
  176. name = name or v4
  177. if name and name ~= mac then
  178. rv[#rv+1] = { mac, name or v4 }
  179. end
  180. end)
  181. return rv
  182. end
  183. end
  184. -- Each entry contains the values in the following order:
  185. -- [ "ip", "name" ]
  186. function net.ipv4_hints(callback)
  187. if callback then
  188. _nethints(2, function(mac, v4, v6, name)
  189. name = name or mac
  190. if name and name ~= v4 then
  191. callback(v4, name)
  192. end
  193. end)
  194. else
  195. local rv = { }
  196. _nethints(2, function(mac, v4, v6, name)
  197. name = name or mac
  198. if name and name ~= v4 then
  199. rv[#rv+1] = { v4, name }
  200. end
  201. end)
  202. return rv
  203. end
  204. end
  205. -- Each entry contains the values in the following order:
  206. -- [ "ip", "name" ]
  207. function net.ipv6_hints(callback)
  208. if callback then
  209. _nethints(3, function(mac, v4, v6, name)
  210. name = name or mac
  211. if name and name ~= v6 then
  212. callback(v6, name)
  213. end
  214. end)
  215. else
  216. local rv = { }
  217. _nethints(3, function(mac, v4, v6, name)
  218. name = name or mac
  219. if name and name ~= v6 then
  220. rv[#rv+1] = { v6, name }
  221. end
  222. end)
  223. return rv
  224. end
  225. end
  226. function net.host_hints(callback)
  227. if callback then
  228. _nethints(1, function(mac, v4, v6, name)
  229. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  230. callback(mac, v4, v6, name)
  231. end
  232. end)
  233. else
  234. local rv = { }
  235. _nethints(1, function(mac, v4, v6, name)
  236. if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
  237. local e = { }
  238. if v4 then e.ipv4 = v4 end
  239. if v6 then e.ipv6 = v6 end
  240. if name then e.name = name end
  241. rv[mac] = e
  242. end
  243. end)
  244. return rv
  245. end
  246. end
  247. function net.conntrack(callback)
  248. local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
  249. if not ok or not nfct then
  250. return nil
  251. end
  252. local line, connt = nil, (not callback) and { }
  253. for line in nfct do
  254. local fam, l3, l4, timeout, tuples =
  255. line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
  256. if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
  257. l4 = nixio.getprotobynumber(l4)
  258. local entry = {
  259. bytes = 0,
  260. packets = 0,
  261. layer3 = fam,
  262. layer4 = l4 and l4.name or "unknown",
  263. timeout = tonumber(timeout, 10)
  264. }
  265. local key, val
  266. for key, val in tuples:gmatch("(%w+)=(%S+)") do
  267. if key == "bytes" or key == "packets" then
  268. entry[key] = entry[key] + tonumber(val, 10)
  269. elseif key == "src" or key == "dst" then
  270. if entry[key] == nil then
  271. entry[key] = luci.ip.new(val):string()
  272. end
  273. elseif key == "sport" or key == "dport" then
  274. if entry[key] == nil then
  275. entry[key] = val
  276. end
  277. elseif val then
  278. entry[key] = val
  279. end
  280. end
  281. if callback then
  282. callback(entry)
  283. else
  284. connt[#connt+1] = entry
  285. end
  286. end
  287. end
  288. return callback and true or connt
  289. end
  290. function net.devices()
  291. local devs = {}
  292. local seen = {}
  293. for k, v in ipairs(nixio.getifaddrs()) do
  294. if v.name and not seen[v.name] then
  295. seen[v.name] = true
  296. devs[#devs+1] = v.name
  297. end
  298. end
  299. return devs
  300. end
  301. function net.duid_to_mac(duid)
  302. local b1, b2, b3, b4, b5, b6
  303. if type(duid) == "string" then
  304. -- DUID-LLT / Ethernet
  305. if #duid == 28 then
  306. b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
  307. -- DUID-LL / Ethernet
  308. elseif #duid == 20 then
  309. b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  310. -- DUID-LL / Ethernet (Without Header)
  311. elseif #duid == 12 then
  312. b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  313. end
  314. end
  315. return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
  316. end
  317. process = {}
  318. function process.info(key)
  319. local s = {uid = nixio.getuid(), gid = nixio.getgid()}
  320. return not key and s or s[key]
  321. end
  322. function process.list()
  323. local data = {}
  324. local k
  325. local ps = luci.util.execi("/bin/busybox top -bn1")
  326. if not ps then
  327. return
  328. end
  329. for line in ps do
  330. local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
  331. "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
  332. )
  333. local idx = tonumber(pid)
  334. if idx and not cmd:match("top %-bn1") then
  335. data[idx] = {
  336. ['PID'] = pid,
  337. ['PPID'] = ppid,
  338. ['USER'] = user,
  339. ['STAT'] = stat,
  340. ['VSZ'] = vsz,
  341. ['%MEM'] = mem,
  342. ['%CPU'] = cpu,
  343. ['COMMAND'] = cmd
  344. }
  345. end
  346. end
  347. return data
  348. end
  349. function process.setgroup(gid)
  350. return nixio.setgid(gid)
  351. end
  352. function process.setuser(uid)
  353. return nixio.setuid(uid)
  354. end
  355. process.signal = nixio.kill
  356. local function xclose(fd)
  357. if fd and fd:fileno() > 2 then
  358. fd:close()
  359. end
  360. end
  361. function process.exec(command, stdout, stderr, nowait)
  362. local out_r, out_w, err_r, err_w
  363. if stdout then out_r, out_w = nixio.pipe() end
  364. if stderr then err_r, err_w = nixio.pipe() end
  365. local pid = nixio.fork()
  366. if pid == 0 then
  367. nixio.chdir("/")
  368. local null = nixio.open("/dev/null", "w+")
  369. if null then
  370. nixio.dup(out_w or null, nixio.stdout)
  371. nixio.dup(err_w or null, nixio.stderr)
  372. nixio.dup(null, nixio.stdin)
  373. xclose(out_w)
  374. xclose(out_r)
  375. xclose(err_w)
  376. xclose(err_r)
  377. xclose(null)
  378. end
  379. nixio.exec(unpack(command))
  380. os.exit(-1)
  381. end
  382. local _, pfds, rv = nil, {}, { code = -1, pid = pid }
  383. xclose(out_w)
  384. xclose(err_w)
  385. if out_r then
  386. pfds[#pfds+1] = {
  387. fd = out_r,
  388. cb = type(stdout) == "function" and stdout,
  389. name = "stdout",
  390. events = nixio.poll_flags("in", "err", "hup")
  391. }
  392. end
  393. if err_r then
  394. pfds[#pfds+1] = {
  395. fd = err_r,
  396. cb = type(stderr) == "function" and stderr,
  397. name = "stderr",
  398. events = nixio.poll_flags("in", "err", "hup")
  399. }
  400. end
  401. while #pfds > 0 do
  402. local nfds, err = nixio.poll(pfds, -1)
  403. if not nfds and err ~= nixio.const.EINTR then
  404. break
  405. end
  406. local i
  407. for i = #pfds, 1, -1 do
  408. local rfd = pfds[i]
  409. if rfd.revents > 0 then
  410. local chunk, err = rfd.fd:read(4096)
  411. if chunk and #chunk > 0 then
  412. if rfd.cb then
  413. rfd.cb(chunk)
  414. else
  415. rfd.buf = rfd.buf or {}
  416. rfd.buf[#rfd.buf + 1] = chunk
  417. end
  418. else
  419. table.remove(pfds, i)
  420. if rfd.buf then
  421. rv[rfd.name] = table.concat(rfd.buf, "")
  422. end
  423. rfd.fd:close()
  424. end
  425. end
  426. end
  427. end
  428. if not nowait then
  429. _, _, rv.code = nixio.waitpid(pid)
  430. end
  431. return rv
  432. end
  433. user = {}
  434. -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
  435. user.getuser = nixio.getpw
  436. function user.getpasswd(username)
  437. local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
  438. local pwh = pwe and (pwe.pwdp or pwe.passwd)
  439. if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
  440. return nil, pwe
  441. else
  442. return pwh, pwe
  443. end
  444. end
  445. function user.checkpasswd(username, pass)
  446. local pwh, pwe = user.getpasswd(username)
  447. if pwe then
  448. return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
  449. end
  450. return false
  451. end
  452. function user.setpasswd(username, password)
  453. return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
  454. luci.util.shellquote(password),
  455. luci.util.shellquote(password),
  456. luci.util.shellquote(username)
  457. })
  458. end
  459. wifi = {}
  460. function wifi.getiwinfo(ifname)
  461. local ntm = require "luci.model.network"
  462. ntm.init()
  463. local wnet = ntm:get_wifinet(ifname)
  464. if wnet and wnet.iwinfo then
  465. return wnet.iwinfo
  466. end
  467. local wdev = ntm:get_wifidev(ifname)
  468. if wdev and wdev.iwinfo then
  469. return wdev.iwinfo
  470. end
  471. return { ifname = ifname }
  472. end
  473. init = {}
  474. init.dir = "/etc/init.d/"
  475. function init.names()
  476. local names = { }
  477. for name in fs.glob(init.dir.."*") do
  478. names[#names+1] = fs.basename(name)
  479. end
  480. return names
  481. end
  482. function init.index(name)
  483. if fs.access(init.dir..name) then
  484. return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
  485. %{ init.dir, name })
  486. end
  487. end
  488. local function init_action(action, name)
  489. if fs.access(init.dir..name) then
  490. return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
  491. end
  492. end
  493. function init.enabled(name)
  494. return (init_action("enabled", name) == 0)
  495. end
  496. function init.enable(name)
  497. return (init_action("enable", name) == 0)
  498. end
  499. function init.disable(name)
  500. return (init_action("disable", name) == 0)
  501. end
  502. function init.start(name)
  503. return (init_action("start", name) == 0)
  504. end
  505. function init.stop(name)
  506. return (init_action("stop", name) == 0)
  507. end
  508. function init.restart(name)
  509. return (init_action("restart", name) == 0)
  510. end
  511. function init.reload(name)
  512. return (init_action("reload", name) == 0)
  513. end