diff --git a/renderer/renderer.uc b/renderer/renderer.uc index 0bc3786..75da01d 100644 --- a/renderer/renderer.uc +++ b/renderer/renderer.uc @@ -990,6 +990,8 @@ let routing_table = { /** @lends uCentral.captive.prototype */ let captive = { + interfaces: {}, + next: 0, /** @@ -998,9 +1000,19 @@ let captive = { * @param {string} id The ID to lookup or reserve * @returns {number} The table number allocated for the given ID */ - get: function() { - return this.next++; - } + get: function(name) { + let iface = this.next++; + push(this.interfaces[name].iface, iface); + return iface; + }, + + /** + * Add an interface + */ + interface: function(name, config) { + this.interfaces[name] = config; + this.interfaces[name].iface = []; + }, }; /** diff --git a/renderer/templates/interface/captive.uc b/renderer/templates/interface/captive.uc index f90dd4c..568ffea 100644 --- a/renderer/templates/interface/captive.uc +++ b/renderer/templates/interface/captive.uc @@ -1,25 +1,119 @@ +{% +if (config.radius_gw_proxy) + services.set_enabled("radius-gw-proxy", true); -# Captive Portal Configuration -add opennds opennds -set opennds.@opennds[-1].enabled='1' -set opennds.@opennds[-1].fwhook_enabled='1' -set opennds.@opennds[-1].debuglevel='1' -add_list opennds.@opennds[-1].users_to_router='allow tcp port 53' -add_list opennds.@opennds[-1].users_to_router='allow udp port 53' -add_list opennds.@opennds[-1].users_to_router='allow udp port 67' -add_list opennds.@opennds[-1].users_to_router='allow tcp port 22' -add_list opennds.@opennds[-1].users_to_router='allow tcp port 80' -add_list opennds.@opennds[-1].users_to_router='allow tcp port 443' -set opennds.@opennds[-1].login_option_enabled='1' -set opennds.@opennds[-1].gatewayinterface='br-{{ name }}' -set opennds.@opennds[-1].gatewayname={{ s(interface.captive.gateway_name) }} -set opennds.@opennds[-1].maxclients='{{ interface.captive.max_clients }}' -set opennds.@opennds[-1].gatewayfqdn={{ s(interface.captive.gateway_fqdn) }} -set opennds.@opennds[-1].uploadrate='{{ interface.captive.upload_rate }}' -set opennds.@opennds[-1].downloadrate='{{ interface.captive.download_rate }}' -set opennds.@opennds[-1].uploadquota='{{ interface.captive.upload_quota }}' -set opennds.@opennds[-1].downloadquota='{{ interface.captive.download_quota }}' -add_list opennds.@opennds[-1].authenticated_users='block to 192.168.0.0/16' -add_list opennds.@opennds[-1].authenticated_users='block to 172.16.0.0/24' -add_list opennds.@opennds[-1].authenticated_users='block to 10.0.0.0/24' -add_list opennds.@opennds[-1].authenticated_users='allow all' +function radius_proxy_tlv(server, port, name) { + let tlv = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name); + return tlv; +} + +captive.interface(section, config); +let name = split(section, '_')[0]; + +if (!captive.web_root) + system('cp -r /www-uspot /tmp/ucentral/'); +else { + let fs = require('fs'); + fs.mkdir('/tmp/ucentral/www-uspot'); + let web_root = fs.open('/tmp/ucentral/web-root.tar', 'w'); + web_root.write(b64dec(captive.web_root)); + web_root.close(); + system('tar x -C /tmp/ucentral/www-uspot -f /tmp/ucentral/web-root.tar'); +} + +if (captive.radius_gw_proxy) + services.set_enabled("radius-gw-proxy", true); +%} + +# Captive Portal service configuration + +set uspot.{{ section }}=uspot +set uspot.{{ section }}.auth_mode={{ s(config.auth_mode) }} +set uspot.{{ section }}.web_root={{ b(config.web_root) }} +set uspot.{{ section }}.idle_timeout={{ config.idle_timeout }} +set uspot.{{ section }}.session_timeout={{ config.session_timeout }} + +{% if (config.auth_mode in [ 'radius', 'uam']): %} +{% if (config.radius_gw_proxy): %} +set uspot.{{ section }}.auth_server='127.0.0.1' +set uspot.{{ section }}.auth_port='1812' +set uspot.{{ section }}.auth_proxy={{ s(radius_proxy_tlv(config.auth_server, config.auth_port, 'captive')) }} +{% if (config.acct_server): %} +set uspot.{{ section }}.acct_server='127.0.0.1' +set uspot.{{ section }}.acct_port='1813' +set uspot.{{ section }}.acct_proxy={{ s(radius_proxy_tlv(config.acct_server, config.acct_port, 'captive')) }} +{% endif %} +{% else %} +set uspot.{{ section }}.auth_server={{ s(config.auth_server) }} +set uspot.{{ section }}.auth_port={{ s(config.auth_port) }} +set uspot.{{ section }}.acct_server={{ s(config.acct_server) }} +set uspot.{{ section }}.acct_port={{ s(config.acct_port) }} +{% endif %} +set uspot.{{ section }}.auth_secret={{ s(config.auth_secret) }} +set uspot.{{ section }}.acct_secret={{ s(config.acct_secret) }} +set uspot.{{ section }}.acct_interval={{ config.acct_interval }} +{% endif %} + +{% if (captive.auth_mode == 'credentials'): %} +{% for (let cred in captive.credentials): %} +add uspot credentials +set uspot.@credentials[-1].username={{ s(cred.username) }} +set uspot.@credentials[-1].password={{ s(cred.password) }} +set uspot.@credentials[-1].interface={{ s(section) }} +{% endfor %} +{% endif %} + +{% if (config.auth_mode == 'uam'): %} +{% +let math = require('math'); +let challenge = ""; +for (let i = 0; i < 16; i++) + challenge += sprintf('%02x', math.rand() % 255); +%} +set uspot.{{ section }}.challenge={{ s(challenge) }} + +set uspot.{{ section }}.uam_port={{ s(config.uam_port) }} +set uspot.{{ section }}.uam_secret={{ s(config.uam_secret) }} +set uspot.{{ section }}.uam_server={{ s(config.uam_server) }} +set uspot.{{ section }}.nasid={{ s(config.nasid) }} +set uspot.{{ section }}.nasmac={{ s(config.nasmac || serial) }} +set uspot.{{ section }}.ssid={{ s(config.ssid) }} +set uspot.{{ section }}.mac_format={{ s(config.mac_format) }} +set uspot.{{ section }}.final_redirect_url={{ s(config.final_redirect_url) }} +set uspot.{{ section }}.mac_auth={{ b(config.mac_auth) }} + +set uhttpd.uam{{ config.uam_port }}=uhttpd +set uhttpd.@uhttpd[-1].redirect_https='0' +set uhttpd.@uhttpd[-1].rfc1918_filter='1' +set uhttpd.@uhttpd[-1].max_requests='5' +set uhttpd.@uhttpd[-1].max_connections='100' +set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt' +set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key' +set uhttpd.@uhttpd[-1].script_timeout='60' +set uhttpd.@uhttpd[-1].network_timeout='30' +set uhttpd.@uhttpd[-1].http_keepalive='20' +set uhttpd.@uhttpd[-1].tcp_keepalive='1' +add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ config.uam_port }}' +add_list uhttpd.@uhttpd[-1].listen_http='[::]:{{ config.uam_port }}' +set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot +add_list uhttpd.@uhttpd[-1].ucode_prefix='/logon=/usr/share/uspot/handler-uam.uc' +add_list uhttpd.@uhttpd[-1].ucode_prefix='/logoff=/usr/share/uspot/handler-uam.uc' +add_list uhttpd.@uhttpd[-1].ucode_prefix='/logout=/usr/share/uspot/handler-uam.uc' + +set firewall.{{ name + config.uam_port}}_1=rule +set firewall.@rule[-1].name='Allow-UAM-{{ name }}' +set firewall.@rule[-1].src='{{ name }}' +set firewall.@rule[-1].dest_port='{{ config.uam_port }}' +set firewall.@rule[-1].proto='tcp' +set firewall.@rule[-1].target='ACCEPT' +set firewall.@rule[-1].mark='1/127' + +set firewall.{{ name + config.uam_port}}_2=rule +set firewall.@rule[-1].name='Allow-UAM-{{ name }}' +set firewall.@rule[-1].src='{{ name }}' +set firewall.@rule[-1].dest_port='{{ config.uam_port }}' +set firewall.@rule[-1].proto='tcp' +set firewall.@rule[-1].target='ACCEPT' +set firewall.@rule[-1].mark='2/127' + +{% endif %} diff --git a/renderer/templates/interface/ssid.uc b/renderer/templates/interface/ssid.uc index 880da02..e07ddf1 100644 --- a/renderer/templates/interface/ssid.uc +++ b/renderer/templates/interface/ssid.uc @@ -249,28 +249,41 @@ return ''; } - function calculate_ifname() { + function calculate_ifname(name) { if ('captive' in ssid.services) - return 'wlanc' + captive.get(); + return 'wlanc' + captive.get(name); return ''; } let radius_gw_proxy = ssid.services && (index(ssid.services, "radius-gw-proxy") >= 0); + + if ('captive' in ssid.services && !ssid.captive) + ssid.captive = state?.services?.captive || {}; + + if (ssid.captive) + include("captive.uc", { + section: name + '_' + count, + config: ssid.captive + }); %} # Wireless configuration {% for (let n, phy in phys): %} -{% let basename = name + '_' + n + '_' + count; %} -{% let section = (owe ? 'o' : '' ) + basename; %} +{% let basename = name + '_' + count; %} +{% let ssidname = basename + '_' + n + '_' + count; %} +{% let section = (owe ? 'o' : '' ) + ssidname; %} {% let id = wiphy.allocate_ssid_section_id(phy) %} {% let crypto = validate_encryption(phy); %} -{% let ifname = calculate_ifname(n) %} +{% let ifname = calculate_ifname(basename) %} {% if (!crypto) continue; %} set wireless.{{ section }}=wifi-iface set wireless.{{ section }}.ucentral_path={{ s(location) }} set wireless.{{ section }}.uci_section={{ s(section) }} set wireless.{{ section }}.device={{ phy.section }} +{% if ('captive' in ssid.services): %} set wireless.{{ section }}.ifname={{ s(ifname) }} +set uspot.devices.{{ ifname }}={{ basename }} +{% endif %} {% if (ssid?.encryption?.proto == 'owe-transition'): %} {% ssid.hidden_ssid = 1 %} {% ssid.name += '-OWE' %} @@ -279,7 +292,7 @@ set wireless.{{ section }}.owe_transition_ifname={{ s('o' + section) }} {% endif %} {% if (owe): %} set wireless.{{ section }}.ifname={{ s(section) }} -set wireless.{{ section }}.owe_transition_ifname={{ s(basename) }} +set wireless.{{ section }}.owe_transition_ifname={{ s(ssidname) }} {% endif %} {% if (bss_mode == 'mesh'): %} set wireless.{{ section }}.mode={{ bss_mode }} diff --git a/renderer/templates/services/captive.uc b/renderer/templates/services/captive.uc index 744411a..b4ff407 100644 --- a/renderer/templates/services/captive.uc +++ b/renderer/templates/services/captive.uc @@ -12,86 +12,9 @@ services.set_enabled("spotfilter", enable); services.set_enabled("uspot", enable); if (!enable) return; - -if (!captive.web_root) - system('cp -r /www-uspot /tmp/ucentral/'); -else { - let fs = require('fs'); - fs.mkdir('/tmp/ucentral/www-uspot'); - let web_root = fs.open('/tmp/ucentral/web-root.tar', 'w'); - web_root.write(b64dec(captive.web_root)); - web_root.close(); - system('tar x -C /tmp/ucentral/www-uspot -f /tmp/ucentral/web-root.tar'); -} - -if (captive.radius_gw_proxy) - services.set_enabled("radius-gw-proxy", true); - -function radius_proxy_tlv(server, port, name) { - let tlv = replace(serial, /^(..)(..)(..)(..)(..)(..)$/, "$1$2$3$4$5$6") + sprintf(":%s:%s:%s", server, port, name); - return tlv; -} - %} -# Captive Portal service configuration - -set uspot.config.auth_mode={{ s(captive.auth_mode) }} -set uspot.config.web_root={{ b(captive.web_root) }} -set uspot.config.idle_timeout={{ captive.idle_timeout }} -set uspot.config.session_timeout={{ captive.session_timeout }} - -{% if (captive.auth_mode in [ 'radius', 'uam']): %} -{% if (captive.radius_gw_proxy): %} -set uspot.radius.auth_server='127.0.0.1' -set uspot.radius.auth_port='1812' -set uspot.radius.auth_proxy={{ s(radius_proxy_tlv(captive.auth_server, captive.auth_port, 'captive')) }} -{% if (captive.acct_server): %} -set uspot.radius.acct_server='127.0.0.1' -set uspot.radius.acct_port='1813' -set uspot.radius.acct_proxy={{ s(radius_proxy_tlv(captive.acct_server, captive.acct_port, 'captive')) }} -{% endif %} -{% else %} -set uspot.radius.auth_server={{ s(captive.auth_server) }} -set uspot.radius.auth_port={{ s(captive.auth_port) }} -set uspot.radius.acct_server={{ s(captive.acct_server) }} -set uspot.radius.acct_port={{ s(captive.acct_port) }} -{% endif %} -set uspot.radius.auth_secret={{ s(captive.auth_secret) }} -set uspot.radius.acct_secret={{ s(captive.acct_secret) }} -set uspot.radius.acct_interval={{ captive.acct_interval }} -{% endif %} - -{% if (captive.auth_mode == 'uam'): %} -set uspot.uam.uam_port={{ s(captive.uam_port) }} -set uspot.uam.uam_secret={{ s(captive.uam_secret) }} -set uspot.uam.uam_server={{ s(captive.uam_server) }} -set uspot.uam.nasid={{ s(captive.nasid) }} -set uspot.uam.nasmac={{ s(captive.nasmac || serial) }} -set uspot.uam.ssid={{ s(captive.ssid) }} -set uspot.uam.mac_format={{ s(captive.mac_format) }} -set uspot.uam.final_redirect_url={{ s(captive.final_redirect_url) }} -set uspot.uam.mac_auth={{ b(captive.mac_auth) }} - -{% -let math = require('math'); -let challenge = ""; -for (let i = 0; i < 16; i++) - challenge += sprintf('%02x', math.rand() % 255); -%} -set uspot.uam.challenge={{ s(challenge) }} - -{% endif %} - -{% if (captive.auth_mode == 'credentials'): %} -{% for (let cred in captive.credentials): %} -add uspot credentials -set uspot.@credentials[-1].username={{ s(cred.username) }} -set uspot.@credentials[-1].password={{ s(cred.password) }} -{% endfor %} -{% endif %} - -{% for (let interface in interfaces): %} +{% for (let interface in uniq(interfaces)): %} {% let name = ethernet.calculate_name(interface) %} add firewall redirect set firewall.@redirect[-1].name='Redirect-captive-{{ name }}' @@ -117,24 +40,6 @@ set firewall.@rule[-1].proto='tcp' set firewall.@rule[-1].target='ACCEPT' set firewall.@rule[-1].mark='2/127' -{% if (captive.auth_mode == 'uam'): %} -add firewall rule -set firewall.@rule[-1].name='Allow-UAM-{{ name }}' -set firewall.@rule[-1].src='{{ name }}' -set firewall.@rule[-1].dest_port='{{ captive.uam_port }}' -set firewall.@rule[-1].proto='tcp' -set firewall.@rule[-1].target='ACCEPT' -set firewall.@rule[-1].mark='1/127' - -add firewall rule -set firewall.@rule[-1].name='Allow-UAM-{{ name }}' -set firewall.@rule[-1].src='{{ name }}' -set firewall.@rule[-1].dest_port='{{ captive.uam_port }}' -set firewall.@rule[-1].proto='tcp' -set firewall.@rule[-1].target='ACCEPT' -set firewall.@rule[-1].mark='2/127' -{% endif %} - {% if (interface.role == 'downstream'): %} add firewall rule set firewall.@rule[-1].name='Allow-pre-captive-{{ name }}' @@ -169,32 +74,11 @@ set uhttpd.@uhttpd[-1].script_timeout='60' set uhttpd.@uhttpd[-1].network_timeout='30' set uhttpd.@uhttpd[-1].http_keepalive='20' set uhttpd.@uhttpd[-1].tcp_keepalive='1' +set uhttpd.@uhttpd[-1].no_dirlists='1' add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:80' add_list uhttpd.@uhttpd[-1].listen_http='[::]:80' set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot add_list uhttpd.@uhttpd[-1].ucode_prefix='/hotspot=/usr/share/uspot/handler.uc' -add_list uhttpd.@uhttpd[-1].ucode_prefix='/=/usr/share/uspot/handler-cpd.uc' add_list uhttpd.@uhttpd[-1].ucode_prefix='/cpd=/usr/share/uspot/handler-cpd.uc' add_list uhttpd.@uhttpd[-1].ucode_prefix='/env=/usr/share/uspot/handler-env.uc' set uhttpd.@uhttpd[-1].error_page='/cpd' - - -{% if (captive.auth_mode == 'uam'): %} -add uhttpd uhttpd -set uhttpd.@uhttpd[-1].redirect_https='0' -set uhttpd.@uhttpd[-1].rfc1918_filter='1' -set uhttpd.@uhttpd[-1].max_requests='5' -set uhttpd.@uhttpd[-1].max_connections='100' -set uhttpd.@uhttpd[-1].cert='/etc/uhttpd.crt' -set uhttpd.@uhttpd[-1].key='/etc/uhttpd.key' -set uhttpd.@uhttpd[-1].script_timeout='60' -set uhttpd.@uhttpd[-1].network_timeout='30' -set uhttpd.@uhttpd[-1].http_keepalive='20' -set uhttpd.@uhttpd[-1].tcp_keepalive='1' -add_list uhttpd.@uhttpd[-1].listen_http='0.0.0.0:{{ captive.uam_port }}' -add_list uhttpd.@uhttpd[-1].listen_http='[::]:{{ captive.uam_port }}' -set uhttpd.@uhttpd[-1].home=/tmp/ucentral/www-uspot -add_list uhttpd.@uhttpd[-1].ucode_prefix='/logon=/usr/share/uspot/handler-uam.uc' -add_list uhttpd.@uhttpd[-1].ucode_prefix='/logoff=/usr/share/uspot/handler-uam.uc' -add_list uhttpd.@uhttpd[-1].ucode_prefix='/logout=/usr/share/uspot/handler-uam.uc' -{% endif %} diff --git a/renderer/templates/spotfilter.uc b/renderer/templates/spotfilter.uc index 8a8f1e2..08a7eed 100644 --- a/renderer/templates/spotfilter.uc +++ b/renderer/templates/spotfilter.uc @@ -3,52 +3,50 @@ let interfaces = services.lookup_interfaces_by_ssids("captive"); let enable = length(interfaces); if (enable != 1) return; -let name; -for (let interface in interfaces) - name = ethernet.calculate_name(interface); +for (let name, data in captive.interfaces) { + let config = { + name, + devices: [], + config: { + default_class: 0, + default_dns_class: 1, + client_autoremove: false, + class: [ + { + index: 0, + device_macaddr: split(name, '_')[0], + fwmark: 1, + fwmark_mask: 127 + }, { + index: 1, + fwmark: 2, + fwmark_mask: 127 + } + ], + whitelist: [ + { + "class": 1, + "hosts": [ ], + "address": [], + } + ] + } + }; -let config = { - name: "hotspot", - devices: [], - config: { - default_class: 0, - default_dns_class: 1, - client_autoremove: false, - class: [ - { - index: 0, - device_macaddr: name, - fwmark: 1, - fwmark_mask: 127 - }, { - index: 1, - fwmark: 2, - fwmark_mask: 127 - } - ], - whitelist: [ - { - "class": 1, - "hosts": [ ], - "address": [], - } - ] - } -}; + for (let iface in data.iface) + push(config.devices, 'wlanc' + iface); -for (let id = 0; id < captive.next; id++) - push(config.devices, 'wlanc' + id); + for (let fqdn in data.walled_garden_fqdn) + push(config.config.whitelist[0].hosts, fqdn); -for (let fqdn in state.services.captive.walled_garden_fqdn) - push(config.config.whitelist[0].hosts, fqdn); + for (let ipaddr in data.walled_garden_ipaddr) + push(config.config.whitelist[0].address, ipaddr); -for (let ipaddr in state.services.captive.walled_garden_ipaddr) - push(config.config.whitelist[0].address, ipaddr); - -let fs = require('fs'); -let file = fs.open('/tmp/spotfilter.json', 'w'); -file.write(config); -file.close(); -services.set_enabled("uhttpd", true) + let fs = require('fs'); + let file = fs.open('/tmp/spotfilter-' + name + '.json', 'w'); + file.write(config); + file.close(); + services.set_enabled("uhttpd", true) +} %} diff --git a/renderer/templates/toplevel.uc b/renderer/templates/toplevel.uc index bdc0fa4..b7b1467 100644 --- a/renderer/templates/toplevel.uc +++ b/renderer/templates/toplevel.uc @@ -117,8 +117,7 @@ } services.set_enabled("usteer2", true); - if (state?.services?.captive) - include('spotfilter.uc'); + include('spotfilter.uc'); if (state.config_raw) include("config_raw.uc", { location: '/config_raw', config_raw: state.config_raw }); diff --git a/schema/interface.ssid.yml b/schema/interface.ssid.yml index 8bb1404..ae69765 100644 --- a/schema/interface.ssid.yml +++ b/schema/interface.ssid.yml @@ -152,6 +152,8 @@ properties: $ref: "https://ucentral.io/schema/v1/interface/ssid/quality-thresholds/" access-control-list: $ref: "https://ucentral.io/schema/v1/interface/ssid/acl/" + captive: + $ref: 'https://ucentral.io/schema/v1/service/captive/' hostapd-bss-raw: description: This array allows passing raw hostapd.conf lines. diff --git a/schemareader.uc b/schemareader.uc index 633aebc..6e06119 100644 --- a/schemareader.uc +++ b/schemareader.uc @@ -4401,6 +4401,775 @@ function instantiateInterfaceSsidAcl(location, value, errors) { return value; } +function instantiateServiceCaptiveClick(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseAuthMode(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (value != "click-to-continue") + push(errors, [ location, "must have value \"click-to-continue\"" ]); + + return value; + } + + if (exists(value, "auth-mode")) { + obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; +} + +function instantiateServiceCaptiveRadius(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseAuthMode(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (value != "radius") + push(errors, [ location, "must have value \"radius\"" ]); + + return value; + } + + if (exists(value, "auth-mode")) { + obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); + } + + function parseAuthServer(location, value, errors) { + if (type(value) == "string") { + if (!matchUcHost(value)) + push(errors, [ location, "must be a valid hostname or IP address" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "auth-server")) { + obj.auth_server = parseAuthServer(location + "/auth-server", value["auth-server"], errors); + } + + function parseAuthPort(location, value, errors) { + if (type(value) in [ "int", "double" ]) { + if (value > 65535) + push(errors, [ location, "must be lower than or equal to 65535" ]); + + if (value < 1024) + push(errors, [ location, "must be bigger than or equal to 1024" ]); + + } + + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "auth-port")) { + obj.auth_port = parseAuthPort(location + "/auth-port", value["auth-port"], errors); + } + else { + obj.auth_port = 1812; + } + + function parseAuthSecret(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "auth-secret")) { + obj.auth_secret = parseAuthSecret(location + "/auth-secret", value["auth-secret"], errors); + } + + function parseAcctServer(location, value, errors) { + if (type(value) == "string") { + if (!matchUcHost(value)) + push(errors, [ location, "must be a valid hostname or IP address" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "acct-server")) { + obj.acct_server = parseAcctServer(location + "/acct-server", value["acct-server"], errors); + } + + function parseAcctPort(location, value, errors) { + if (type(value) in [ "int", "double" ]) { + if (value > 65535) + push(errors, [ location, "must be lower than or equal to 65535" ]); + + if (value < 1024) + push(errors, [ location, "must be bigger than or equal to 1024" ]); + + } + + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "acct-port")) { + obj.acct_port = parseAcctPort(location + "/acct-port", value["acct-port"], errors); + } + else { + obj.acct_port = 1812; + } + + function parseAcctSecret(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "acct-secret")) { + obj.acct_secret = parseAcctSecret(location + "/acct-secret", value["acct-secret"], errors); + } + + function parseAcctInterval(location, value, errors) { + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "acct-interval")) { + obj.acct_interval = parseAcctInterval(location + "/acct-interval", value["acct-interval"], errors); + } + else { + obj.acct_interval = 600; + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; +} + +function instantiateServiceCaptiveCredentials(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseAuthMode(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (value != "credentials") + push(errors, [ location, "must have value \"credentials\"" ]); + + return value; + } + + if (exists(value, "auth-mode")) { + obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); + } + + function parseCredentials(location, value, errors) { + if (type(value) == "array") { + function parseItem(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseUsername(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "username")) { + obj.username = parseUsername(location + "/username", value["username"], errors); + } + + function parsePassword(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "password")) { + obj.password = parsePassword(location + "/password", value["password"], errors); + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; + } + + return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); + } + + if (type(value) != "array") + push(errors, [ location, "must be of type array" ]); + + return value; + } + + if (exists(value, "credentials")) { + obj.credentials = parseCredentials(location + "/credentials", value["credentials"], errors); + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; +} + +function instantiateServiceCaptiveUam(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseAuthMode(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (value != "uam") + push(errors, [ location, "must have value \"uam\"" ]); + + return value; + } + + if (exists(value, "auth-mode")) { + obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); + } + + function parseUamPort(location, value, errors) { + if (type(value) in [ "int", "double" ]) { + if (value > 65535) + push(errors, [ location, "must be lower than or equal to 65535" ]); + + if (value < 1024) + push(errors, [ location, "must be bigger than or equal to 1024" ]); + + } + + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "uam-port")) { + obj.uam_port = parseUamPort(location + "/uam-port", value["uam-port"], errors); + } + else { + obj.uam_port = 3990; + } + + function parseUamSecret(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "uam-secret")) { + obj.uam_secret = parseUamSecret(location + "/uam-secret", value["uam-secret"], errors); + } + + function parseUamServer(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "uam-server")) { + obj.uam_server = parseUamServer(location + "/uam-server", value["uam-server"], errors); + } + + function parseNasid(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "nasid")) { + obj.nasid = parseNasid(location + "/nasid", value["nasid"], errors); + } + + function parseNasmac(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "nasmac")) { + obj.nasmac = parseNasmac(location + "/nasmac", value["nasmac"], errors); + } + + function parseAuthServer(location, value, errors) { + if (type(value) == "string") { + if (!matchUcHost(value)) + push(errors, [ location, "must be a valid hostname or IP address" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "auth-server")) { + obj.auth_server = parseAuthServer(location + "/auth-server", value["auth-server"], errors); + } + + function parseAuthPort(location, value, errors) { + if (type(value) in [ "int", "double" ]) { + if (value > 65535) + push(errors, [ location, "must be lower than or equal to 65535" ]); + + if (value < 1024) + push(errors, [ location, "must be bigger than or equal to 1024" ]); + + } + + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "auth-port")) { + obj.auth_port = parseAuthPort(location + "/auth-port", value["auth-port"], errors); + } + else { + obj.auth_port = 1812; + } + + function parseAuthSecret(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "auth-secret")) { + obj.auth_secret = parseAuthSecret(location + "/auth-secret", value["auth-secret"], errors); + } + + function parseAcctServer(location, value, errors) { + if (type(value) == "string") { + if (!matchUcHost(value)) + push(errors, [ location, "must be a valid hostname or IP address" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "acct-server")) { + obj.acct_server = parseAcctServer(location + "/acct-server", value["acct-server"], errors); + } + + function parseAcctPort(location, value, errors) { + if (type(value) in [ "int", "double" ]) { + if (value > 65535) + push(errors, [ location, "must be lower than or equal to 65535" ]); + + if (value < 1024) + push(errors, [ location, "must be bigger than or equal to 1024" ]); + + } + + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "acct-port")) { + obj.acct_port = parseAcctPort(location + "/acct-port", value["acct-port"], errors); + } + else { + obj.acct_port = 1812; + } + + function parseAcctSecret(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "acct-secret")) { + obj.acct_secret = parseAcctSecret(location + "/acct-secret", value["acct-secret"], errors); + } + + function parseAcctInterval(location, value, errors) { + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "acct-interval")) { + obj.acct_interval = parseAcctInterval(location + "/acct-interval", value["acct-interval"], errors); + } + else { + obj.acct_interval = 600; + } + + function parseSsid(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "ssid")) { + obj.ssid = parseSsid(location + "/ssid", value["ssid"], errors); + } + + function parseMacFormat(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (!(value in [ "aabbccddeeff", "aa-bb-cc-dd-ee-ff", "aa:bb:cc:dd:ee:ff", "AABBCCDDEEFF", "AA:BB:CC:DD:EE:FF", "AA-BB-CC-DD-EE-FF" ])) + push(errors, [ location, "must be one of \"aabbccddeeff\", \"aa-bb-cc-dd-ee-ff\", \"aa:bb:cc:dd:ee:ff\", \"AABBCCDDEEFF\", \"AA:BB:CC:DD:EE:FF\" or \"AA-BB-CC-DD-EE-FF\"" ]); + + return value; + } + + if (exists(value, "mac-format")) { + obj.mac_format = parseMacFormat(location + "/mac-format", value["mac-format"], errors); + } + + function parseFinalRedirectUrl(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + if (!(value in [ "default", "uam" ])) + push(errors, [ location, "must be one of \"default\" or \"uam\"" ]); + + return value; + } + + if (exists(value, "final-redirect-url")) { + obj.final_redirect_url = parseFinalRedirectUrl(location + "/final-redirect-url", value["final-redirect-url"], errors); + } + + function parseMacAuth(location, value, errors) { + if (type(value) != "bool") + push(errors, [ location, "must be of type boolean" ]); + + return value; + } + + if (exists(value, "mac-auth")) { + obj.mac_auth = parseMacAuth(location + "/mac-auth", value["mac-auth"], errors); + } + else { + obj.mac_auth = "default"; + } + + function parseRadiusGwProxy(location, value, errors) { + if (type(value) != "bool") + push(errors, [ location, "must be of type boolean" ]); + + return value; + } + + if (exists(value, "radius-gw-proxy")) { + obj.radius_gw_proxy = parseRadiusGwProxy(location + "/radius-gw-proxy", value["radius-gw-proxy"], errors); + } + else { + obj.radius_gw_proxy = false; + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; +} + +function instantiateServiceCaptive(location, value, errors) { + function parseVariant0(location, value, errors) { + function parseVariant0(location, value, errors) { + value = instantiateServiceCaptiveClick(location, value, errors); + + return value; + } + + function parseVariant1(location, value, errors) { + value = instantiateServiceCaptiveRadius(location, value, errors); + + return value; + } + + function parseVariant2(location, value, errors) { + value = instantiateServiceCaptiveCredentials(location, value, errors); + + return value; + } + + function parseVariant3(location, value, errors) { + value = instantiateServiceCaptiveUam(location, value, errors); + + return value; + } + + let success = 0, tryval, tryerr, vvalue = null, verrors = []; + + tryerr = []; + tryval = parseVariant0(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + tryerr = []; + tryval = parseVariant1(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + tryerr = []; + tryval = parseVariant2(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + tryerr = []; + tryval = parseVariant3(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + if (success != 1) { + if (length(verrors)) + push(errors, [ location, "must match exactly one of the following constraints:\n" + join("\n- or -\n", verrors) ]); + else + push(errors, [ location, "must match only one variant" ]); + return null; + } + + value = vvalue; + + return value; + } + + function parseVariant1(location, value, errors) { + if (type(value) == "object") { + let obj = {}; + + function parseWalledGardenFqdn(location, value, errors) { + if (type(value) == "array") { + function parseItem(location, value, errors) { + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); + } + + if (type(value) != "array") + push(errors, [ location, "must be of type array" ]); + + return value; + } + + if (exists(value, "walled-garden-fqdn")) { + obj.walled_garden_fqdn = parseWalledGardenFqdn(location + "/walled-garden-fqdn", value["walled-garden-fqdn"], errors); + } + + function parseWalledGardenIpaddr(location, value, errors) { + if (type(value) == "array") { + function parseItem(location, value, errors) { + if (type(value) == "string") { + if (!matchUcIp(value)) + push(errors, [ location, "must be a valid IPv4 or IPv6 address" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); + } + + if (type(value) != "array") + push(errors, [ location, "must be of type array" ]); + + return value; + } + + if (exists(value, "walled-garden-ipaddr")) { + obj.walled_garden_ipaddr = parseWalledGardenIpaddr(location + "/walled-garden-ipaddr", value["walled-garden-ipaddr"], errors); + } + + function parseWebRoot(location, value, errors) { + if (type(value) == "string") { + if (!matchUcBase64(value)) + push(errors, [ location, "must be a valid base64 encoded data" ]); + + } + + if (type(value) != "string") + push(errors, [ location, "must be of type string" ]); + + return value; + } + + if (exists(value, "web-root")) { + obj.web_root = parseWebRoot(location + "/web-root", value["web-root"], errors); + } + + function parseIdleTimeout(location, value, errors) { + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "idle-timeout")) { + obj.idle_timeout = parseIdleTimeout(location + "/idle-timeout", value["idle-timeout"], errors); + } + else { + obj.idle_timeout = 600; + } + + function parseSessionTimeout(location, value, errors) { + if (type(value) != "int") + push(errors, [ location, "must be of type integer" ]); + + return value; + } + + if (exists(value, "session-timeout")) { + obj.session_timeout = parseSessionTimeout(location + "/session-timeout", value["session-timeout"], errors); + } + + return obj; + } + + if (type(value) != "object") + push(errors, [ location, "must be of type object" ]); + + return value; + } + + let success = 0, tryval, tryerr, vvalue = null, verrors = []; + + tryerr = []; + tryval = parseVariant0(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + tryerr = []; + tryval = parseVariant1(location, value, tryerr); + if (!length(tryerr)) { + if (type(vvalue) == "object" && type(tryval) == "object") + vvalue = { ...vvalue, ...tryval }; + else + vvalue = tryval; + + success++; + } + else { + push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); + } + + if (success != 2) { + if (length(verrors)) + push(errors, [ location, "must match all of the following constraints:\n" + join("\n- or -\n", verrors) ]); + else + push(errors, [ location, "must match only one variant" ]); + return null; + } + + value = vvalue; + + return value; +} + function instantiateInterfaceSsid(location, value, errors) { if (type(value) == "object") { let obj = {}; @@ -4802,6 +5571,10 @@ function instantiateInterfaceSsid(location, value, errors) { obj.access_control_list = instantiateInterfaceSsidAcl(location + "/access-control-list", value["access-control-list"], errors); } + if (exists(value, "captive")) { + obj.captive = instantiateServiceCaptive(location + "/captive", value["captive"], errors); + } + function parseHostapdBssRaw(location, value, errors) { if (type(value) == "array") { function parseItem(location, value, errors) { @@ -7517,775 +8290,6 @@ function instantiateServiceWireguardOverlay(location, value, errors) { return value; } -function instantiateServiceCaptiveClick(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseAuthMode(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (value != "click-to-continue") - push(errors, [ location, "must have value \"click-to-continue\"" ]); - - return value; - } - - if (exists(value, "auth-mode")) { - obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; -} - -function instantiateServiceCaptiveRadius(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseAuthMode(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (value != "radius") - push(errors, [ location, "must have value \"radius\"" ]); - - return value; - } - - if (exists(value, "auth-mode")) { - obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); - } - - function parseAuthServer(location, value, errors) { - if (type(value) == "string") { - if (!matchUcHost(value)) - push(errors, [ location, "must be a valid hostname or IP address" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "auth-server")) { - obj.auth_server = parseAuthServer(location + "/auth-server", value["auth-server"], errors); - } - - function parseAuthPort(location, value, errors) { - if (type(value) in [ "int", "double" ]) { - if (value > 65535) - push(errors, [ location, "must be lower than or equal to 65535" ]); - - if (value < 1024) - push(errors, [ location, "must be bigger than or equal to 1024" ]); - - } - - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "auth-port")) { - obj.auth_port = parseAuthPort(location + "/auth-port", value["auth-port"], errors); - } - else { - obj.auth_port = 1812; - } - - function parseAuthSecret(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "auth-secret")) { - obj.auth_secret = parseAuthSecret(location + "/auth-secret", value["auth-secret"], errors); - } - - function parseAcctServer(location, value, errors) { - if (type(value) == "string") { - if (!matchUcHost(value)) - push(errors, [ location, "must be a valid hostname or IP address" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "acct-server")) { - obj.acct_server = parseAcctServer(location + "/acct-server", value["acct-server"], errors); - } - - function parseAcctPort(location, value, errors) { - if (type(value) in [ "int", "double" ]) { - if (value > 65535) - push(errors, [ location, "must be lower than or equal to 65535" ]); - - if (value < 1024) - push(errors, [ location, "must be bigger than or equal to 1024" ]); - - } - - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "acct-port")) { - obj.acct_port = parseAcctPort(location + "/acct-port", value["acct-port"], errors); - } - else { - obj.acct_port = 1812; - } - - function parseAcctSecret(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "acct-secret")) { - obj.acct_secret = parseAcctSecret(location + "/acct-secret", value["acct-secret"], errors); - } - - function parseAcctInterval(location, value, errors) { - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "acct-interval")) { - obj.acct_interval = parseAcctInterval(location + "/acct-interval", value["acct-interval"], errors); - } - else { - obj.acct_interval = 600; - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; -} - -function instantiateServiceCaptiveCredentials(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseAuthMode(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (value != "credentials") - push(errors, [ location, "must have value \"credentials\"" ]); - - return value; - } - - if (exists(value, "auth-mode")) { - obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); - } - - function parseCredentials(location, value, errors) { - if (type(value) == "array") { - function parseItem(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseUsername(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "username")) { - obj.username = parseUsername(location + "/username", value["username"], errors); - } - - function parsePassword(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "password")) { - obj.password = parsePassword(location + "/password", value["password"], errors); - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; - } - - return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); - } - - if (type(value) != "array") - push(errors, [ location, "must be of type array" ]); - - return value; - } - - if (exists(value, "credentials")) { - obj.credentials = parseCredentials(location + "/credentials", value["credentials"], errors); - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; -} - -function instantiateServiceCaptiveUam(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseAuthMode(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (value != "uam") - push(errors, [ location, "must have value \"uam\"" ]); - - return value; - } - - if (exists(value, "auth-mode")) { - obj.auth_mode = parseAuthMode(location + "/auth-mode", value["auth-mode"], errors); - } - - function parseUamPort(location, value, errors) { - if (type(value) in [ "int", "double" ]) { - if (value > 65535) - push(errors, [ location, "must be lower than or equal to 65535" ]); - - if (value < 1024) - push(errors, [ location, "must be bigger than or equal to 1024" ]); - - } - - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "uam-port")) { - obj.uam_port = parseUamPort(location + "/uam-port", value["uam-port"], errors); - } - else { - obj.uam_port = 3990; - } - - function parseUamSecret(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "uam-secret")) { - obj.uam_secret = parseUamSecret(location + "/uam-secret", value["uam-secret"], errors); - } - - function parseUamServer(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "uam-server")) { - obj.uam_server = parseUamServer(location + "/uam-server", value["uam-server"], errors); - } - - function parseNasid(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "nasid")) { - obj.nasid = parseNasid(location + "/nasid", value["nasid"], errors); - } - - function parseNasmac(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "nasmac")) { - obj.nasmac = parseNasmac(location + "/nasmac", value["nasmac"], errors); - } - - function parseAuthServer(location, value, errors) { - if (type(value) == "string") { - if (!matchUcHost(value)) - push(errors, [ location, "must be a valid hostname or IP address" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "auth-server")) { - obj.auth_server = parseAuthServer(location + "/auth-server", value["auth-server"], errors); - } - - function parseAuthPort(location, value, errors) { - if (type(value) in [ "int", "double" ]) { - if (value > 65535) - push(errors, [ location, "must be lower than or equal to 65535" ]); - - if (value < 1024) - push(errors, [ location, "must be bigger than or equal to 1024" ]); - - } - - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "auth-port")) { - obj.auth_port = parseAuthPort(location + "/auth-port", value["auth-port"], errors); - } - else { - obj.auth_port = 1812; - } - - function parseAuthSecret(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "auth-secret")) { - obj.auth_secret = parseAuthSecret(location + "/auth-secret", value["auth-secret"], errors); - } - - function parseAcctServer(location, value, errors) { - if (type(value) == "string") { - if (!matchUcHost(value)) - push(errors, [ location, "must be a valid hostname or IP address" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "acct-server")) { - obj.acct_server = parseAcctServer(location + "/acct-server", value["acct-server"], errors); - } - - function parseAcctPort(location, value, errors) { - if (type(value) in [ "int", "double" ]) { - if (value > 65535) - push(errors, [ location, "must be lower than or equal to 65535" ]); - - if (value < 1024) - push(errors, [ location, "must be bigger than or equal to 1024" ]); - - } - - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "acct-port")) { - obj.acct_port = parseAcctPort(location + "/acct-port", value["acct-port"], errors); - } - else { - obj.acct_port = 1812; - } - - function parseAcctSecret(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "acct-secret")) { - obj.acct_secret = parseAcctSecret(location + "/acct-secret", value["acct-secret"], errors); - } - - function parseAcctInterval(location, value, errors) { - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "acct-interval")) { - obj.acct_interval = parseAcctInterval(location + "/acct-interval", value["acct-interval"], errors); - } - else { - obj.acct_interval = 600; - } - - function parseSsid(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "ssid")) { - obj.ssid = parseSsid(location + "/ssid", value["ssid"], errors); - } - - function parseMacFormat(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (!(value in [ "aabbccddeeff", "aa-bb-cc-dd-ee-ff", "aa:bb:cc:dd:ee:ff", "AABBCCDDEEFF", "AA:BB:CC:DD:EE:FF", "AA-BB-CC-DD-EE-FF" ])) - push(errors, [ location, "must be one of \"aabbccddeeff\", \"aa-bb-cc-dd-ee-ff\", \"aa:bb:cc:dd:ee:ff\", \"AABBCCDDEEFF\", \"AA:BB:CC:DD:EE:FF\" or \"AA-BB-CC-DD-EE-FF\"" ]); - - return value; - } - - if (exists(value, "mac-format")) { - obj.mac_format = parseMacFormat(location + "/mac-format", value["mac-format"], errors); - } - - function parseFinalRedirectUrl(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - if (!(value in [ "default", "uam" ])) - push(errors, [ location, "must be one of \"default\" or \"uam\"" ]); - - return value; - } - - if (exists(value, "final-redirect-url")) { - obj.final_redirect_url = parseFinalRedirectUrl(location + "/final-redirect-url", value["final-redirect-url"], errors); - } - - function parseMacAuth(location, value, errors) { - if (type(value) != "bool") - push(errors, [ location, "must be of type boolean" ]); - - return value; - } - - if (exists(value, "mac-auth")) { - obj.mac_auth = parseMacAuth(location + "/mac-auth", value["mac-auth"], errors); - } - else { - obj.mac_auth = "default"; - } - - function parseRadiusGwProxy(location, value, errors) { - if (type(value) != "bool") - push(errors, [ location, "must be of type boolean" ]); - - return value; - } - - if (exists(value, "radius-gw-proxy")) { - obj.radius_gw_proxy = parseRadiusGwProxy(location + "/radius-gw-proxy", value["radius-gw-proxy"], errors); - } - else { - obj.radius_gw_proxy = false; - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; -} - -function instantiateServiceCaptive(location, value, errors) { - function parseVariant0(location, value, errors) { - function parseVariant0(location, value, errors) { - value = instantiateServiceCaptiveClick(location, value, errors); - - return value; - } - - function parseVariant1(location, value, errors) { - value = instantiateServiceCaptiveRadius(location, value, errors); - - return value; - } - - function parseVariant2(location, value, errors) { - value = instantiateServiceCaptiveCredentials(location, value, errors); - - return value; - } - - function parseVariant3(location, value, errors) { - value = instantiateServiceCaptiveUam(location, value, errors); - - return value; - } - - let success = 0, tryval, tryerr, vvalue = null, verrors = []; - - tryerr = []; - tryval = parseVariant0(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - tryerr = []; - tryval = parseVariant1(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - tryerr = []; - tryval = parseVariant2(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - tryerr = []; - tryval = parseVariant3(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - if (success != 1) { - if (length(verrors)) - push(errors, [ location, "must match exactly one of the following constraints:\n" + join("\n- or -\n", verrors) ]); - else - push(errors, [ location, "must match only one variant" ]); - return null; - } - - value = vvalue; - - return value; - } - - function parseVariant1(location, value, errors) { - if (type(value) == "object") { - let obj = {}; - - function parseWalledGardenFqdn(location, value, errors) { - if (type(value) == "array") { - function parseItem(location, value, errors) { - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); - } - - if (type(value) != "array") - push(errors, [ location, "must be of type array" ]); - - return value; - } - - if (exists(value, "walled-garden-fqdn")) { - obj.walled_garden_fqdn = parseWalledGardenFqdn(location + "/walled-garden-fqdn", value["walled-garden-fqdn"], errors); - } - - function parseWalledGardenIpaddr(location, value, errors) { - if (type(value) == "array") { - function parseItem(location, value, errors) { - if (type(value) == "string") { - if (!matchUcIp(value)) - push(errors, [ location, "must be a valid IPv4 or IPv6 address" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - return map(value, (item, i) => parseItem(location + "/" + i, item, errors)); - } - - if (type(value) != "array") - push(errors, [ location, "must be of type array" ]); - - return value; - } - - if (exists(value, "walled-garden-ipaddr")) { - obj.walled_garden_ipaddr = parseWalledGardenIpaddr(location + "/walled-garden-ipaddr", value["walled-garden-ipaddr"], errors); - } - - function parseWebRoot(location, value, errors) { - if (type(value) == "string") { - if (!matchUcBase64(value)) - push(errors, [ location, "must be a valid base64 encoded data" ]); - - } - - if (type(value) != "string") - push(errors, [ location, "must be of type string" ]); - - return value; - } - - if (exists(value, "web-root")) { - obj.web_root = parseWebRoot(location + "/web-root", value["web-root"], errors); - } - - function parseIdleTimeout(location, value, errors) { - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "idle-timeout")) { - obj.idle_timeout = parseIdleTimeout(location + "/idle-timeout", value["idle-timeout"], errors); - } - else { - obj.idle_timeout = 600; - } - - function parseSessionTimeout(location, value, errors) { - if (type(value) != "int") - push(errors, [ location, "must be of type integer" ]); - - return value; - } - - if (exists(value, "session-timeout")) { - obj.session_timeout = parseSessionTimeout(location + "/session-timeout", value["session-timeout"], errors); - } - - return obj; - } - - if (type(value) != "object") - push(errors, [ location, "must be of type object" ]); - - return value; - } - - let success = 0, tryval, tryerr, vvalue = null, verrors = []; - - tryerr = []; - tryval = parseVariant0(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - tryerr = []; - tryval = parseVariant1(location, value, tryerr); - if (!length(tryerr)) { - if (type(vvalue) == "object" && type(tryval) == "object") - vvalue = { ...vvalue, ...tryval }; - else - vvalue = tryval; - - success++; - } - else { - push(verrors, join(" and\n", map(tryerr, err => "\t - " + err[1]))); - } - - if (success != 2) { - if (length(verrors)) - push(errors, [ location, "must match all of the following constraints:\n" + join("\n- or -\n", verrors) ]); - else - push(errors, [ location, "must match only one variant" ]); - return null; - } - - value = vvalue; - - return value; -} - function instantiateServiceGps(location, value, errors) { if (type(value) == "object") { let obj = {}; diff --git a/ucentral.schema.json b/ucentral.schema.json index ee5a074..48b7ed4 100644 --- a/ucentral.schema.json +++ b/ucentral.schema.json @@ -1672,6 +1672,236 @@ } } }, + "service.captive.click": { + "type": "object", + "properties": { + "auth-mode": { + "type": "string", + "const": "click-to-continue" + } + } + }, + "service.captive.radius": { + "type": "object", + "properties": { + "auth-mode": { + "type": "string", + "const": "radius" + }, + "auth-server": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "auth-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "default": 1812 + }, + "auth-secret": { + "type": "string", + "examples": [ + "secret" + ] + }, + "acct-server": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "acct-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "default": 1812 + }, + "acct-secret": { + "type": "string", + "examples": [ + "secret" + ] + }, + "acct-interval": { + "type": "integer", + "default": 600 + } + } + }, + "service.captive.credentials": { + "type": "object", + "properties": { + "auth-mode": { + "type": "string", + "const": "credentials" + }, + "credentials": { + "type": "array", + "items": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "service.captive.uam": { + "type": "object", + "properties": { + "auth-mode": { + "type": "string", + "const": "uam" + }, + "uam-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "default": 3990 + }, + "uam-secret": { + "type": "string" + }, + "uam-server": { + "type": "string" + }, + "nasid": { + "type": "string" + }, + "nasmac": { + "type": "string" + }, + "auth-server": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "auth-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "default": 1812 + }, + "auth-secret": { + "type": "string", + "examples": [ + "secret" + ] + }, + "acct-server": { + "type": "string", + "format": "uc-host", + "examples": [ + "192.168.1.10" + ] + }, + "acct-port": { + "type": "integer", + "maximum": 65535, + "minimum": 1024, + "default": 1812 + }, + "acct-secret": { + "type": "string", + "examples": [ + "secret" + ] + }, + "acct-interval": { + "type": "integer", + "default": 600 + }, + "ssid": { + "type": "string" + }, + "mac-format": { + "type": "string", + "enum": [ + "aabbccddeeff", + "aa-bb-cc-dd-ee-ff", + "aa:bb:cc:dd:ee:ff", + "AABBCCDDEEFF", + "AA:BB:CC:DD:EE:FF", + "AA-BB-CC-DD-EE-FF" + ] + }, + "final-redirect-url": { + "type": "string", + "enum": [ + "default", + "uam" + ] + }, + "mac-auth": { + "type": "boolean", + "default": "default" + }, + "radius-gw-proxy": { + "type": "boolean", + "default": false + } + } + }, + "service.captive": { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/$defs/service.captive.click" + }, + { + "$ref": "#/$defs/service.captive.radius" + }, + { + "$ref": "#/$defs/service.captive.credentials" + }, + { + "$ref": "#/$defs/service.captive.uam" + } + ] + }, + { + "type": "object", + "properties": { + "walled-garden-fqdn": { + "type": "array", + "items": { + "type": "string" + } + }, + "walled-garden-ipaddr": { + "type": "array", + "items": { + "type": "string", + "format": "uc-ip" + } + }, + "web-root": { + "type": "string", + "format": "uc-base64" + }, + "idle-timeout": { + "type": "integer", + "default": 600 + }, + "session-timeout": { + "type": "integer" + } + } + } + ] + }, "interface.ssid": { "type": "object", "properties": { @@ -1816,6 +2046,9 @@ "access-control-list": { "$ref": "#/$defs/interface.ssid.acl" }, + "captive": { + "$ref": "#/$defs/service.captive" + }, "hostapd-bss-raw": { "type": "array", "items": { @@ -2714,236 +2947,6 @@ } } }, - "service.captive.click": { - "type": "object", - "properties": { - "auth-mode": { - "type": "string", - "const": "click-to-continue" - } - } - }, - "service.captive.radius": { - "type": "object", - "properties": { - "auth-mode": { - "type": "string", - "const": "radius" - }, - "auth-server": { - "type": "string", - "format": "uc-host", - "examples": [ - "192.168.1.10" - ] - }, - "auth-port": { - "type": "integer", - "maximum": 65535, - "minimum": 1024, - "default": 1812 - }, - "auth-secret": { - "type": "string", - "examples": [ - "secret" - ] - }, - "acct-server": { - "type": "string", - "format": "uc-host", - "examples": [ - "192.168.1.10" - ] - }, - "acct-port": { - "type": "integer", - "maximum": 65535, - "minimum": 1024, - "default": 1812 - }, - "acct-secret": { - "type": "string", - "examples": [ - "secret" - ] - }, - "acct-interval": { - "type": "integer", - "default": 600 - } - } - }, - "service.captive.credentials": { - "type": "object", - "properties": { - "auth-mode": { - "type": "string", - "const": "credentials" - }, - "credentials": { - "type": "array", - "items": { - "type": "object", - "properties": { - "username": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - } - }, - "service.captive.uam": { - "type": "object", - "properties": { - "auth-mode": { - "type": "string", - "const": "uam" - }, - "uam-port": { - "type": "integer", - "maximum": 65535, - "minimum": 1024, - "default": 3990 - }, - "uam-secret": { - "type": "string" - }, - "uam-server": { - "type": "string" - }, - "nasid": { - "type": "string" - }, - "nasmac": { - "type": "string" - }, - "auth-server": { - "type": "string", - "format": "uc-host", - "examples": [ - "192.168.1.10" - ] - }, - "auth-port": { - "type": "integer", - "maximum": 65535, - "minimum": 1024, - "default": 1812 - }, - "auth-secret": { - "type": "string", - "examples": [ - "secret" - ] - }, - "acct-server": { - "type": "string", - "format": "uc-host", - "examples": [ - "192.168.1.10" - ] - }, - "acct-port": { - "type": "integer", - "maximum": 65535, - "minimum": 1024, - "default": 1812 - }, - "acct-secret": { - "type": "string", - "examples": [ - "secret" - ] - }, - "acct-interval": { - "type": "integer", - "default": 600 - }, - "ssid": { - "type": "string" - }, - "mac-format": { - "type": "string", - "enum": [ - "aabbccddeeff", - "aa-bb-cc-dd-ee-ff", - "aa:bb:cc:dd:ee:ff", - "AABBCCDDEEFF", - "AA:BB:CC:DD:EE:FF", - "AA-BB-CC-DD-EE-FF" - ] - }, - "final-redirect-url": { - "type": "string", - "enum": [ - "default", - "uam" - ] - }, - "mac-auth": { - "type": "boolean", - "default": "default" - }, - "radius-gw-proxy": { - "type": "boolean", - "default": false - } - } - }, - "service.captive": { - "allOf": [ - { - "oneOf": [ - { - "$ref": "#/$defs/service.captive.click" - }, - { - "$ref": "#/$defs/service.captive.radius" - }, - { - "$ref": "#/$defs/service.captive.credentials" - }, - { - "$ref": "#/$defs/service.captive.uam" - } - ] - }, - { - "type": "object", - "properties": { - "walled-garden-fqdn": { - "type": "array", - "items": { - "type": "string" - } - }, - "walled-garden-ipaddr": { - "type": "array", - "items": { - "type": "string", - "format": "uc-ip" - } - }, - "web-root": { - "type": "string", - "format": "uc-base64" - }, - "idle-timeout": { - "type": "integer", - "default": 600 - }, - "session-timeout": { - "type": "integer" - } - } - } - ] - }, "service.gps": { "type": "object", "properties": {