luci-base: luci.js: introduce hasSystemFeature() api
authorJo-Philipp Wich <jo@mein.io>
Wed, 14 Aug 2019 14:42:55 +0000 (16:42 +0200)
committerJo-Philipp Wich <jo@mein.io>
Wed, 14 Aug 2019 20:58:15 +0000 (22:58 +0200)
The new function allows querying the presence of certain system features
such as dnsmasq or firewall availability or the compile time features
of hostapd and wpa_supplicant.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-base/htdocs/luci-static/resources/luci.js
modules/luci-base/root/usr/libexec/rpcd/luci
modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json

index 66f32d72237d68abec63e76a692efafae31b63c1..d72764b11449be885466bba6a438406ef3881932 100644 (file)
            domParser = null,
            originalCBIInit = null,
            rpcBaseURL = null,
+           sysFeatures = null,
            classes = {};
 
        var LuCI = Class.extend({
                        return Promise.resolve(rpcBaseURL);
                },
 
+               probeSystemFeatures: function() {
+                       if (sysFeatures == null) {
+                               try {
+                                       sysFeatures = JSON.parse(window.sessionStorage.getItem('sysFeatures'));
+                               }
+                               catch (e) {}
+                       }
+
+                       if (!this.isObject(sysFeatures)) {
+                               sysFeatures = classes.rpc.declare({
+                                       object: 'luci',
+                                       method: 'getFeatures',
+                                       expect: { '': {} }
+                               })().then(function(features) {
+                                       try {
+                                               window.sessionStorage.setItem('sysFeatures', JSON.stringify(features));
+                                       }
+                                       catch (e) {}
+
+                                       sysFeatures = features;
+
+                                       return features;
+                               });
+                       }
+
+                       return Promise.resolve(sysFeatures);
+               },
+
+               hasSystemFeature: function() {
+                       var ft = sysFeatures[arguments[0]];
+
+                       if (arguments.length == 2)
+                               return this.isObject(ft) ? ft[arguments[1]] : null;
+
+                       return (ft != null && ft != false);
+               },
+
                setupDOM: function(res) {
                        var domEv = res[0],
                            uiClass = res[1],
                                throw 'Session expired';
                        });
 
-                       originalCBIInit();
+                       return this.probeSystemFeatures().finally(this.initDOM);
+               },
 
+               initDOM: function() {
+                       originalCBIInit();
                        Poll.start();
-
                        document.dispatchEvent(new CustomEvent('luci-loaded'));
                },
 
index 7644745efd6edcf92f23f0e26ddb3dcc056ce328..d2e90bc35d6d11c0e41d1270730a7daacb6eeb2a 100755 (executable)
@@ -320,6 +320,92 @@ local methods = {
 
                        return { helpers = rv }
                end
+       },
+
+       getFeatures = {
+               call = function()
+                       local fs = require "nixio.fs"
+                       local rv = {}
+                       local ok, fd
+
+                       rv.firewall      = fs.access("/sbin/fw3")
+                       rv.opkg          = fs.access("/bin/opkg")
+                       rv.offloading    = fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt")
+                       rv.br2684ctl     = fs.access("/usr/sbin/br2684ctl")
+                       rv.swconfig      = fs.access("/sbin/swconfig")
+                       rv.odhcpd        = fs.access("/usr/sbin/odhcpd")
+
+                       local wifi_features = { "eap", "11n", "11ac", "11r", "11w", "acs", "sae", "owe", "suiteb192" }
+
+                       if fs.access("/usr/sbin/hostapd") then
+                               rv.hostapd = {}
+
+                               local _, feature
+                               for _, feature in ipairs(wifi_features) do
+                                       rv.hostapd[feature] =
+                                               (os.execute(string.format("/usr/sbin/hostapd -v%s >/dev/null 2>/dev/null", feature)) == 0)
+                               end
+                       end
+
+                       if fs.access("/usr/sbin/wpa_supplicant") then
+                               rv.wpasupplicant = {}
+
+                               local _, feature
+                               for _, feature in ipairs(wifi_features) do
+                                       rv.wpasupplicant[feature] =
+                                               (os.execute(string.format("/usr/sbin/wpa_supplicant -v%s >/dev/null 2>/dev/null", feature)) == 0)
+                               end
+                       end
+
+                       ok, fd = pcall(io.popen, "dnsmasq --version 2>/dev/null")
+                       if ok then
+                               rv.dnsmasq = {}
+
+                               while true do
+                                       local line = fd:read("*l")
+                                       if not line then
+                                               break
+                                       end
+
+                                       local opts = line:match("^Compile time options: (.+)$")
+                                       if opts then
+                                               local opt
+                                               for opt in opts:gmatch("%S+") do
+                                                       local no = opt:match("^no%-(%S+)$")
+                                                       rv.dnsmasq[string.lower(no or opt)] = not no
+                                               end
+                                               break
+                                       end
+                               end
+
+                               fd:close()
+                       end
+
+                       ok, fd = pcall(io.popen, "ipset --help 2>/dev/null")
+                       if ok then
+                               rv.ipset = {}
+
+                               local sets = false
+
+                               while true do
+                                       local line = fd:read("*l")
+                                       if not line then
+                                               break
+                                       elseif line:match("^Supported set types:") then
+                                               sets = true
+                                       elseif sets then
+                                               local set, ver = line:match("^%s+(%S+)%s+(%d+)")
+                                               if set and not rv.ipset[set] then
+                                                       rv.ipset[set] = tonumber(ver)
+                                               end
+                                       end
+                               end
+
+                               fd:close()
+                       end
+
+                       return rv
+               end
        }
 }
 
index e58c9947bc5204d55b40fccdb0fa33ee62e879d3..5d08a4eab85952481b4e4d9cb8af8a96d0a1963a 100644 (file)
@@ -1,4 +1,13 @@
 {
+       "unauthenticated": {
+               "description": "Allow system feature probing",
+               "read": {
+                       "ubus": {
+                               "luci": [ "getFeatures" ]
+                       }
+               }
+       },
+
        "uci-access": {
                "description": "Grant uci write access to all configurations",
                "read": {