luci-app-fwknopd: Client-side rendering and improvements/fixes.
authorOldřich Jedlička <oldium.pro@gmail.com>
Mon, 5 Oct 2020 07:50:01 +0000 (09:50 +0200)
committerOldřich Jedlička <oldium.pro@gmail.com>
Sat, 31 Oct 2020 08:40:38 +0000 (09:40 +0100)
 * Use client-side rendering.
 * Asynchronous QR code generation and on-the-fly updates.
 * Added button to generate keys.
 * Added keys validation (empty, __CHANGEME__ and CHANGEME values are not
   valid).
 * Added SOURCE option non-empty validation (SOURCE is mandatory field).
 * Added network and DESTINATION options.
 * Use checkboxes for Y/N options instead of text fields.
 * Fix the qrencode tool, which currently has only `--inline` option, not
   `-I`. Use `--inline` instead.
 * Improve configuration handling by removing the key type from the config
   file. The key type is deduced from the actual values (normal/base64).
 * Provide migration script (clean-up of stale keytype/hkeytype values in
   config).
 * Fixed usage of translations.

Signed-off-by: Oldřich Jedlička <oldium.pro@gmail.com>
applications/luci-app-fwknopd/Makefile
applications/luci-app-fwknopd/htdocs/luci-static/resources/view/fwknopd.js [new file with mode: 0644]
applications/luci-app-fwknopd/luasrc/model/cbi/fwknopd.lua [deleted file]
applications/luci-app-fwknopd/luasrc/view/fwknopd-qr.htm [deleted file]
applications/luci-app-fwknopd/root/etc/uci-defaults/40_luci-fwknopd
applications/luci-app-fwknopd/root/usr/sbin/gen-qr.sh [deleted file]
applications/luci-app-fwknopd/root/usr/share/luci/menu.d/luci-app-fwknopd.json
applications/luci-app-fwknopd/root/usr/share/rpcd/acl.d/luci-app-fwknopd.json

index 73a9e6864ff1dc10574c16dc305268af96b6977c..ba7a8568e7caff0218746a74ed03b80d885b261b 100644 (file)
@@ -7,7 +7,7 @@
 include $(TOPDIR)/rules.mk
 
 LUCI_TITLE:=Fwknopd config - web config for the firewall knock daemon
-LUCI_DEPENDS:=+luci-compat +fwknopd +qrencode
+LUCI_DEPENDS:=+fwknopd +qrencode
 PKG_LICENSE:=GPLv2
 PKG_MAINTAINER:=Jonathan Bennett <JBennett@incomsystems.biz>
 include ../../luci.mk
