#!/usr/bin/env lua --[[ Copyright 2017 Marcos Gutierrez Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-3.0 ]]-- require "ubus" local nixio = require "nixio",require "nixio.fs" local split = require 'lime.utils'.split local json = require 'luci.jsonc' local function printJson (obj) print(json.stringify(obj)) end local function shell(command) -- TODO(nicoechaniz): sanitize or evaluate if this is a security risk local handle = io.popen(command) local result = handle:read("*a") handle:close() return result end local conn = ubus.connect() if not conn then error("Failed to connect to ubus") end function bmx6get(field, host) local url if host ~= nil then if host:match(":") then url = 'http://['..host..']/cgi-bin/bmx6-info?' else url = 'http://'..host..'/cgi-bin/bmx6-info?' end else url = "exec:/www/cgi-bin/bmx6-info -s" end if url == nil then print_error("bmx6 json url not configured, cannot fetch bmx6 daemon data",true) return nil end local json_url = split(url,":") local raw = "" if json_url[1] == "http" then raw,err = wget(url..field,500) else if json_url[1] == "exec" then raw = shell(json_url[2]..' '..field) else print_error("bmx6 json url not recognized, cannot fetch bmx6 daemon data. Use http: or exec:",true) return nil end end local data = nil if raw and raw:len() > 10 then data = json.parse( raw ) end return data end function wget(url, timeout) local rfd, wfd = nixio.pipe() local pid = nixio.fork() if pid == 0 then rfd:close() nixio.dup(wfd, nixio.stdout) local candidates = { "/usr/bin/wget", "/bin/wget" } local _, bin for _, bin in ipairs(candidates) do if nixio.fs.access(bin, "x") then nixio.exec(bin, "--no-check-certificate", "-q", "-O", "-",url) end end return else wfd:close() rfd:setblocking(false) local buffer = { } local err1, err2 while true do local ready = nixio.poll({{ fd = rfd, events = nixio.poll_flags("in") }}, timeout) if not ready then nixio.kill(pid, nixio.const.SIGKILL) err1 = "timeout" break end local rv = rfd:read(4096) if rv then -- eof if #rv == 0 then break end buffer[#buffer+1] = rv else -- error if nixio.errno() ~= nixio.const.EAGAIN and nixio.errno() ~= nixio.const.EWOULDBLOCK then err1 = "error" err2 = nixio.errno() end end end nixio.waitpid(pid, "untraced") if not err1 then return table.concat(buffer) else return nil, err1, err2 end end end function status(msg) local status = bmx6get("status").status or nil local interfaces = bmx6get("interfaces").interfaces or nil local result if status == nil or interfaces == nil then result = { error = "Cannot fetch data from bmx6 json"} else result = { status = status, interfaces = interfaces } end printJson(result) end function originators(msg) local orig_list = bmx6get("originators").originators if orig_list == nil then conn:reply(req, { error = "Cannot fetch data from bmx6 json"}) else local originators = {} local desc = nil local orig = nil local name = "" local ipv4 = "" for _,o in ipairs(orig_list) do orig = bmx6get("originators/"..o.name) or {} desc = bmx6get("descriptions/"..o.name) or {} if string.find(o.name,'.') then name = split(o.name,'.')[1] else name = o.name end table.insert(originators,{name=name,orig=orig,desc=desc}) end printJson({originators = originators }) end end function links(msg) local links = bmx6get("links", msg.host) local devlinks = {} local _,l if links ~= nil then links = links.links for _,l in ipairs(links) do devlinks[l.viaDev] = {} end for _,l in ipairs(links) do l.name = split(l.name,'.')[1] table.insert(devlinks[l.viaDev],l) end end printJson({ links = devlinks }) end function topology(msg) local originators = bmx6get("originators/all") local o,i,l,i2 local first = true local topology = '[ ' local cache = '/tmp/bmx6-topology.json' local offset = 60 local cachefd = io.open(cache,r) local update = false if cachefd ~= nil then local lastupdate = tonumber(cachefd:read("*line")) or 0 if os.time() >= lastupdate + offset then update = true else topology = cachefd:read("*all") end cachefd:close() end if cachefd == nil or update then for i,o in ipairs(originators) do local links = bmx6get("links",o.name) if links then if first then first = false else topology = topology .. ', ' end topology = topology .. '{ "name": "'.. o.name ..'", "links": [' local first2 = true for i2,l in ipairs(links.links) do if first2 then first2 = false else topology = topology .. ', ' end name = l.name or l.llocalIp or "unknown" topology = topology .. '{ "name": "'.. name ..'", "rxRate": '.. l.rxRate ..', "txRate": '.. l.txRate .. ' }' end topology = topology .. ']}' else print('Error load links of '..o.name) end end topology = topology .. ' ]' -- Upgrading the content of the cache file cachefd = io.open(cache,'w+') cachefd:write(os.time()..'\n') cachefd:write(topology) cachefd:close() printJson({ topology = json.parse( topology ) }) return end printJson({ topology = json.parse( topology ) }) end local function tunnels (msg) local tunnels = bmx6get("tunnels").tunnels printJson({ tunnels = tunnels }) end local methods = { status = { no_params = 0 }, originators = { no_params = 0 }, links = { host = 'value' }, topology = { no_params = 0 }, tunnels = { no_params = 0 } } if arg[1] == 'list' then printJson(methods) end if arg[1] == 'call' then local msg = io.read() msg = json.parse(msg) if arg[2] == 'status' then status(msg) elseif arg[2] == 'originators' then originators(msg) elseif arg[2] == 'links' then links(msg) elseif arg[2] == 'topology' then topology(msg) elseif arg[2] == 'tunnels' then tunnels(msg) else printJson({ error = "Method not found" }) end end