sys.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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, rest =
  255. line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$")
  256. local timeout, tuples = rest:match("^(%d+) +(.+)$")
  257. if not tuples then
  258. tuples = rest
  259. end
  260. if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then
  261. l4 = nixio.getprotobynumber(l4)
  262. local entry = {
  263. bytes = 0,
  264. packets = 0,
  265. layer3 = fam,
  266. layer4 = l4 and l4.name or "unknown",
  267. timeout = tonumber(timeout, 10)
  268. }
  269. local key, val
  270. for key, val in tuples:gmatch("(%w+)=(%S+)") do
  271. if key == "bytes" or key == "packets" then
  272. entry[key] = entry[key] + tonumber(val, 10)
  273. elseif key == "src" or key == "dst" then
  274. if entry[key] == nil then
  275. entry[key] = luci.ip.new(val):string()
  276. end
  277. elseif key == "sport" or key == "dport" then
  278. if entry[key] == nil then
  279. entry[key] = val
  280. end
  281. elseif val then
  282. entry[key] = val
  283. end
  284. end
  285. if callback then
  286. callback(entry)
  287. else
  288. connt[#connt+1] = entry
  289. end
  290. end
  291. end
  292. return callback and true or connt
  293. end
  294. function net.devices()
  295. local devs = {}
  296. local seen = {}
  297. for k, v in ipairs(nixio.getifaddrs()) do
  298. if v.name and not seen[v.name] then
  299. seen[v.name] = true
  300. devs[#devs+1] = v.name
  301. end
  302. end
  303. return devs
  304. end
  305. function net.duid_to_mac(duid)
  306. local b1, b2, b3, b4, b5, b6
  307. if type(duid) == "string" then
  308. -- DUID-LLT / Ethernet
  309. if #duid == 28 then
  310. 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$")
  311. -- DUID-LL / Ethernet
  312. elseif #duid == 20 then
  313. b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  314. -- DUID-LL / Ethernet (Without Header)
  315. elseif #duid == 12 then
  316. b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
  317. end
  318. end
  319. return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
  320. end
  321. process = {}
  322. function process.info(key)
  323. local s = {uid = nixio.getuid(), gid = nixio.getgid()}
  324. return not key and s or s[key]
  325. end
  326. function process.list()
  327. local data = {}
  328. local k
  329. local ps = luci.util.execi("/bin/busybox top -bn1")
  330. if not ps then
  331. return
  332. end
  333. for line in ps do
  334. local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
  335. "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+m?) +(%d+%%) +(%d+%%) +(.+)"
  336. )
  337. local idx = tonumber(pid)
  338. if idx and not cmd:match("top %-bn1") then
  339. data[idx] = {
  340. ['PID'] = pid,
  341. ['PPID'] = ppid,
  342. ['USER'] = user,
  343. ['STAT'] = stat,
  344. ['VSZ'] = vsz,
  345. ['%MEM'] = mem,
  346. ['%CPU'] = cpu,
  347. ['COMMAND'] = cmd
  348. }
  349. end
  350. end
  351. return data
  352. end
  353. function process.setgroup(gid)
  354. return nixio.setgid(gid)
  355. end
  356. function process.setuser(uid)
  357. return nixio.setuid(uid)
  358. end
  359. process.signal = nixio.kill
  360. local function xclose(fd)
  361. if fd and fd:fileno() > 2 then
  362. fd:close()
  363. end
  364. end
  365. function process.exec(command, stdout, stderr, nowait)
  366. local out_r, out_w, err_r, err_w
  367. if stdout then out_r, out_w = nixio.pipe() end
  368. if stderr then err_r, err_w = nixio.pipe() end
  369. local pid = nixio.fork()
  370. if pid == 0 then
  371. nixio.chdir("/")
  372. local null = nixio.open("/dev/null", "w+")
  373. if null then
  374. nixio.dup(out_w or null, nixio.stdout)
  375. nixio.dup(err_w or null, nixio.stderr)
  376. nixio.dup(null, nixio.stdin)
  377. xclose(out_w)
  378. xclose(out_r)
  379. xclose(err_w)
  380. xclose(err_r)
  381. xclose(null)
  382. end
  383. nixio.exec(unpack(command))
  384. os.exit(-1)
  385. end
  386. local _, pfds, rv = nil, {}, { code = -1, pid = pid }
  387. xclose(out_w)
  388. xclose(err_w)
  389. if out_r then
  390. pfds[#pfds+1] = {
  391. fd = out_r,
  392. cb = type(stdout) == "function" and stdout,
  393. name = "stdout",
  394. events = nixio.poll_flags("in", "err", "hup")
  395. }
  396. end
  397. if err_r then
  398. pfds[#pfds+1] = {
  399. fd = err_r,
  400. cb = type(stderr) == "function" and stderr,
  401. name = "stderr",
  402. events = nixio.poll_flags("in", "err", "hup")
  403. }
  404. end
  405. while #pfds > 0 do
  406. local nfds, err = nixio.poll(pfds, -1)
  407. if not nfds and err ~= nixio.const.EINTR then
  408. break
  409. end
  410. local i
  411. for i = #pfds, 1, -1 do
  412. local rfd = pfds[i]
  413. if rfd.revents > 0 then
  414. local chunk, err = rfd.fd:read(4096)
  415. if chunk and #chunk > 0 then
  416. if rfd.cb then
  417. rfd.cb(chunk)
  418. else
  419. rfd.buf = rfd.buf or {}
  420. rfd.buf[#rfd.buf + 1] = chunk
  421. end
  422. else
  423. table.remove(pfds, i)
  424. if rfd.buf then
  425. rv[rfd.name] = table.concat(rfd.buf, "")
  426. end
  427. rfd.fd:close()
  428. end
  429. end
  430. end
  431. end
  432. if not nowait then
  433. _, _, rv.code = nixio.waitpid(pid)
  434. end
  435. return rv
  436. end
  437. user = {}
  438. -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
  439. user.getuser = nixio.getpw
  440. function user.getpasswd(username)
  441. local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
  442. local pwh = pwe and (pwe.pwdp or pwe.passwd)
  443. if not pwh or #pwh < 1 then
  444. return nil, pwe
  445. else
  446. return pwh, pwe
  447. end
  448. end
  449. function user.checkpasswd(username, pass)
  450. local pwh, pwe = user.getpasswd(username)
  451. if pwe then
  452. return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
  453. end
  454. return false
  455. end
  456. function user.setpasswd(username, password)
  457. return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
  458. luci.util.shellquote(password),
  459. luci.util.shellquote(password),
  460. luci.util.shellquote(username)
  461. })
  462. end
  463. wifi = {}
  464. function wifi.getiwinfo(ifname)
  465. local ntm = require "luci.model.network"
  466. ntm.init()
  467. local wnet = ntm:get_wifinet(ifname)
  468. if wnet and wnet.iwinfo then
  469. return wnet.iwinfo
  470. end
  471. local wdev = ntm:get_wifidev(ifname)
  472. if wdev and wdev.iwinfo then
  473. return wdev.iwinfo
  474. end
  475. return { ifname = ifname }
  476. end
  477. init = {}
  478. init.dir = "/etc/init.d/"
  479. function init.names()
  480. local names = { }
  481. for name in fs.glob(init.dir.."*") do
  482. names[#names+1] = fs.basename(name)
  483. end
  484. return names
  485. end
  486. function init.index(name)
  487. name = fs.basename(name)
  488. if fs.access(init.dir..name) then
  489. return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
  490. %{ init.dir, name })
  491. end
  492. end
  493. local function init_action(action, name)
  494. name = fs.basename(name)
  495. if fs.access(init.dir..name) then
  496. return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
  497. end
  498. end
  499. function init.enabled(name)
  500. return (init_action("enabled", name) == 0)
  501. end
  502. function init.enable(name)
  503. return (init_action("enable", name) == 0)
  504. end
  505. function init.disable(name)
  506. return (init_action("disable", name) == 0)
  507. end
  508. function init.start(name)
  509. return (init_action("start", name) == 0)
  510. end
  511. function init.stop(name)
  512. return (init_action("stop", name) == 0)
  513. end
  514. function init.restart(name)
  515. return (init_action("restart", name) == 0)
  516. end
  517. function init.reload(name)
  518. return (init_action("reload", name) == 0)
  519. end