diff --git a/applications/luci-app-fwknopd/htdocs/luci-static/resources/view/fwknopd.js b/applications/luci-app-fwknopd/htdocs/luci-static/resources/view/fwknopd.js
new file mode 100644 (file)
index 0000000..b88e119
--- /dev/null
@@ -0,0 +1,379 @@
+'use strict';
+'require fs';
+'require dom';
+'require view';
+'require form';
+'require ui';
+'require tools.widgets as widgets';
+
+var domparser = new DOMParser();
+var QRCODE_VARIABLES = ['KEY_BASE64', 'KEY', 'HMAC_KEY_BASE64', 'HMAC_KEY'];
+var INVALID_KEYS = ['__CHANGEME__', 'CHANGEME'];
+
+function setOptionValue(map, section_id, option, value) {
+       var option = L.toArray(map.lookupOption(option, section_id))[0];
+       var uiEl = option ? option.getUIElement(section_id) : null;
+       if (uiEl)
+               uiEl.setValue(value);
+}
+
+function lines(content) {
+       return content.split(/\r?\n/);
+}
+
+function parseKeys(content) {
+       var l = lines(content);
+       var keys = {};
+       for (var i = 0; i < l.length; i++) {
+               var p = l[i].split(/:(.*)/, 2);
+               if (p.length == 2)
+                       keys[p[0].trim()] = p[1].trim();
+       }
+       return keys;
+}
+
+var KeyTypeValue = form.ListValue.extend({
+       __init__: function() {
+               this.super('__init__', arguments);
+       },
+
+       cfgvalue: function(section_id) {
+               for (var i = 0; i < this.keylist.length; i++) {
+                       var value = this.map.data.get(
+                               this.uciconfig || this.section.uciconfig || this.map.config,
+                               this.ucisection || section_id,
+                               this.keylist[i]
+                       );
+                       if (value)
+                               return this.keylist[i];
+               }
+               return this.keylist[0];
+       },
+
+       remove: function() {
+               // Ignore
+       },
+
+       write: function() {
+               // Ignore
+       },
+});
+
+var YNValue = form.Flag.extend({
+       __init__: function() {
+               this.super('__init__', arguments);
+               this.enabled = 'Y';
+               this.disabled = 'N';
+               this.default = 'N';
+       },
+       
+       cfgvalue: function(section_id) {
+               var value = this.super('cfgvalue', arguments);
+               return value ? String(value).toUpperCase() : value;
+       },
+
+       parse: function(section_id) {
+               var active = this.isActive(section_id),
+                   cval = this.cfgvalue(section_id),
+                   fval = active ? this.formvalue(section_id) : null;
+
+               if (String(fval).toUpperCase() != cval) {
+                       if (fval == 'Y')
+                               return Promise.resolve(this.write(section_id, fval));
+                       else if (cval !== undefined)
+                               return Promise.resolve(this.remove(section_id));
+               }
+       },
+});
+
+var QrCodeValue = form.DummyValue.extend({
+       __init__: function() {
+               this.super('__init__', arguments);
+               this.needsRefresh = {};
+
+               this.components = [];
+               QRCODE_VARIABLES.forEach(L.bind(function(option) {
+                       this.components.push(option);
+                       var dep = {};
+                       dep[option] = /.+/;
+                       this.depends(dep);
+               }, this));
+       },
+
+       cfgQrCode: function(section_id) {
+               var qr = [];
+               for (var i = 0; i < this.components.length; i++) {
+                       var value = this.map.data.get(
+                               this.uciconfig || this.section.uciconfig || this.map.config,
+                               this.ucisection || section_id,
+                               this.components[i]
+                       );
+
+                       if (value)
+                               qr.push(this.components[i] + ':' + value);
+               }
+               return qr ? qr.join(' ') : null;
+       },
+
+       formQrCode: function(section_id) {
+               var qr = [];
+               for (var i = 0; i < this.components.length; i++) {
+                       var value = null;
+
+                       var uiEl = L.toArray(this.map.lookupOption(this.components[i], section_id))[0];
+                       if (uiEl) {
+                               if (uiEl.isActive(section_id))
+                                       value = uiEl.formvalue(section_id);
+                       }
+
+                       if (value)
+                               qr.push(this.components[i] + ':' + value);
+               }
+               return qr ? qr.join(' ') : null;
+       },
+
+       onchange: function(ev, section_id) {
+               if (this.needsRefresh[section_id] !== undefined)
+                       this.needsRefresh[section_id] = true;
+               else {
+                       this.refresh(section_id);
+               }
+       },
+
+       refresh: function(section_id) {
+               var qrcode = this.formQrCode(section_id);
+               var formvalue = this.formvalue(section_id);
+               if (formvalue != qrcode) {
+                       this.getUIElement(section_id).setValue(qrcode);
+                       var uiEl = document.getElementById(this.cbid(section_id));
+                       if (uiEl) {
+                               var contentEl = uiEl.nextSibling;
+                               if (contentEl.childNodes.length == 1) {
+                                       dom.append(contentEl, E('em', { 'class': 'spinning',  }, [ _('Loading…') ]));
+                               }
+
+                               this.needsRefresh[section_id] = false;
+
+                               // Render QR code
+                               return this.renderSvg(qrcode)
+                                       .then(L.bind(function(svgEl) {
+                                               dom.content(contentEl, svgEl || E('div'));
+
+                                               var needsAnotherRefresh = this.needsRefresh[section_id];
+                                               delete this.needsRefresh[section_id];
+
+                                               if (needsAnotherRefresh) {
+                                                       this.refresh(section_id);
+                                               }
+                                       }, this)).finally(L.bind(function() {
+                                               if (this.needsRefresh[section_id] === undefined) {
+                                                       if (contentEl.childNodes.length == 2)
+                                                               contentEl.removeChild(contentEl.lastChild);
+                                                       delete this.needsRefresh[section_id];
+                                               }
+                                       }, this)).catch(L.error);
+                       }
+               }
+               // Nothing to render
+               return Promise.resolve(null);
+       },
+
+       renderWidget: function(section_id) {
+               var qrcode = this.cfgQrCode(section_id);
+               return this.renderSvg(qrcode)
+                       .then(L.bind(function(svgEl) {
+                               var uiEl = new ui.Hiddenfield(qrcode, { id: this.cbid(section_id) });
+                               return E([
+                                       uiEl.render(),
+                                       E('div', {}, svgEl || E('div'))
+                               ]);
+                       }, this));
+       },
+
+       qrEncodeSvg: function(qrcode) {
+               return fs.exec('/usr/bin/qrencode', ['--type', 'svg', '--inline', '-o', '-', qrcode])
+                       .then(function(response) {
+                               return response.stdout;
+                       });
+       },
+
+       renderSvg: function(qrcode) {
+               if (qrcode)
+                       return this.qrEncodeSvg(qrcode)
+                               .then(function(rawsvg) {
+                                       return domparser.parseFromString(rawsvg, 'image/svg+xml')
+                                               .querySelector('svg');
+                               });
+               else
+                       return Promise.resolve(null);
+       },
+});
+
+var GenerateButton = form.Button.extend({
+       __init__: function() {
+               this.super('__init__', arguments);
+               this.onclick = L.bind(this.generateKeys, this);
+               this.keytypes = {};
+       },
+
+       keytype: function(key, regex) {
+               this.keytypes[key] = regex;
+       },
+
+       qrcode: function(option) {
+               this.qrcode = option;
+       },
+
+       generateKeys: function(ev, section_id) {
+               return fs.exec('/usr/sbin/fwknopd', ['--key-gen'])
+                       .then(function(response) { return parseKeys(response.stdout); })
+                       .then(L.bind(this.applyKeys, this, section_id))
+                       .catch(L.error);
+       },
+
+       applyKeys: function(section_id, keys) {
+               for (var key in keys) {
+                       setOptionValue(this.map, section_id, key, keys[key]);
+                       for (var type in this.keytypes) {
+                               if (this.keytypes[type].test(key))
+                                       setOptionValue(this.map, section_id, type, key);
+                       }
+               }
+
+               // Force update of dependencies (element visibility)
+               this.map.checkDepends();
+
+               // Refresh QR code
+               var option = L.toArray(this.map.lookupOption(this.qrcode, section_id))[0];
+               if (option)
+                       return option.refresh(section_id);
+               else
+                       return Promise.resolve(null);
+       },
+
+});
+
+return view.extend({
+
+       render: function() {
+               var m, s, o;
+
+               m = new form.Map('fwknopd', _('Firewall Knock Operator Daemon'));
+
+               s = m.section(form.TypedSection, 'global', _('Enable Uci/Luci control'));
+               s.anonymous = true;
+               s.option(form.Flag, 'uci_enabled', _('Enable config overwrite'), _('When unchecked, the config files in /etc/fwknopd will be used as is, ignoring any settings here.'));
+
+               s = m.section(form.TypedSection, 'network', _('Network configuration'));
+               s.anonymous = true;
+               o = s.option(widgets.NetworkSelect, 'network', _('Network'), _('The network on which the daemon listens. The daemon \
+                                                               is automatically started when the network is up-and-running. This option \
+                                                               has precedence over “PCAP_INTF” option.'));
+               o.unpecified = true;
+               o.nocreate = true;
+               o.rmempty = true;
+
+               // set the access.conf settings
+               s = m.section(form.TypedSection, 'access', _('access.conf stanzas'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               var qrCode = s.option(QrCodeValue, 'qr', _('QR code'), ('QR code to configure fwknopd Android application.'));
+
+               o = s.option(form.Value, 'SOURCE', 'SOURCE', _('The source address from which the SPA packet will be accepted. The string “ANY” is \
+                                                               also accepted if a valid SPA packet should be honored from any source IP. \
+                                                               Networks should be specified in CIDR notation (e.g. “192.168.10.0/24”), \
+                                                               and individual IP addresses can be specified as well. Multiple entries \
+                                                               are comma-separated.'));
+               o.validate = function(section_id, value) {
+                       return String(value).length > 0 ? true : _('The source address has to be specified.');
+               }
+
+               s.option(form.Value, 'DESTINATION', 'DESTINATION', _('The destination address for which the SPA packet will be accepted. The \
+                                                               string “ANY” is also accepted if a valid SPA packet should be honored to any \
+                                                               destination IP. Networks should be specified in CIDR notation \
+                                                               (e.g. “192.168.10.0/24”), and individual IP addresses can be specified as well. \
+                                                               Multiple entries are comma-separated.'));
+
+               o = s.option(GenerateButton, 'keys', _('Generate keys'), _('Generates the symmetric key used for decrypting an incoming \
+                                                               SPA packet, that is encrypted by the fwknop client with Rijndael block cipher, \
+                                                               and HMAC authentication key used to verify the authenticity of the incoming SPA \
+                                                               packet before the packet is decrypted.'));
+               o.inputtitle = _("Generate Keys");
+               o.keytype('keytype', /^KEY/);
+               o.keytype('hkeytype', /^HMAC_KEY/);
+               o.qrcode('qr');
+
+               o = s.option(form.Value, 'KEY', 'KEY', _('Define the symmetric key used for decrypting an incoming SPA \
+                                                               packet that is encrypted by the fwknop client with Rijndael.'));
+               o.depends('keytype', 'KEY');
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+               o.validate = function(section_id, value) {
+                       return (String(value).length > 0 && !INVALID_KEYS.includes(value)) ? true : _('The symmetric key has to be specified.');
+               }
+
+               o = s.option(form.Value, 'KEY_BASE64', 'KEY_BASE64', _('Define the symmetric key (in Base64 encoding) used for \
+                                                               decrypting an incoming SPA packet that is encrypted by the fwknop client \
+                                                               with Rijndael.'));
+               o.depends('keytype', 'KEY_BASE64');
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+               o.validate = function(section_id, value) {
+                       return (String(value).length > 0 && !INVALID_KEYS.includes(value)) ? true : _('The symmetric key has to be specified.');
+               }
+
+               o = s.option(KeyTypeValue, 'keytype', _('Key type'));
+               o.value('KEY', _('Normal key'));
+               o.value('KEY_BASE64', _('Base64 key'));
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+
+               o = s.option(form.Value, 'HMAC_KEY', 'HMAC_KEY', _('Define the HMAC authentication key used for verifying \
+                                                               the authenticity of the SPA packet before the packet is decrypted.'));
+               o.depends('hkeytype', 'HMAC_KEY');
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+               o.validate = function(section_id, value) {
+                       return (String(value).length > 0 && !INVALID_KEYS.includes(value)) ? true : _('The HMAC authentication key has to be specified.');
+               }
+
+               o = s.option(form.Value, 'HMAC_KEY_BASE64', 'HMAC_KEY_BASE64', _('Define the HMAC authentication key \
+                                                               (in Base64 encoding) used for verifying the authenticity of the SPA \
+                                                               packet before the packet is decrypted.'));
+               o.depends('hkeytype', 'HMAC_KEY_BASE64');
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+               o.validate = function(section_id, value) {
+                       return (String(value).length > 0 && !INVALID_KEYS.includes(value)) ? true : _('The HMAC authentication key has to be specified.');
+               }
+
+               o = s.option(KeyTypeValue, 'hkeytype', _('HMAC key type'));
+               o.value('HMAC_KEY', _('Normal key'));
+               o.value('HMAC_KEY_BASE64', _('Base64 key'));
+               o.onchange = L.bind(qrCode.onchange, qrCode);
+
+               o = s.option(form.Value, 'OPEN_PORTS', 'OPEN_PORTS', _('Define a set of ports and protocols (tcp or udp) that will be opened if a valid knock sequence is seen. \
+                                                       If this entry is not set, fwknopd will attempt to honor any proto/port request specified in the SPA data \
+                                                       (unless of it matches any “RESTRICT_PORTS” entries). Multiple entries are comma-separated.'));
+               o.placeholder = "protocol/port,...";
+
+               o = s.option(form.Value, 'RESTRICT_PORTS', 'RESTRICT_PORTS', _('Define a set of ports and protocols (tcp or udp) that are explicitly not allowed \
+                                                       regardless of the validity of the incoming SPA packet. Multiple entries are comma-separated.'));
+               o.placeholder = "protocol/port,...";
+
+               o = s.option(form.Value, 'FW_ACCESS_TIMEOUT', 'FW_ACCESS_TIMEOUT', _('Define the length of time access will be granted by fwknopd through the firewall after a \
+                                                       valid knock sequence from a source IP address. If “FW_ACCESS_TIMEOUT” is not set then the default \
+                                                       timeout of 30 seconds will automatically be set.'));
+               o.placeholder = "30";
+
+               s.option(YNValue, 'REQUIRE_SOURCE_ADDRESS', 'REQUIRE_SOURCE_ADDRESS', _('Force all SPA packets to contain a real IP address within the encrypted data. \
+                                                       This makes it impossible to use the -s command line argument on the fwknop client command line, so either -R \
+                                                       has to be used to automatically resolve the external address (if the client behind a NAT) or the client must \
+                                                       know the external IP and set it via the -a argument.'));
+
+               s = m.section(form.TypedSection, 'config', _('fwknopd.conf config options'));
+               s.anonymous=true;
+               s.option(form.Value, 'MAX_SPA_PACKET_AGE', 'MAX_SPA_PACKET_AGE', _('Maximum age in seconds that an SPA packet will be accepted. Defaults to 120 seconds.'));
+               s.option(form.Value, 'PCAP_INTF', 'PCAP_INTF', _('Specify the ethernet interface on which fwknopd will sniff packets.'));
+               s.option(YNValue, 'ENABLE_IPT_FORWARDING', 'ENABLE_IPT_FORWARDING', _('Allow SPA clients to request access to services through an iptables firewall instead of just to it.'));
+               s.option(YNValue, 'ENABLE_NAT_DNS', 'ENABLE_NAT_DNS', _('Allow SPA clients to request forwarding destination by DNS name.'));
+
+               return m.render();
+       }
+});
diff --git a/applications/luci-app-fwknopd/luasrc/model/cbi/fwknopd.lua b/applications/luci-app-fwknopd/luasrc/model/cbi/fwknopd.lua
deleted file mode 100644 (file)
index 9ae754c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
--- Copyright 2015 Jonathan Bennett <jbennett@incomsystems.biz>
--- Licensed to the public under the GNU General Public License v2.
-tmp = 0
-m = Map("fwknopd", translate("Firewall Knock Operator"))
-
-s = m:section(TypedSection, "global", translate("Enable Uci/Luci control")) -- Set uci control on or off
-s.anonymous=true
-s:option(Flag, "uci_enabled", translate("Enable config overwrite"), translate("When unchecked, the config files in /etc/fwknopd will be used as is, ignoring any settings here."))
-
-s = m:section(TypedSection, "access", translate("access.conf stanzas")) -- set the access.conf settings
-s.anonymous=true
-s.addremove=true
-qr = s:option(DummyValue, "note0", "dummy")
-qr.tmp = tmp
-qr.template = "fwknopd-qr"
-qr:depends("uci_enabled", "1")
-s:option(Value, "SOURCE", "SOURCE", translate("Use ANY for any source IP"))
-k1 = s:option(Value, "KEY", "KEY", translate("Define the symmetric key used for decrypting an incoming SPA packet that is encrypted by the fwknop client with Rijndael."))
-k1:depends("keytype", translate("Normal Key"))
-k2 = s:option(Value, "KEY_BASE64", "KEY_BASE64", translate("Define the symmetric key used for decrypting an incoming SPA \
-                                               packet that is encrypted by the fwknop client with Rijndael."))
-k2:depends("keytype", translate("Base64 key"))
-l1 = s:option(ListValue, "keytype", "Key type")
-l1:value("Normal Key", "Normal Key")
-l1:value("Base64 key", "Base64 key")
-k3 = s:option(Value, "HMAC_KEY", "HMAC_KEY", "The hmac key")
-k3:depends("hkeytype", "Normal Key")
-k4 = s:option(Value, "HMAC_KEY_BASE64", "HMAC_KEY_BASE64", translate("The base64 hmac key"))
-k4:depends("hkeytype", "Base64 key")
-l2 = s:option(ListValue, "hkeytype", "HMAC Key type")
-l2:value("Normal Key", "Normal Key")
-l2:value("Base64 key", "Base64 key")
-s:option(Value, "OPEN_PORTS", "OPEN_PORTS", translate("Define a set of ports and protocols (tcp or udp) that will be opened if a valid knock sequence is seen. \
-                                       If this entry is not set, fwknopd will attempt to honor any proto/port request specified in the SPA data \
-                                       (unless of it matches any “RESTRICT_PORTS” entries). Multiple entries are comma-separated."))
-s:option(Value, "FW_ACCESS_TIMEOUT", "FW_ACCESS_TIMEOUT", translate("Define the length of time access will be granted by fwknopd through the firewall after a \
-                                       valid knock sequence from a source IP address. If “FW_ACCESS_TIMEOUT” is not set then the default \
-                                       timeout of 30 seconds will automatically be set."))
-s:option(Value, "REQUIRE_SOURCE_ADDRESS", "REQUIRE_SOURCE_ADDRESS", translate("Force all SPA packets to contain a real IP address within the encrypted data. \
-                                       This makes it impossible to use the -s command line argument on the fwknop client command line, so either -R \
-                                       has to be used to automatically resolve the external address (if the client behind a NAT) or the client must \
-                                       know the external IP and set it via the -a argument."))
-
-s = m:section(TypedSection, "config", translate("fwknopd.conf config options")) 
-s.anonymous=true
-s:option(Value, "MAX_SPA_PACKET_AGE", "MAX_SPA_PACKET_AGE", translate("Maximum age in seconds that an SPA packet will be accepted. Defaults to 120 seconds."))
-s:option(Value, "PCAP_INTF", "PCAP_INTF", translate("Specify the ethernet interface on which fwknopd will sniff packets."))
-s:option(Value, "ENABLE_IPT_FORWARDING", "ENABLE_IPT_FORWARDING", translate("Allow SPA clients to request access to services through an iptables firewall instead of just to it."))
-s:option(Value, "ENABLE_NAT_DNS", "ENABLE_NAT_DNS", translate("Allow SPA clients to request forwarding destination by DNS name."))
-
-return m
-
diff --git a/applications/luci-app-fwknopd/luasrc/view/fwknopd-qr.htm b/applications/luci-app-fwknopd/luasrc/view/fwknopd-qr.htm
deleted file mode 100644 (file)
index 5773f52..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<% print(luci.sys.exec("sh /usr/sbin/gen-qr.sh " .. self.tmp)) %>
-<% self.tmp = self.tmp + 1 %>
index 7cecf27461e5b16e4a22166474bab0996f026532..00d721e067c9422e507332e1d32e0f02042fc35c 100644 (file)
@@ -3,16 +3,24 @@
 #-- Licensed to the public under the GNU General Public License v2.
 . /lib/functions/network.sh
 
-[ "$(uci -q get fwknopd.@access[0].KEY)" != "CHANGEME" ] && exit 0
+# Clean-up - keytype/hkeytype is unnecessary now
+if uci -q show fwknopd | grep \\.h\\?keytype > /dev/null; then
+       for keytype in $(uci -q show fwknopd | grep \\.h\\?keytype= | cut -d= -f1); do
+               uci delete $keytype
+       done
+       uci commit fwknopd
+fi
 
-uci delete fwknopd.@access[0].KEY
-uci delete fwknopd.@access[0].HMAC_KEY
-uci set fwknopd.@access[0].keytype='Base64 key'
-uci set fwknopd.@access[0].hkeytype='Base64 key'
-uci set fwknopd.@access[0].KEY_BASE64=`fwknopd --key-gen | awk '/^KEY/ {print $2;}'`
-uci set fwknopd.@access[0].HMAC_KEY_BASE64=`fwknopd --key-gen | awk '/^HMAC/ {print $2;}'`
-uci set fwknopd.@config[0].ENABLE_IPT_FORWARDING='y'
-uci set fwknopd.@config[0].ENABLE_NAT_DNS='y'
+# Generate valid keys
+if [ "$(uci -q get fwknopd.@access[0].KEY)" = "CHANGEME" ]; then
+       uci delete fwknopd.@access[0].KEY
+       uci delete fwknopd.@access[0].HMAC_KEY
+       uci set fwknopd.@access[0].KEY_BASE64=`fwknopd --key-gen | awk '/^KEY/ {print $2;}'`
+       uci set fwknopd.@access[0].HMAC_KEY_BASE64=`fwknopd --key-gen | awk '/^HMAC/ {print $2;}'`
+       uci set fwknopd.@config[0].ENABLE_IPT_FORWARDING='y'
+       uci set fwknopd.@config[0].ENABLE_NAT_DNS='y'
+
+       uci commit fwknopd
+fi
 
-uci commit fwknopd
 exit 0
diff --git a/applications/luci-app-fwknopd/root/usr/sbin/gen-qr.sh b/applications/luci-app-fwknopd/root/usr/sbin/gen-qr.sh
deleted file mode 100644 (file)
index 48850bd..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-entry_num=0
-if [ "$1" != "" ]; then
-entry_num=$1
-fi
-
-key_base64=$(uci -q get fwknopd.@access[$entry_num].KEY_BASE64)
-key=$(uci -q get fwknopd.@access[$entry_num].KEY)
-hmac_key_base64=$(uci -q get fwknopd.@access[$entry_num].HMAC_KEY_BASE64)
-hmac_key=$(uci -q get fwknopd.@access[$entry_num].HMAC_KEY)
-
-if [ "$key_base64" != "" ]; then
-qr="KEY_BASE64:$key_base64"
-fi
-if [ "$key" != "" ]; then
-qr="$qr KEY:$key"
-
-fi
-if [ "$hmac_key_base64" != "" ]; then
-qr="$qr HMAC_KEY_BASE64:$hmac_key_base64"
-fi
-if [ "$hmac_key" != "" ]; then
-qr="$qr HMAC_KEY:$hmac_key"
-fi
-
-qrencode -t svg -I -o - "$qr"
index 85486b997e96112ccb9ad7e8044b8f6ed9af7a1e..e3ada68d7882e7b3e6c34a0a6fae8bde3197c792 100644 (file)
@@ -2,12 +2,15 @@
        "admin/services/fwknopd": {
                "title": "Firewall Knock Daemon",
                "action": {
-                       "type": "cbi",
-                       "path": "fwknopd",
-                       "post": { "cbi.submit": true }
+                       "type": "view",
+                       "path": "fwknopd"
                },
                "depends": {
                        "acl": [ "luci-app-fwknopd" ],
+                       "fs": {
+                               "/usr/bin/qrencode": "executable",
+                               "/usr/sbin/fwknopd": "executable"
+                       },
                        "uci": { "fwknopd": true }
                }
        }
index 3877f87526181a79b8e0b845ef53c8b00e096dba..15d7975bde06e108aee5dc854bda705e0847e046 100644 (file)
@@ -2,7 +2,11 @@
        "luci-app-fwknopd": {
                "description": "Grant UCI access for luci-app-fwknopd",
                "read": {
-                       "uci": [ "fwknopd" ]
+                       "uci": [ "fwknopd" ],
+                       "file": {
+                               "/usr/bin/qrencode": [ "exec" ],
+                               "/usr/sbin/fwknopd --key-gen": [ "exec" ]
+                       }
                },
                "write": {
                        "uci": [ "fwknopd" ]