3 local json = require "luci.jsonc"
4 local fs = require "nixio.fs"
6 local function readfile(path)
7 local s = fs.readfile(path)
8 return s and (s:gsub("^%s+", ""):gsub("%s+$", ""))
13 args = { name = "name" },
15 local sys = require "luci.sys"
16 local _, name, scripts = nil, nil, {}
17 for _, name in ipairs(args.name and { args.name } or sys.init.names()) do
18 local index = sys.init.index(name)
20 scripts[name] = { index = index, enabled = sys.init.enabled(name) }
22 return { error = "No such init script" }
30 args = { name = "name", action = "action" },
32 local sys = require "luci.sys"
33 if type(sys.init[args.action]) ~= "function" then
34 return { error = "Invalid action" }
36 return { result = sys.init[args.action](args.name) }
42 return { result = os.time() }
47 args = { localtime = 0 },
49 local sys = require "luci.sys"
50 local date = os.date("*t", args.localtime)
52 sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec })
53 sys.call("/etc/init.d/sysfixtime restart >/dev/null")
55 return { result = args.localtime }
61 local util = require "luci.util"
62 local zones = require "luci.sys.zoneinfo"
64 local tz = readfile("/etc/TZ")
65 local res = util.ubus("uci", "get", {
67 section = "@system[0]",
73 for _, zone in ipairs(zones.TZ) do
76 active = (res and res.value == zone[1]) and true or nil
85 local iter = fs.dir("/sys/class/leds")
93 result[led] = { triggers = {} }
95 s = readfile("/sys/class/leds/"..led.."/trigger")
96 for s in (s or ""):gmatch("%S+") do
97 m = s:match("^%[(.+)%]$")
98 result[led].triggers[#result[led].triggers+1] = m or s
99 result[led].active_trigger = m or result[led].active_trigger
102 s = readfile("/sys/class/leds/"..led.."/brightness")
104 result[led].brightness = tonumber(s)
107 s = readfile("/sys/class/leds/"..led.."/max_brightness")
109 result[led].max_brightness = tonumber(s)
120 local fs = require "nixio.fs"
121 local iter = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
129 local id = p:match("/([^/]+)/manufacturer$")
131 result.devices[#result.devices+1] = {
133 vid = readfile("/sys/bus/usb/devices/"..id.."/idVendor"),
134 pid = readfile("/sys/bus/usb/devices/"..id.."/idProduct"),
135 vendor = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"),
136 product = readfile("/sys/bus/usb/devices/"..id.."/product"),
137 speed = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product")))
142 iter = fs.glob("/sys/bus/usb/devices/*/*-port[0-9]*")
149 local port = p:match("([^/]+)$")
150 local link = fs.readlink(p.."/device")
152 result.ports[#result.ports+1] = {
154 device = link and fs.basename(link)
163 getConntrackHelpers = {
165 local ok, fd = pcall(io.open, "/usr/share/fw3/helpers.conf", "r")
172 local line = fd:read("*l")
177 if line:match("^%s*config%s") then
183 local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
185 opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
186 val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
199 return { result = rv }
205 local fs = require "nixio.fs"
209 rv.firewall = fs.access("/sbin/fw3")
210 rv.opkg = fs.access("/bin/opkg")
211 rv.offloading = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt")
212 rv.br2684ctl = fs.access("/usr/sbin/br2684ctl")
213 rv.swconfig = fs.access("/sbin/swconfig")
214 rv.odhcpd = fs.access("/usr/sbin/odhcpd")
215 rv.zram = fs.access("/sys/class/zram-control")
216 rv.sysntpd = fs.readlink("/usr/sbin/ntpd") and true
217 rv.ipv6 = fs.access("/proc/net/ipv6_route")
218 rv.dropbear = fs.access("/usr/sbin/dropbear")
219 rv.cabundle = fs.access("/etc/ssl/certs/ca-certificates.crt")
220 rv.relayd = fs.access("/usr/sbin/relayd")
221 rv.dsl = fs.access("/sbin/vdsl_cpe_control")
223 local wifi_features = { "eap", "11n", "11ac", "11r", "acs", "sae", "owe", "suiteb192", "wep", "wps" }
225 if fs.access("/usr/sbin/hostapd") then
226 rv.hostapd = { cli = fs.access("/usr/sbin/hostapd_cli") }
229 for _, feature in ipairs(wifi_features) do
230 rv.hostapd[feature] =
231 (os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
235 if fs.access("/usr/sbin/wpa_supplicant") then
236 rv.wpasupplicant = { cli = fs.access("/usr/sbin/wpa_cli") }
239 for _, feature in ipairs(wifi_features) do
240 rv.wpasupplicant[feature] =
241 (os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0)
245 ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null")
250 local line = fd:read("*l")
255 local opts = line:match("^Compile time options: (.+)$")
258 for opt in opts:gmatch("%S+") do
259 local no = opt:match("^no%-(%S+)$")
260 rv.dnsmasq[string.lower(no or opt)] = not no
269 ok, fd = pcall(io.popen, "ipset --help 2>/dev/null")
276 local line = fd:read("*l")
279 elseif line:match("^Supported set types:") then
282 local set, ver = line:match("^%s+(%S+)%s+(%d+)")
283 if set and not rv.ipset[set] then
284 rv.ipset[set] = tonumber(ver)
296 getSwconfigFeatures = {
297 args = { switch = "switch0" },
298 call = function(args)
299 local util = require "luci.util"
301 -- Parse some common switch properties from swconfig help output.
302 local swc, err = io.popen("swconfig dev %s help 2>/dev/null" % util.shellquote(args.switch))
304 local is_port_attr = false
305 local is_vlan_attr = false
309 local line = swc:read("*l")
310 if not line then break end
312 if line:match("^%s+%-%-vlan") then
315 elseif line:match("^%s+%-%-port") then
319 elseif line:match("cpu @") then
320 rv.switch_title = line:match("^switch%d: %w+%((.-)%)")
321 rv.num_vlans = tonumber(line:match("vlans: (%d+)")) or 16
324 elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
325 if is_vlan_attr then rv.vid_option = line:match(": (%w+)") end
327 elseif line:match(": enable_vlan4k") then
328 rv.vlan4k_option = "enable_vlan4k"
330 elseif line:match(": enable_vlan") then
331 rv.vlan_option = "enable_vlan"
333 elseif line:match(": enable_learning") then
334 rv.learning_option = "enable_learning"
336 elseif line:match(": enable_mirror_rx") then
337 rv.mirror_option = "enable_mirror_rx"
339 elseif line:match(": max_length") then
340 rv.jumbo_option = "max_length"
347 return { error = "No such switch" }
352 return { error = err }
357 getSwconfigPortState = {
358 args = { switch = "switch0" },
359 call = function(args)
360 local util = require "luci.util"
362 local swc, err = io.popen("swconfig dev %s show 2>/dev/null" % util.shellquote(args.switch))
367 local line = swc:read("*l")
368 if not line or (line:match("^VLAN %d+:") and #ports > 0) then
372 local pnum = line:match("^Port (%d+):$")
375 port = tonumber(pnum),
384 ports[#ports+1] = port
390 if line:match("full[%- ]duplex") then
394 m = line:match(" speed:(%d+)")
396 port.speed = tonumber(m)
399 m = line:match("(%d+) Mbps")
400 if m and port.speed == 0 then
401 port.speed = tonumber(m)
404 m = line:match("link: (%d+)")
405 if m and port.speed == 0 then
406 port.speed = tonumber(m)
409 if line:match("link: ?up") or line:match("status: ?up") then
413 if line:match("auto%-negotiate") or line:match("link:.-auto") then
417 if line:match("link:.-rxflow") then
421 if line:match("link:.-txflow") then
429 if not next(ports) then
430 return { error = "No such switch" }
433 return { result = ports }
435 return { error = err }
441 args = { username = "root", password = "password" },
442 call = function(args)
443 local util = require "luci.util"
445 result = (os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
446 luci.util.shellquote(args.password),
447 luci.util.shellquote(args.password),
448 luci.util.shellquote(args.username)
456 local fs = require "nixio.fs"
458 local block = io.popen("/sbin/block info", "r")
463 local ln = block:read("*l")
468 local dev = ln:match("^/dev/(.-):")
470 local s = tonumber((fs.readfile("/sys/class/block/" .. dev .."/size")))
472 dev = "/dev/" .. dev,
477 for key, val in ln:gmatch([[(%w+)="(.-)"]]) do
489 return { error = "Unable to execute block utility" }
496 return { result = (os.execute("/sbin/block detect > /etc/config/fstab") == 0) }
502 local fs = require "nixio.fs"
504 local fd, err = io.open("/proc/mounts", "r")
509 local ln = fd:read("*l")
514 local device, mount, fstype, options, freq, pass = ln:match("^(%S*) (%S*) (%S*) (%S*) (%d+) (%d+)$")
515 if device and mount then
516 device = device:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end)
517 mount = mount:gsub("\\(%d+)", function(n) return string.char(tonumber(n, 8)) end)
519 local stat = fs.statvfs(mount)
520 if stat and stat.blocks > 0 then
524 size = stat.bsize * stat.blocks,
525 avail = stat.bsize * stat.bavail,
526 free = stat.bsize * stat.bfree
534 return { result = rv }
536 return { error = err }
542 args = { mode = "interface", device = "eth0" },
543 call = function(args)
544 local util = require "luci.util"
547 if args.mode == "interface" then
548 flags = "-i %s" % util.shellquote(args.device)
549 elseif args.mode == "wireless" then
550 flags = "-r %s" % util.shellquote(args.device)
551 elseif args.mode == "conntrack" then
553 elseif args.mode == "load" then
556 return { error = "Invalid mode" }
559 local fd, err = io.popen("luci-bwc %s" % flags, "r")
561 local parse = json.new()
567 local ln = fd:read("*l")
572 done, err = parse:parse((ln:gsub("%d+", "%1.0")))
575 err = "Unexpected JSON data"
585 done, err = parse:parse("]")
588 return { error = err }
590 return { error = "Incomplete JSON data" }
592 return { result = parse:get() }
595 return { error = err }
602 local sys = require "luci.sys"
603 return { result = sys.net.conntrack() }
609 local sys = require "luci.sys"
611 for _, v in pairs(sys.process.list()) do
614 return { result = res }
619 local function parseInput()
620 local parse = json.new()
624 local chunk = io.read(4096)
627 elseif not done and not err then
628 done, err = parse:parse(chunk)
633 print(json.stringify({ error = err or "Incomplete input" }))
640 local function validateArgs(func, uargs)
641 local method = methods[func]
643 print(json.stringify({ error = "Method not found" }))
647 if type(uargs) ~= "table" then
648 print(json.stringify({ error = "Invalid arguments" }))
652 uargs.ubus_rpc_session = nil
655 local margs = method.args or {}
656 for k, v in pairs(uargs) do
657 if margs[k] == nil or
658 (v ~= nil and type(v) ~= type(margs[k]))
660 print(json.stringify({ error = "Invalid arguments" }))
668 if arg[1] == "list" then
669 local _, method, rv = nil, nil, {}
670 for _, method in pairs(methods) do rv[_] = method.args or {} end
671 print((json.stringify(rv):gsub(":%[%]", ":{}")))
672 elseif arg[1] == "call" then
673 local args = parseInput()
674 local method = validateArgs(arg[2], args)
675 local result, code = method.call(args)
676 print((json.stringify(result):gsub("^%[%]$", "{}")))