diff options
Diffstat (limited to 'protocols/luci-proto-openconnect')
4 files changed, 254 insertions, 90 deletions
diff --git a/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js new file mode 100644 index 0000000000..14fc8f6d35 --- /dev/null +++ b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js @@ -0,0 +1,159 @@ +'use strict'; +'require rpc'; +'require form'; +'require network'; + +var callGetCertificateFiles = rpc.declare({ + object: 'luci.openconnect', + method: 'getCertificates', + params: [ 'interface' ], + expect: { '': {} } +}); + +var callSetCertificateFiles = rpc.declare({ + object: 'luci.openconnect', + method: 'setCertificates', + params: [ 'interface', 'user_certificate', 'user_privatekey', 'ca_certificate' ], + expect: { '': {} } +}); + +network.registerPatternVirtual(/^vpn-.+$/); + +function sanitizeCert(s) { + if (typeof(s) != 'string') + return null; + + s = s.trim(); + + if (s == '') + return null; + + s = s.replace(/\r\n?/g, '\n'); + + if (!s.match(/\n$/)) + s += '\n'; + + return s; +} + +function validateCert(priv, section_id, value) { + var beg = priv ? /^-----BEGIN RSA PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/, + end = priv ? /^-----END RSA PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/, + lines = value.trim().split(/[\r\n]/), + start = false, + i; + + for (i = 0; i < lines.length; i++) { + if (lines[i].match(beg)) + start = true; + else if (start && !lines[i].match(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)) + break; + } + + if (!start || i < lines.length - 1 || !lines[i].match(end)) + return _('This does not look like a valid PEM file'); + + return true; +} + +return network.registerProtocol('openconnect', { + getI18n: function() { + return _('OpenConnect (CISCO AnyConnect)'); + }, + + getIfname: function() { + return this._ubus('l3_device') || 'vpn-%s'.format(this.sid); + }, + + getOpkgPackage: function() { + return 'openconnect'; + }, + + isFloating: function() { + return true; + }, + + isVirtual: function() { + return true; + }, + + getDevices: function() { + return null; + }, + + containsDevice: function(ifname) { + return (network.getIfnameOf(ifname) == this.getIfname()); + }, + + renderFormOptions: function(s) { + var dev = this.getDevice().getName(), + certLoadPromise = null, + o; + + o = s.taboption('general', form.Value, 'server', _('VPN Server')); + o.datatype = 'host(0)'; + + o = s.taboption('general', form.Value, 'port', _('VPN Server port')); + o.placeholder = '443'; + o.datatype = 'port'; + + s.taboption('general', form.Value, 'serverhash', _("VPN Server's certificate SHA1 hash")); + s.taboption('general', form.Value, 'authgroup', _('Auth Group')); + s.taboption("general", form.Value, "username", _("Username")); + + o = s.taboption('general', form.Value, 'password', _('Password')); + o.password = true; + + o = s.taboption('general', form.Value, 'password2', _('Password2')); + o.password = true; + + o = s.taboption('general', form.TextValue, 'usercert', _('User certificate (PEM encoded)')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, false); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.user_certificate }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, sanitizeCert(value), null, null); + }; + + o = s.taboption('general', form.TextValue, 'userkey', _('User key (PEM encoded)')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, true); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.user_privatekey }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, null, sanitizeCert(value), null); + }; + + o = s.taboption('general', form.TextValue, 'ca', _('CA certificate; if empty it will be saved after the first connection.')); + o.rows = 10; + o.monospace = true; + o.validate = L.bind(validateCert, o, false); + o.load = function(section_id) { + certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id); + return certLoadPromise.then(function(certs) { return certs.ca_certificate }); + }; + o.write = function(section_id, value) { + return callSetCertificateFiles(section_id, null, null, sanitizeCert(value)); + }; + + o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured')); + o.default = o.enabled; + + o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric')); + o.placeholder = '0'; + o.datatype = 'uinteger'; + o.depends('defaultroute', '1'); + + o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU')); + o.optional = true; + o.placeholder = 1406; + o.datatype = 'range(68, 9200)'; + } +}); diff --git a/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua b/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua deleted file mode 100644 index 5adfccae48..0000000000 --- a/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua +++ /dev/null @@ -1,90 +0,0 @@ --- Copyright 2014 Nikos Mavrogiannopoulos <nmav@gnutls.org> --- Licensed to the public under the Apache License 2.0. - -local map, section, net = ... - -local server, username, password, cert, ca -local oc_cert_file, oc_key_file, oc_ca_file - -local ifc = net:get_interface():name() - -oc_cert_file = "/etc/openconnect/user-cert-" .. ifc .. ".pem" -oc_key_file = "/etc/openconnect/user-key-" .. ifc .. ".pem" -oc_ca_file = "/etc/openconnect/ca-" .. ifc .. ".pem" - -server = section:taboption("general", Value, "server", translate("VPN Server")) -server.datatype = "host(0)" - -port = section:taboption("general", Value, "port", translate("VPN Server port")) -port.placeholder = "443" -port.datatype = "port" - - -defaultroute = section:taboption("advanced", Flag, "defaultroute", - translate("Use default gateway"), - translate("If unchecked, no default route is configured")) - -defaultroute.default = defaultroute.enabled - - -metric = section:taboption("advanced", Value, "metric", - translate("Use gateway metric")) - -metric.placeholder = "0" -metric.datatype = "uinteger" -metric:depends("defaultroute", defaultroute.enabled) - -section:taboption("general", Value, "serverhash", translate("VPN Server's certificate SHA1 hash")) - -section:taboption("general", Value, "authgroup", translate("Auth Group")) - -username = section:taboption("general", Value, "username", translate("Username")) -password = section:taboption("general", Value, "password", translate("Password")) -password.password = true -password2 = section:taboption("general", Value, "password2", translate("Password2")) -password2.password = true - - -cert = section:taboption("advanced", Value, "usercert", translate("User certificate (PEM encoded)")) -cert.template = "cbi/tvalue" -cert.rows = 10 - -function cert.cfgvalue(self, section) - return nixio.fs.readfile(oc_cert_file) -end - -function cert.write(self, section, value) - value = value:gsub("\r\n?", "\n") - nixio.fs.writefile(oc_cert_file, value) -end - -cert = section:taboption("advanced", Value, "userkey", translate("User key (PEM encoded)")) -cert.template = "cbi/tvalue" -cert.rows = 10 - -function cert.cfgvalue(self, section) - return nixio.fs.readfile(oc_key_file) -end - -function cert.write(self, section, value) - value = value:gsub("\r\n?", "\n") - nixio.fs.writefile(oc_key_file, value) -end - - -ca = section:taboption("advanced", Value, "ca", translate("CA certificate; if empty it will be saved after the first connection.")) -ca.template = "cbi/tvalue" -ca.rows = 10 - -function ca.cfgvalue(self, section) - return nixio.fs.readfile(oc_ca_file) -end - -function ca.write(self, section, value) - value = value:gsub("\r\n?", "\n") - nixio.fs.writefile(oc_ca_file, value) -end - -mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) -mtu.placeholder = "1406" -mtu.datatype = "max(9200)" diff --git a/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect b/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect new file mode 100755 index 0000000000..9378cc518b --- /dev/null +++ b/protocols/luci-proto-openconnect/root/usr/libexec/rpcd/luci.openconnect @@ -0,0 +1,78 @@ +#!/usr/bin/env lua + +local json = require "luci.jsonc" +local fs = require "nixio.fs" + +local function readfile(path) + local s = fs.readfile(path) + return s and (s:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function writefile(path, data) + local n = fs.writefile(path, data) + return (n == #data) +end + +local function parseInput() + local parse = json.new() + local done, err + + while true do + local chunk = io.read(4096) + if not chunk then + break + elseif not done and not err then + done, err = parse:parse(chunk) + end + end + + if not done then + print(json.stringify({ error = err or "Incomplete input" })) + os.exit(1) + end + + return parse:get() +end + +if arg[1] == "list" then + print(json.stringify({ + getCertificates = { + interface = "interface" + }, + setCertificates = { + interface = "interface", + user_certificate = "PEM file data", + user_privatekey = "PEM file data", + ca_certificate = "PEM file data" + } + })) +elseif arg[1] == "call" then + local args = parseInput() + + if not args.interface or + type(args.interface) ~= "string" or + not args.interface:match("^[a-zA-Z0-9_]+$") + then + print(json.stringify({ error = "Invalid interface name" })) + os.exit(1) + end + + if arg[2] == "getCertificates" then + print(json.stringify({ + user_certificate = readfile(string.format("/etc/openconnect/user-cert-%s.pem", args.interface)), + user_privatekey = readfile(string.format("/etc/openconnect/user-key-%s.pem", args.interface)), + ca_certificate = readfile(string.format("/etc/openconnect/ca-%s.pem", args.interface)) + })) + elseif arg[2] == "setCertificates" then + if args.user_certificate then + writefile(string.format("/etc/openconnect/user-cert-%s.pem", args.interface), args.user_certificate) + end + if args.user_privatekey then + writefile(string.format("/etc/openconnect/user-key-%s.pem", args.interface), args.user_privatekey) + end + if args.ca_certificate then + writefile(string.format("/etc/openconnect/ca-%s.pem", args.interface), args.ca_certificate) + end + print(json.stringify({ result = true })) + end +end diff --git a/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json b/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json new file mode 100644 index 0000000000..66bc5ac24f --- /dev/null +++ b/protocols/luci-proto-openconnect/root/usr/share/rpcd/acl.d/luci-openconnect.json @@ -0,0 +1,17 @@ +{ + "luci-proto-openconnect": { + "description": "Grant access to LuCI OpenConnect procedures", + "read": { + "ubus": { + "luci.openconnect": [ "getCertificates" ] + }, + "uci": [ "network" ] + }, + "write": { + "ubus": { + "luci.openconnect": [ "setCertificates" ] + }, + "uci": [ "network" ] + } + } +} |