luci-app-fwknopd: Show QR codes for custom configuration.

* Addsed /etc/fwknop/access.conf parsing to show QR codes.

Signed-off-by: Oldřich Jedlička <oldium.pro@gmail.com>
This commit is contained in:
Oldřich Jedlička 2020-10-18 21:23:16 +02:00
parent 6059f6a1a6
commit 7a42a6eaec
2 changed files with 227 additions and 1 deletions

View file

@ -21,6 +21,19 @@ function lines(content) {
return content.split(/\r?\n/);
}
function parseLine(rawLine) {
if (rawLine[0] != '#' && rawLine[0] != ';') {
var line = rawLine.split(/ ([^;]*)/, 2);
if (line.length == 2) {
var key = line[0].trim();
var value = line[1].trim();
if (key && value)
return [key, value];
}
}
return null;
}
function parseKeys(content) {
var l = lines(content);
var keys = {};
@ -35,6 +48,7 @@ function parseKeys(content) {
var KeyTypeValue = form.ListValue.extend({
__init__: function() {
this.super('__init__', arguments);
this.hidden = false;
},
cfgvalue: function(section_id) {
@ -50,6 +64,18 @@ var KeyTypeValue = form.ListValue.extend({
return this.keylist[0];
},
render: function(section_id, option_index, cfgvalue) {
return this.super('render', arguments)
.then(L.bind(function(el) {
// Use direct style to hide, because class .hidden
// is used by this.isActive(). We want full functionality,
// but hidden field
if (this.hidden)
el.style.display = 'none';
return el;
}, this));
},
remove: function() {
// Ignore
},
@ -250,12 +276,203 @@ var GenerateButton = form.Button.extend({
else
return Promise.resolve(null);
},
});
var ParseButton = form.Button.extend({
__init__: function() {
this.super('__init__', arguments);
this.onclick = L.bind(this.parseAccessConf, this);
},
parseAccessConf: function() {
this.stanzas = [];
var ctx = {
processLine: L.bind(this.processAccessLine, this),
remainingLines: [],
stanzas: {
last: {},
all: []
}
};
return fs.read('/etc/fwknop/access.conf')
.then(L.bind(this.parseFile, this, ctx))
.then(L.bind(function() {
if (ctx.stanzas.all.length > 0)
return this.renderStanzas(ctx.stanzas.all)
.then(function(topEl) {
var dlg = ui.showModal(_('Firewall Knock Operator Daemon'), [
topEl,
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': ui.hideModal
}, _('Close'))
], 'cbi-modal');
dlg.querySelector('button').focus();
dlg.parentNode.scrollTop = 0;
});
else {
var dlg = ui.showModal(_('Firewall Knock Operator Daemon'), [
E('p', _("No stanza found.")),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': ui.hideModal
}, _('Close'))
]);
dlg.querySelector('button').focus();
}
}, this))
.catch(function(err) {
L.error(err);
});
},
parseFile: function(ctx, content) {
ctx.remainingLines.unshift.apply(ctx.remainingLines, lines(content));
return this.parseLines(ctx);
},
parseFolder: function(ctx, folder, entries) {
// Parse and process files in order
var parseJobs = [];
var parsedLines = [];
entries.sort(function(el1, el2) {
return (el1.name > el2.name) ? 1
: (el1.name < el2.name) ? -1
: 0;
});
entries.forEach(L.bind(function(entry) {
var ctxLines = [];
parsedLines.unshift(ctxLines);
parseJobs.push(fs.read(folder + '/' + entry.name)
.then(function(content) {
ctxLines.push.apply(ctxLines, lines(content));
}));
}, this));
return Promise.all(parseJobs)
.then(L.bind(function(ctx) {
parsedLines.forEach(function(lines) {
ctx.remainingLines.unshift.apply(ctx.remainingLines, lines);
});
}, this, ctx))
.then(L.bind(this.parseLines, this, ctx));
},
parseLines: function(ctx) {
while (ctx.remainingLines.length > 0) {
var line = parseLine(ctx.remainingLines.shift());
if (line) {
var result = ctx.processLine.call(this, ctx, line[0], line[1]);
if (result)
return result;
}
}
},
processAccessLine: function(ctx, key, value) {
if (key.endsWith(':')) {
key = key.slice(0, -1);
}
if (key == "%include") {
return fs.read(value)
.then(L.bind(this.parseFile, this, ctx));
} else if (key == "%include_folder") {
return fs.list(value)
.then(L.bind(this.parseFolder, this, ctx, value));
} else if (key == "%include_keys") {
var keysCtx = {
processLine: L.bind(this.processKeysLine, this),
remainingLines: [],
stanzas: ctx.stanzas
};
return fs.read(value)
.then(L.bind(this.parseFile, this, keysCtx))
.then(L.bind(this.parseLines, this, ctx));
} else {
if (key == 'SOURCE') {
ctx.stanzas.last = {};
ctx.stanzas.all.push(ctx.stanzas.last);
}
ctx.stanzas.last[key] = value;
}
},
processKeysLine: function(ctx, key, value) {
// Simplification - accept only KEY arguments
if (ctx.stanzas.last && key.match(/KEY/))
ctx.stanzas.last[key] = value;
},
renderStanzas: function(stanzas) {
var svgJobs = [];
var config = {};
config.access = stanzas;
var m, s, o;
var accessSection;
var sourceValue;
m = new form.JSONMap(config, null, _('Custom configuration read from /etc/fwknop/access.conf.'));
m.readonly = true;
// set the access.conf settings
accessSection = s = m.section(form.TypedSection, 'access', _('access.conf stanzas'));
s.anonymous = true;
var qrCode = s.option(QrCodeValue, 'qr', _('QR code'), ('QR code to configure fwknopd Android application.'));
sourceValue = s.option(form.Value, 'SOURCE', 'SOURCE');
s.option(form.Value, 'DESTINATION', 'DESTINATION');
o = s.option(form.Value, 'KEY', 'KEY');
o.depends('keytype', 'KEY');
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');
o.depends('keytype', 'KEY_BASE64');
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');
o.value('KEY', _('Normal key'));
o.value('KEY_BASE64', _('Base64 key'));
o.hidden = true;
o = s.option(form.Value, 'HMAC_KEY', 'HMAC_KEY');
o.depends('hkeytype', 'HMAC_KEY');
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');
o.depends('hkeytype', 'HMAC_KEY_BASE64');
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');
o.value('HMAC_KEY', _('Normal key'));
o.value('HMAC_KEY_BASE64', _('Base64 key'));
o.hidden = true;
return m.load()
.then(L.bind(m.render, m));
}
});
return view.extend({
render: function() {
load: function() {
return Promise.all([
L.resolveDefault(fs.stat('/etc/fwknop/access.conf'))
]);
},
render: function(results) {
var has_access_conf = results[0];
var m, s, o;
m = new form.Map('fwknopd', _('Firewall Knock Operator Daemon'));
@ -264,6 +481,14 @@ return view.extend({
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.'));
if ( has_access_conf ) {
o = s.option(ParseButton, 'parse', _('Custom configuration'), _('Parses the /etc/fwknop/access.conf file (and \
included files/folders/keys) and generates QR codes for all found \
stanzas. Handles only files in /etc/fwknop folder due to access rights \
restrictions.'));
o.inputtitle = _("Show access.conf QR codes");
}
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 \

View file

@ -4,6 +4,7 @@
"read": {
"uci": [ "fwknopd" ],
"file": {
"/etc/fwknop/*": [ "read" ],
"/usr/bin/qrencode": [ "exec" ],
"/usr/sbin/fwknopd --key-gen": [ "exec" ]
}