uspot: add advanced captive http components

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2022-08-17 14:51:29 +02:00
parent cf18242ee5
commit baaa31f445
35 changed files with 1834 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
{
"uuid": 2,
"radios": [
{
"band": "6G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "5G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "2G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
}
],
"interfaces": [
{
"name": "WAN",
"role": "upstream",
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"ssids": [
{
"name": "OpenWifi-hotspot",
"services": [ "captive" ],
"wifi-bands": [
"5G",
"2G"
],
"bss-mode": "ap",
"encryption": {
"proto": "psk2",
"key": "OpenWifi",
"ieee80211w": "optional"
}
}
]
},
{
"name": "LAN",
"role": "downstream",
"services": [ "ssh" ],
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"subnet": "192.168.1.1/24",
"dhcp": {
"lease-first": 10,
"lease-count": 100,
"lease-time": "6h"
}
}
}
],
"metrics": {
"statistics": {
"interval": 120,
"types": [ "ssids", "lldp", "clients" ]
},
"health": {
"interval": 120
}
},
"services": {
"ssh": {
"port": 22
},
"captive": {
"auth-mode": "click-to-continue",
"walled-garden-fqdn": [
"*.google.com", "telecominfraproject.com"
]
}
}
}

View File

@@ -0,0 +1,103 @@
{
"uuid": 2,
"radios": [
{
"band": "6G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "5G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "2G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
}
],
"interfaces": [
{
"name": "WAN",
"role": "upstream",
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"ssids": [
{
"name": "OpenWifi-hotspot",
"services": [ "captive" ],
"wifi-bands": [
"5G",
"2G"
],
"bss-mode": "ap",
"encryption": {
"proto": "psk2",
"key": "OpenWifi",
"ieee80211w": "optional"
}
}
]
},
{
"name": "LAN",
"role": "downstream",
"services": [ "ssh" ],
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"subnet": "192.168.1.1/24",
"dhcp": {
"lease-first": 10,
"lease-count": 100,
"lease-time": "6h"
}
}
}
],
"metrics": {
"statistics": {
"interval": 120,
"types": [ "ssids", "lldp", "clients" ]
},
"health": {
"interval": 120
}
},
"services": {
"ssh": {
"port": 22
},
"captive": {
"auth-mode": "credentials",
"credentials": [
{
"username": "abc",
"password": "def"
}
],
"walled-garden-fqdn": [
"*.google.com", "telecominfraproject.com"
]
}
}
}

View File

@@ -0,0 +1,100 @@
{
"uuid": 2,
"radios": [
{
"band": "6G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "5G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "2G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
}
],
"interfaces": [
{
"name": "WAN",
"role": "upstream",
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"ssids": [
{
"name": "OpenWifi-hotspot",
"services": [ "captive" ],
"wifi-bands": [
"5G",
"2G"
],
"bss-mode": "ap",
"encryption": {
"proto": "psk2",
"key": "OpenWifi",
"ieee80211w": "optional"
}
}
]
},
{
"name": "LAN",
"role": "downstream",
"services": [ "ssh" ],
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"subnet": "192.168.1.1/24",
"dhcp": {
"lease-first": 10,
"lease-count": 100,
"lease-time": "6h"
}
}
}
],
"metrics": {
"statistics": {
"interval": 120,
"types": [ "ssids", "lldp", "clients" ]
},
"health": {
"interval": 120
}
},
"services": {
"ssh": {
"port": 22
},
"captive": {
"auth-mode": "radius",
"auth-server": "212.24.98.232",
"auth-port": 1812,
"auth-secret": "secret",
"walled-garden-fqdn": [
"*.google.com", "telecominfraproject.com"
]
}
}
}

View File

@@ -0,0 +1,104 @@
{
"uuid": 2,
"radios": [
{
"band": "6G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "5G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
},
{
"band": "2G",
"country": "CA",
"channel-mode": "HE",
"channel-width": 80
}
],
"interfaces": [
{
"name": "WAN",
"role": "upstream",
"ethernet": [
{
"select-ports": [
"WAN*"
]
}
],
"ipv4": {
"addressing": "dynamic"
},
"ssids": [
{
"name": "OpenWifi-hotspot",
"services": [ "captive" ],
"wifi-bands": [
"5G",
"2G"
],
"bss-mode": "ap",
"encryption": {
"proto": "psk2",
"key": "OpenWifi",
"ieee80211w": "optional"
}
}
]
},
{
"name": "LAN",
"role": "downstream",
"services": [ "ssh" ],
"ethernet": [
{
"select-ports": [
"LAN*"
]
}
],
"ipv4": {
"addressing": "static",
"subnet": "192.168.1.1/24",
"dhcp": {
"lease-first": 10,
"lease-count": 100,
"lease-time": "6h"
}
}
}
],
"metrics": {
"statistics": {
"interval": 120,
"types": [ "ssids", "lldp", "clients" ]
},
"health": {
"interval": 120
}
},
"services": {
"ssh": {
"port": 22
},
"captive": {
"auth-mode": "uam",
"uam-port": 3990,
"uam-secret": "hotsys123",
"uam-server": "https://customer.hotspotsystem.com/customer/hotspotlogin.php",
"nasid": "AlmondLabs",
"auth-server": "radius.hotspotsystem.com",
"auth-port": 1812,
"auth-secret": "hotsys123",
"walled-garden-fqdn": [
"*.google.com", "telecominfraproject.com", "customer.hotspotsystem.com"
]
}
}
}

View File

@@ -305,4 +305,5 @@ VALUE Add-Port-To-IP-Address Yes 1
#$INCLUDE /etc/radcli/dictionary.microsoft
#$INCLUDE /etc/radcli/dictionary.roaringpenguin
$INCLUDE /etc/radcli/dictionary.WISPr

View File

@@ -0,0 +1,13 @@
VENDOR WISPr 14122 WISPr
ATTRIBUTE WISPr-Location-ID 1 string WISPr
ATTRIBUTE WISPr-Location-Name 2 string WISPr
ATTRIBUTE WISPr-Logoff-URL 3 string WISPr
ATTRIBUTE WISPr-Redirection-URL 4 string WISPr
ATTRIBUTE WISPr-Bandwidth-Min-Up 5 integer WISPr
ATTRIBUTE WISPr-Bandwidth-Min-Down 6 integer WISPr
ATTRIBUTE WISPr-Bandwidth-Max-Up 7 integer WISPr
ATTRIBUTE WISPr-Bandwidth-Max-Down 8 integer WISPr
ATTRIBUTE WISPr-Session-Terminate-Time 9 string WISPr
ATTRIBUTE WISPr-Billing-Class-Of-Service 11 string WISPr

View File

@@ -0,0 +1,26 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=uspot
PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/uspot
SECTION:=net
CATEGORY:=Network
TITLE:=hotspot daemon
DEPENDS:=+spotfilter +uhttpd-mod-ucode +libradcli
endef
define Package/uspot/install
$(INSTALL_DIR) $(1)/usr/bin/ $(1)/usr/lib/ucode
$(INSTALL_BIN) $(PKG_BUILD_DIR)/radius-client $(1)/usr/bin
$(INSTALL_DATA) $(PKG_BUILD_DIR)/libuam.so $(1)/usr/lib/ucode/uam.so
$(CP) ./files/* $(1)
endef
$(eval $(call BuildPackage,uspot))

View File

@@ -0,0 +1,36 @@
ubus call spotfilter interface_add '{ "name": "hotspot", "devices": [ "wlancaptive0", "wlancaptive1" ], "config": { "default_class": 1, "default_dns_class": 0, "class": [ { "index": 0, "device_macaddr": "up0v0", "fwmark": 1,"fwmark_mask": 127}, { "index": 1, "fwmark": 2,"fwmark_mask": 127 }] } }'
ubus call spotfilter interface_add '{ "name": "hotspot", "devices": [ "wlan0" ], "config": { "class": [ { "index": 0, "device_macaddr": "up0v0", "fwmark": 1,"fwmark_mask": 127}, { "index": 1, "fwmark": 2,"fwmark_mask": 127 }] } }'
ubus call spotfilter interface_add '{ "name": "hotspot", "devices": [ "wlan1" ], "config": { "class": [ { "index": 0, "device_macaddr": "up0v0", "fwmark": 1,"fwmark_mask": 127}, { "index": 1, "fwmark": 2,"fwmark_mask": 127 }] } }'
ubus call spotfilter client_set '{"interface":"hotspot", "address": "1c:57:dc:37:3c:b1", "state": 0, "dns_state": 1}'
ubus call spotfilter client_set '{"interface":"hotspot", "address": "e8:a7:30:a0:9c:6e", "state": 0, "dns_state": 1}'
ubus call spotfilter client_get '{"interface":"hotspot", "address": "1e:07:4c:c0:89:a7", "state": 0, "dns_state": 1}'
ubus call spotfilter client_set '{"interface":"hotspot", "address": "e8:a7:30:a0:9c:6e", "state": 0, "dns_state": 1}'
ubus call spotfilter client_get '{"interface":"hotspot", "address": "1c:57:dc:37:3c:b1", "state": 0, "dns_state": 1}'
config redirect
option name 'HTTP-Redirect'
option src up0v0
option src_dport 80
option proto tcp
option target DNAT
option mark 1/127
config rule
option name 'Allow-captive-up0v0'
option src 'up0v0'
option dest_port '80'
option proto 'tcp'
option mark 2/127
option target 'ACCEPT'
config rule
option name 'Allow-captive-up0v0'
option src 'up0v0'
option dest_port '80'
option proto 'tcp'
option mark 1/127
option target 'ACCEPT'

View File

@@ -0,0 +1,25 @@
config uspot config
#option auth_mode 'uam'
#option auth_mode 'radius'
#option auth_mode 'credentials'
option auth_mode 'click-to-continue'
config radius radius
# option auth_server 212.24.98.232
# option auth_port 1812
# option auth_secret secret
config uam uam
# option port 3990
# option nasid AlmondLabs
# option nasmac 903cb3bb25e3
# option server https://customer.hotspotsystem.com/customer/hotspotlogin.php
# option secret hotsys123
#config credential
# option username abc
# option password def
#config credential
# option username 123
# option password 456

View File

@@ -0,0 +1,8 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
<h1> Connected </h1>
{{ footer }}

View File

@@ -0,0 +1,11 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
{{ form_data.username }}
{{ form_data.password }}
<h1> Radius auth </h1>
{{ footer }}

View File

@@ -0,0 +1,19 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
{% if (error): %}
<h1> {{ PO('accept_terms_error', 'Please accept the terms of use.') }} </h1>
{% endif %}
<h5 class="card-title">{{ PO('welcome', 'Welcome!') }}</h5>
<p class="card-text">{{ PO('accept_terms_header', 'To access the Internet you must Accept the Terms of Service.') }}</p>
<hr>
<form action="/hotspot" method="post">
<input type="hidden" name="action" value="click">
<input type="checkbox" name="accept_terms" value="clicked">{{ PO('accept_terms_checkbox', 'I accept the terms of use') }}
<input type="submit" value="{{ PO('accept_terms_button', 'Accept Terms of Service') }}" class="btn btn-primary btn-block">
{% if (query_string?.redir): %}
<input type="hidden" name="redir" value="{{ query_string.redir }}">
{% endif %}
</form>
{{ footer }}

View File

@@ -0,0 +1,8 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
<h1> you are already Connected </h1>
{{ footer }}

View File

@@ -0,0 +1,14 @@
Status: 200 OK
Content-Type: text/html
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="refresh" content="0; URL=http://{{env.SERVER_ADDR}}/hotspot/?redir={{env.headers.host}}" />
</head>
<body style="background-color: white">
<a style="color: black; font-family: arial, helvetica, sans-serif;" href="http://{{env.SERVER_ADDR}}/hotspot">HotSpot Login</a>
</body>
</html>

View File

@@ -0,0 +1,18 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
{% if (error): %}
<h1> Invalid credentials </h1>
{% endif %}
<form action="/hotspot" method="POST">
<label for="fname">Username:</label>
<input type="text" name="username"><br>
<label for="fname">Password:</label>
<input type="password" name="password">
<input type="hidden" name="action" value="credentials">
<input type="submit" value="Login" class="btn btn-primary btn-block">
</form>
{{ footer }}

View File

@@ -0,0 +1,22 @@
Status: 200 OK
Content-Type: text/html
<h1>Headers</h1>
{% for (let k, v in env.headers): %}
<strong>{{ replace(k, /(^|-)(.)/g, (m0, d, c) => d + uc(c)) }}</strong>: {{ v }}<br>
{% endfor %}
<h1>Environment</h1>
{% for (let k, v in env): if (type(v) == 'string'): %}
<code>{{ k }}={{ v }}</code><br>
{% endif; endfor %}
{% if (env.CONTENT_LENGTH > 0): %}
<h1>Body Contents</h1>
{% for (let chunk = uhttpd.recv(64); chunk != null; chunk = uhttpd.recv(64)): %}
<code>{{ replace(chunk, /[^[:graph:]]/g, '.') }}</code><br>
{% endfor %}
{% endif %}

View File

@@ -0,0 +1,8 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
<h1> An Error occured, please try again </h1>
{{ footer }}

View File

@@ -0,0 +1,9 @@
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,8 @@
{%
'use strict';
global.handle_request = function(env) {
include('cpd.uc', { env });
};
%}

View File

@@ -0,0 +1,7 @@
{%
'use strict';
global.handle_request = function(env) {
include("dump-env.uc", { env });
};

View File

@@ -0,0 +1,93 @@
{%
'use strict';
let fs = require('fs');
let rtnl = require('rtnl');
let file = fs.open('/usr/share/uspot/header', 'r');
let header = file.read('all');
file.close();
file = fs.open('/usr/share/uspot/footer', 'r');
let footer = file.read('all');
file.close();
let uci = require('uci').cursor();
let config = uci.get_all('uspot');
let uam = require('uam');
// give a client access to the internet
function allow_client(ctx) {
system('ubus call spotfilter client_set \'{ "interface": "hotspot", "address": "' + replace(ctx.mac, '-', ':') + '", "state": 1, "dns_state": 1}\'');
include('allow.uc', ctx);
}
// log the client in via radius
function auth_client(ctx) {
if (!ctx.query_string.username || !ctx.query_string.password) {
include('allow.uc', ctx);
return false;
}
let password = uam.password(uam.md5(config.uam.challenge, ctx.mac), ctx.query_string.password, config.uam.uam_secret);
let cfg = fs.open('/tmp/' + ctx.mac + '.json', 'w');
cfg.write({
type: 'uam-auth',
server: sprintf('%s:%s:%s',config.radius.auth_server, config.radius.auth_port, config.radius.auth_secret),
username: ctx.mac,
password,
acct_session: "0123456789",
client_ip: ctx.env.REMOTE_ADDR,
called_station: ctx.mac,
calling_station: config.uam.nasmac,
nas_ip: ctx.env.SERVER_ADDR,
nas_id: config.uam.nasid
});
cfg.close();
let stdout = fs.popen('/usr/bin/radius-client /tmp/' + ctx.mac + '.json');
let reply;
if (!stdout) {
request_start({ ...ctx, error: 1 });
return;
}
reply = json(stdout.read('all'));
stdout.close();
if (reply['access-accept']) {
allow_client(ctx);
return;
}
include('error.uc', ctx);
}
global.handle_request = function(env) {
let mac;
let form_data = {};
let query_string = {};
let post_data = '';
let ctx = { env, header, footer, mac, form_data, post_data, query_string, config };
// lookup the peers MAC
let macs = rtnl.request(rtnl.const.RTM_GETNEIGH, rtnl.const.NLM_F_DUMP, { });
for (let m in macs)
if (m.dst == env.REMOTE_HOST)
ctx.mac = replace(m.lladdr, ':', '-');
// if the MAC lookup failed, go to the error page
if (!ctx.mac)
include('error.uc', ctx);
// split QUERY_STRING
if (env.QUERY_STRING)
for (let chunk in split(env.QUERY_STRING, '&')) {
let var = split(chunk, '=');
if (length(var) != 2)
continue;
ctx.query_string[var[0]] = var[1];
}
auth_client(ctx);
};
%}

View File

@@ -0,0 +1,233 @@
{%
'use strict';
let fs = require('fs');
let rtnl = require('rtnl');
let uam = require('uam');
let file = fs.open('/usr/share/uspot/header', 'r');
let header = file.read('all');
file.close();
file = fs.open('/usr/share/uspot/footer', 'r');
let footer = file.read('all');
file.close();
let uci = require('uci').cursor();
let config = uci.get_all('uspot');
// fs.open wrapper
function fs_open(cmd) {
let stdout = fs.popen(cmd);
if (!stdout)
return null;
let reply = null;
try {
reply = json(stdout.read('all'));
} catch(e) {
}
stdout.close();
return reply;
}
// give a client access to the internet
function allow_client(ctx) {
system('ubus call spotfilter client_set \'{ "interface": "hotspot", "address": "' + replace(ctx.mac, '-', ':') + '", "state": 1, "dns_state": 1}\'');
include('allow.uc', ctx);
}
// delegate an initial connection to the correct handler
function request_start(ctx) {
switch (config?.config?.auth_mode) {
case 'click-to-continue':
include('click.uc', ctx);
return;
case 'credentials':
include('credentials.uc', ctx);
return;
case 'radius':
include('radius.uc', ctx);
return;
case 'uam':
ctx.uam_location = config.uam.uam_server +
'?res=notyet' +
'&uamip=' + ctx.env.SERVER_ADDR +
'&uamport=' + config.uam.uam_port +
'&challenge=' + uam.md5(config.uam.challenge, ctx.mac) +
'&mac=' + replace(ctx.mac, ':', '-') +
'&ip=' + ctx.env.REMOTE_ADDR +
'&called=' + config.uam.nasmac +
'&nasid=' + config.uam.nasid +
'&userurl=www.google.com';
ctx.uam_location += '&md=' + uam.md5(ctx.uam_location, config.uam.uam_secret);
include('uam.uc', ctx);
return;
default:
include('error.uc', ctx);
return;
}
}
// delegate a local click-to-continue authentication
function request_click(ctx) {
// make sure this is the right auth_mode
if (config?.config?.auth_mode != 'click-to-continue') {
include('error.uc', ctx);
return;
}
// check if a username and password was provided
if (ctx.form_data.accept_terms != 'clicked') {
request_start({ ...ctx, error: 1 });
return;
}
allow_client(ctx);
}
// delegate a local username/password authentication
function request_credentials(ctx) {
// make sure this is the right auth_mode
if (config?.config?.auth_mode != 'credentials') {
include('error.uc', ctx);
return;
}
// check if a username and password was provided
if (!ctx.form_data.username || !ctx.form_data.password) {
request_start({ ...ctx, error: 1 });
return;
}
// check if the credentials are valid
for (let k in config) {
let cred = config[k];
if (cred['.type'] != 'credentials')
continue;
if (ctx.form_data.username != cred.username ||
ctx.form_data.password != cred.password)
continue;
allow_client(ctx);
return;
}
// auth failed
request_start({ ...ctx, error: 1 });
}
// delegate a radius username/password authentication
function request_radius(ctx) {
// make sure this is the right auth_mode
if (config?.config?.auth_mode != 'radius') {
include('error.uc', ctx);
return;
}
// check if a username and password was provided
if (!ctx.form_data.username || !ctx.form_data.password) {
request_start({ ...ctx, error: 1 });
return;
}
let cfg = fs.open('/tmp/' + ctx.mac + '.json', 'w');
cfg.write({
type: 'auth',
server: sprintf('%s:%s:%s',config.radius.auth_server, config.radius.auth_port, config.radius.auth_secret),
username: ctx.form_data.username,
password: ctx.form_data.password,
acct_session: "0123456789",
client_ip: ctx.env.REMOTE_ADDR,
called_station: ctx.mac,
nas_ip: ctx.env.SERVER_ADDR,
});
cfg.close();
let stdout = fs.popen('/usr/bin/radius-client /tmp/' + ctx.mac + '.json');
let reply;
if (!stdout) {
request_start({ ...ctx, error: 1 });
return;
}
reply = json(stdout.read('all'));
stdout.close();
if (reply['access-accept']) {
allow_client(ctx);
return;
}
// auth failed
request_start({ ...ctx, error: 1 });
}
function PO(id, english) {
return english;
}
global.handle_request = function(env) {
let mac;
let form_data = {};
let query_string = {};
let post_data = '';
let ctx = { env, header, footer, mac, form_data, post_data, query_string, config, PO };
// lookup the peers MAC
let macs = rtnl.request(rtnl.const.RTM_GETNEIGH, rtnl.const.NLM_F_DUMP, { });
for (let m in macs)
if (m.dst == env.REMOTE_HOST)
ctx.mac = replace(m.lladdr, ':', '-');
// if the MAC lookup failed, go to the error page
if (!ctx.mac)
include('error.uc', ctx);
// check if a client is already connected
let connected = fs_open('ubus call spotfilter client_get \'{"interface": "hotspot", "address": "' + ctx.mac + '"}\'');
if (connected?.state) {
include('connected.uc', ctx);
return;
}
// split QUERY_STRING
if (env.QUERY_STRING)
for (let chunk in split(env.QUERY_STRING, '&')) {
let var = split(chunk, '=');
if (length(var) != 2)
continue;
ctx.query_string[var[0]] = var[1];
}
// recv POST data
if (env.CONTENT_LENGTH > 0)
for (let chunk = uhttpd.recv(64); chunk != null; chunk = uhttpd.recv(64))
post_data += replace(chunk, /[^[:graph:]]/g, '.');
// split POST data into an array
if (post_data)
for (let chunk in split(post_data, '&')) {
let var = split(chunk, '=');
if (length(var) != 2)
continue;
ctx.form_data[var[0]] = var[1];
}
switch (ctx.form_data.action) {
case 'credentials':
request_credentials(ctx);
return;
case 'radius':
request_radius(ctx);
return;
case 'click':
request_click(ctx);
return;
default:
request_start(ctx);
return;
}
};
%}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/images/splash.jpg" type="image/x-icon">
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/css/captive-portal.css">
<title>uCentral - Captive Portal</title>
</head>
<body>
<div id="root">
<div class="portal flex-column align-items-center">
<div class="container">
<div class="justify-content-center row">
<div class="col-md-8">
<img class="c-sidebar-brand-full" src="/images/OpenWiFi_LogoLockup_DarkGreyColour.svg" alt="OpenWifi" style="padding-left: 17%; width: 85%;">
<div class="card">
<div class="card-header">uCentral - Captive Portal</div>
<div class="card-body">

View File

@@ -0,0 +1,18 @@
Status: 200 OK
Content-Type: text/html
{{ header }}
{% if (error): %}
<h1> Invalid credentials </h1>
{% endif %}
<form action="/hotspot" method="POST">
<label for="fname">Username:</label>
<input type="text" name="username"><br>
<label for="fname">Password:</label>
<input type="password" name="password">
<input type="hidden" name="action" value="radius">
<input type="submit" value="Login" class="btn btn-primary btn-block">
</form>
{{ footer }}

View File

@@ -0,0 +1,5 @@
Status: 302 Found
Location: {{ uam_location }}
Content-Type: text/html

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="refresh" content="0; URL=http://192.168.178.22/hotspot" />
</head>
<body style="background-color: white">
<a style="color: black; font-family: arial, helvetica, sans-serif;" href="http://192.168.178.22/hotspot">HotSpot Login</a>
</body>
</html>

View File

@@ -0,0 +1,42 @@
let uci = require('uci').cursor();
let config = uci.get_all('uspot');
let fs = require('fs');
let stdout = fs.popen('env');
let raw = stdout.read('all');
let lines = split(raw, '\n');
let env = {};
for (let line in lines) {
let e = split(line, '=');
if (length(e) == 2)
env[e[0]] = e[1];
}
let location;
switch(config.config.auth_mode) {
case 'uam':
location = config.uam.server +
'?res=notyet' +
'&uamip=' + env.SERVER_ADDR +
'&uamport=' + config.uam.port +
//'&challenge=' +
//'&mac=' +
'&called=' + config.uam.nasmac +
'&nasid=' + config.uam.nasid +
// '&sessionid=' + +
// userurl
// md=
"";
break;
default:
location = 'http://' + env.SERVER_ADDR + '/hotspot';
}
system('logger "' + location + '"');
printf('Status: 302 Found
Location: %s
Content-Type: text/html
', location);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
.portal {
display: flex;
flex-direction: row;
min-height: 100vh;
background-color: #ebedef;
}
.align-items-center {
align-items: center!important;
}
.flex-row {
flex-direction: row!important;
}
.justify-content-center {
justify-content: center!important;
}

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 141.5 185.6" style="enable-background:new 0 0 141.5 185.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#414141;}
.st2{fill:#FED206;}
.st3{fill:#EB6F53;}
.st4{fill:#3BA9B6;}
</style>
<g>
<g>
<path class="st0" d="M120.7,183.9H21.5c-10.8,0-19.5-8.7-19.5-19.5V20.5c0-10.8,8.7-19.5,19.5-19.5h99.2
c10.8,0,19.5,8.7,19.5,19.5v143.9C140.2,175.2,131.5,183.9,120.7,183.9z"/>
<g>
<g>
<g>
<path class="st1" d="M46.3,166.2v-3.4h-1.2v-0.6h3.1v0.6H47v3.4H46.3z"/>
</g>
<g>
<path class="st1" d="M49,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H49z"/>
</g>
<g>
<path class="st1" d="M52.6,166.2v-4h0.7v3.4h1.8v0.6H52.6z"/>
</g>
<g>
<path class="st1" d="M55.7,166.2v-4h2.7v0.6h-2v1h2v0.6h-2v1.1h2v0.6H55.7z"/>
</g>
<g>
<path class="st1" d="M59.1,164.2c0-1.2,0.9-2.1,2.1-2.1c0.8,0,1.3,0.4,1.6,0.9l-0.6,0.3c-0.2-0.3-0.6-0.6-1-0.6
c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.4,0,0.8-0.3,1-0.6l0.6,0.3c-0.3,0.5-0.8,0.9-1.6,0.9
C60,166.3,59.1,165.5,59.1,164.2z"/>
</g>
<g>
<path class="st1" d="M63.2,164.2c0-1.2,0.8-2.1,2-2.1c1.2,0,2,0.9,2,2.1c0,1.2-0.8,2.1-2,2.1C64,166.3,63.2,165.4,63.2,164.2z
M66.5,164.2c0-0.8-0.5-1.4-1.3-1.4c-0.8,0-1.3,0.6-1.3,1.4c0,0.8,0.5,1.4,1.3,1.4C66,165.7,66.5,165,66.5,164.2z"/>
</g>
<g>
<path class="st1" d="M71.3,166.2v-3.1l-1.2,3.1h-0.3l-1.2-3.1v3.1h-0.7v-4h1l1.1,2.7l1.1-2.7h1v4H71.3z"/>
</g>
<g>
<path class="st1" d="M75.7,166.2v-4h0.7v4H75.7z"/>
</g>
<g>
<path class="st1" d="M80.4,166.2l-2.1-2.8v2.8h-0.7v-4h0.7l2,2.8v-2.8h0.7v4H80.4z"/>
</g>
<g>
<path class="st1" d="M82.3,166.2v-4H85v0.6h-2v1h2v0.6h-2v1.7H82.3z"/>
</g>
<g>
<path class="st1" d="M87.9,166.2l-0.9-1.5h-0.7v1.5h-0.7v-4h1.7c0.8,0,1.3,0.5,1.3,1.2c0,0.7-0.5,1.1-0.9,1.2l1,1.6H87.9z
M88,163.5c0-0.4-0.3-0.6-0.7-0.6h-1v1.3h1C87.7,164.1,88,163.9,88,163.5z"/>
</g>
<g>
<path class="st1" d="M92.4,166.2l-0.3-0.8h-1.8l-0.3,0.8h-0.8l1.6-4h0.9l1.6,4H92.4z M91.2,162.9l-0.7,1.9h1.4L91.2,162.9z"/>
</g>
<g>
<path class="st1" d="M95.8,166.2v-4h1.5c0.8,0,1.2,0.5,1.2,1.2c0,0.6-0.4,1.2-1.2,1.2h-1.2v1.7H95.8z M98.2,163.4
c0-0.5-0.3-0.9-0.9-0.9h-1.1v1.7h1.1C97.8,164.3,98.2,163.9,98.2,163.4z"/>
</g>
<g>
<path class="st1" d="M101.5,166.2l-1.1-1.6h-0.9v1.6h-0.3v-4h1.5c0.7,0,1.2,0.4,1.2,1.2c0,0.7-0.5,1.1-1.1,1.1l1.2,1.7H101.5z
M101.6,163.4c0-0.5-0.4-0.9-0.9-0.9h-1.1v1.7h1.1C101.2,164.3,101.6,163.9,101.6,163.4z"/>
</g>
<g>
<path class="st1" d="M102.8,164.2c0-1.2,0.8-2.1,1.9-2.1c1.2,0,1.9,0.9,1.9,2.1c0,1.2-0.8,2.1-1.9,2.1
C103.6,166.3,102.8,165.4,102.8,164.2z M106.3,164.2c0-1-0.6-1.7-1.6-1.7c-1,0-1.6,0.7-1.6,1.7c0,1,0.6,1.7,1.6,1.7
C105.7,166,106.3,165.2,106.3,164.2z"/>
</g>
<g>
<path class="st1" d="M106.9,165.8l0.2-0.3c0.2,0.2,0.4,0.4,0.8,0.4c0.5,0,0.9-0.4,0.9-0.9v-2.8h0.3v2.8c0,0.8-0.5,1.2-1.2,1.2
C107.5,166.3,107.2,166.1,106.9,165.8z"/>
</g>
<g>
<path class="st1" d="M110.4,166.2v-4h2.5v0.3h-2.2v1.5h2.1v0.3h-2.1v1.6h2.2v0.3H110.4z"/>
</g>
<g>
<path class="st1" d="M113.5,164.2c0-1.2,0.9-2.1,2-2.1c0.6,0,1.1,0.3,1.5,0.7l-0.3,0.2c-0.3-0.3-0.7-0.6-1.2-0.6
c-0.9,0-1.7,0.7-1.7,1.7c0,1,0.7,1.7,1.7,1.7c0.5,0,0.9-0.2,1.2-0.6l0.3,0.2c-0.4,0.4-0.8,0.7-1.5,0.7
C114.4,166.3,113.5,165.5,113.5,164.2z"/>
</g>
<g>
<path class="st1" d="M118.7,166.2v-3.7h-1.3v-0.3h2.9v0.3H119v3.7H118.7z"/>
</g>
</g>
<g>
<polygon class="st1" points="26.3,163.8 31.6,158.5 36.9,163.8 37.7,163.8 31.6,157.6 25.5,163.8 "/>
<polygon class="st1" points="36.9,164.7 31.6,170 26.3,164.7 25.5,164.7 31.6,170.8 37.7,164.7 "/>
<polygon class="st1" points="31,163.8 36.3,158.5 41.6,163.8 42.5,163.8 36.3,157.6 30.2,163.8 "/>
<polygon class="st1" points="41.6,164.7 36.3,170 31,164.7 30.2,164.7 36.3,170.8 42.5,164.7 "/>
</g>
</g>
<g>
<path class="st1" d="M33.2,100.7c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3S37.8,100.7,33.2,100.7z"/>
</g>
<g>
<g>
<g>
<path class="st2" d="M33.2,35.2c40.7,0,73.8,33.1,73.8,73.8c0,0.7,0,1.4,0,2.1c0,1.7,0.6,3.3,1.7,4.6c1.2,1.2,2.8,1.9,4.5,2
l0.2,0c3.5,0,6.3-2.7,6.4-6.2c0-0.8,0-1.7,0-2.5c0-47.7-38.8-86.6-86.6-86.6c-0.8,0-1.7,0-2.5,0c-1.7,0-3.3,0.8-4.5,2
c-1.2,1.2-1.8,2.9-1.7,4.6c0.1,3.5,3,6.3,6.6,6.2C31.8,35.2,32.5,35.2,33.2,35.2z"/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st3" d="M33.2,60.5c26.7,0,48.5,21.7,48.5,48.5c0,0.6,0,1.3,0,2c-0.1,1.7,0.5,3.3,1.7,4.6c1.2,1.3,2.7,2,4.4,2.1
c1.7,0.1,3.3-0.5,4.6-1.7c1.2-1.2,2-2.7,2-4.4c0-0.9,0.1-1.8,0.1-2.6c0-33.8-27.5-61.2-61.2-61.2c-0.8,0-1.6,0-2.6,0.1
c-1.7,0.1-3.3,0.8-4.4,2.1c-1.2,1.3-1.8,2.9-1.7,4.6s0.8,3.3,2.1,4.4c1.3,1.2,2.9,1.8,4.6,1.7C31.9,60.5,32.6,60.5,33.2,60.5z"
/>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st4" d="M33.2,86.7c12.3,0,22.3,10,22.3,22.3c0,0.5,0,1.1-0.1,1.8c-0.3,3.5,2.3,6.6,5.8,6.9
c3.5,0.3,6.6-2.3,6.9-5.8c0.1-1,0.1-1.9,0.1-2.8c0-19.3-15.7-35.1-35.1-35.1c-0.9,0-1.8,0-2.8,0.1c-1.7,0.1-3.2,0.9-4.3,2.2
c-1.1,1.3-1.6,2.9-1.5,4.6c0.1,1.7,0.9,3.2,2.2,4.3c1.3,1.1,2.9,1.6,4.6,1.5C32.1,86.7,32.7,86.7,33.2,86.7z"/>
</g>
</g>
</g>
</g>
<g>
<path class="st1" d="M35.8,130.4c1.1,0.6,2.1,1.5,2.7,2.6c0.7,1.1,1,2.3,1,3.7s-0.3,2.6-1,3.7c-0.7,1.1-1.6,2-2.7,2.6
c-1.1,0.6-2.4,1-3.8,1s-2.7-0.3-3.8-1c-1.1-0.6-2.1-1.5-2.7-2.6c-0.7-1.1-1-2.3-1-3.7c0-1.3,0.3-2.6,1-3.7c0.7-1.1,1.6-2,2.7-2.6
c1.1-0.6,2.4-0.9,3.8-0.9C33.4,129.5,34.7,129.8,35.8,130.4z M29.9,132.9c-0.7,0.4-1.2,0.9-1.6,1.6s-0.6,1.4-0.6,2.2
c0,0.8,0.2,1.6,0.6,2.3c0.4,0.7,0.9,1.2,1.6,1.6c0.7,0.4,1.4,0.6,2.1,0.6c0.8,0,1.5-0.2,2.1-0.6c0.6-0.4,1.2-0.9,1.5-1.6
c0.4-0.7,0.6-1.4,0.6-2.3c0-0.8-0.2-1.6-0.6-2.2s-0.9-1.2-1.5-1.6c-0.6-0.4-1.4-0.6-2.1-0.6C31.3,132.3,30.6,132.5,29.9,132.9z"/>
<path class="st1" d="M50.6,133.6c0.8,0.5,1.4,1.1,1.8,2c0.4,0.8,0.6,1.8,0.6,2.9c0,1.1-0.2,2-0.6,2.8c-0.4,0.8-1,1.5-1.8,1.9
c-0.8,0.5-1.6,0.7-2.6,0.7c-0.7,0-1.4-0.1-2-0.4s-1.1-0.7-1.5-1.2v5.4h-3.1V133h3.1v1.6c0.4-0.5,0.9-1,1.4-1.2s1.2-0.4,2-0.4
C48.9,132.9,49.8,133.1,50.6,133.6z M49.1,140.5c0.5-0.6,0.7-1.3,0.7-2.2c0-0.9-0.2-1.6-0.7-2.1c-0.5-0.6-1.1-0.8-1.9-0.8
s-1.4,0.3-1.9,0.8c-0.5,0.6-0.8,1.3-0.8,2.1c0,0.9,0.2,1.6,0.8,2.2s1.1,0.8,1.9,0.8S48.6,141,49.1,140.5z"/>
<path class="st1" d="M63.4,134.4c0.9,1,1.4,2.4,1.4,4.2c0,0.3,0,0.6,0,0.7H57c0.2,0.7,0.5,1.2,1,1.6c0.5,0.4,1.1,0.6,1.8,0.6
c0.5,0,1-0.1,1.5-0.3s0.9-0.5,1.3-0.9l1.6,1.6c-0.5,0.6-1.2,1.1-2,1.4c-0.8,0.3-1.6,0.5-2.6,0.5c-1.1,0-2.1-0.2-3-0.7
s-1.5-1.1-2-1.9c-0.5-0.8-0.7-1.8-0.7-2.9c0-1.1,0.2-2.1,0.7-2.9s1.1-1.5,2-1.9c0.8-0.5,1.8-0.7,2.9-0.7
C61.2,132.9,62.5,133.4,63.4,134.4z M61.8,137.5c0-0.7-0.3-1.3-0.7-1.7s-1-0.6-1.7-0.6c-0.7,0-1.2,0.2-1.7,0.6
c-0.4,0.4-0.7,1-0.9,1.7H61.8z"/>
<path class="st1" d="M76.2,134c0.7,0.7,1.1,1.7,1.1,3v6.8h-3.1v-5.9c0-0.7-0.2-1.2-0.6-1.6s-0.9-0.6-1.5-0.6
c-0.8,0-1.4,0.3-1.8,0.8c-0.4,0.5-0.7,1.2-0.7,2v5.3h-3.1V133h3.1v1.9c0.7-1.3,2-2,3.7-2C74.6,132.8,75.5,133.2,76.2,134z"/>
<path class="st1" d="M96,129.7h3.3l-4.7,14h-3.3l-2.9-10.1l-3,10.1h-3.2l-4.7-14h3.4l3,10.7l3-10.7H90l3.1,10.7L96,129.7z"/>
<path class="st1" d="M103.3,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C102.6,128.2,103,128.3,103.3,128.7z M100.6,133h3.1
v10.8h-3.1V133z"/>
<path class="st1" d="M106.5,129.7h10.1l0,2.6h-6.9v3.4h6.3v2.6h-6.3v5.3h-3.2V129.7z"/>
<path class="st1" d="M120.9,128.7c0.3,0.3,0.5,0.7,0.5,1.2s-0.2,0.9-0.5,1.2c-0.3,0.3-0.7,0.5-1.2,0.5c-0.5,0-0.9-0.2-1.2-0.5
c-0.3-0.3-0.5-0.7-0.5-1.2c0-0.5,0.2-0.9,0.5-1.2c0.3-0.3,0.7-0.5,1.2-0.5C120.1,128.2,120.5,128.3,120.9,128.7z M118.1,133h3.1
v10.8h-3.1V133z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 251.2 114.2" style="enable-background:new 0 0 251.2 114.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FED206;}
.st1{fill:#EB6F53;}
.st2{fill:#3BA9B6;}
.st3{fill:#414141;}
</style>
<g>
<path class="st0" d="M219.6,43.3C219.5,43.3,219.5,43.3,219.6,43.3c-1.3,0-2.2-1-2.2-2.2c0-0.2,0-0.4,0-0.6
c0-11.9-9.7-21.6-21.6-21.6c-0.2,0-0.4,0-0.6,0c-1.2,0-2.2-0.9-2.2-2.1c0-1.2,0.9-2.2,2.1-2.2c0.2,0,0.5,0,0.7,0
c14.3,0,25.9,11.6,25.9,25.9c0,0.2,0,0.5,0,0.7C221.7,42.4,220.7,43.3,219.6,43.3z"/>
<path class="st1" d="M212.1,43.3C212,43.3,212,43.3,212.1,43.3c-1.3-0.1-2.2-1.1-2.2-2.3c0-0.2,0-0.4,0-0.6
c0-7.7-6.3-14.1-14.1-14.1c-0.2,0-0.4,0-0.6,0c-1.2,0.1-2.2-0.9-2.3-2.1c0-1.2,0.9-2.2,2.1-2.3c0.3,0,0.5,0,0.8,0
c10.2,0,18.4,8.3,18.4,18.4c0,0.2,0,0.5,0,0.8C214.2,42.4,213.2,43.3,212.1,43.3z"/>
<path class="st2" d="M204.3,43.3c-0.1,0-0.1,0-0.2,0c-1.2-0.1-2.1-1.1-2-2.3c0-0.2,0-0.4,0-0.5c0-3.5-2.8-6.3-6.3-6.3
c-0.1,0-0.3,0-0.5,0c-1.2,0.1-2.3-0.8-2.3-2c-0.1-1.2,0.8-2.3,2-2.3c0.3,0,0.6,0,0.9,0c5.9,0,10.7,4.8,10.7,10.7c0,0.3,0,0.5,0,0.9
C206.4,42.4,205.4,43.3,204.3,43.3z"/>
<g>
<g>
<g>
<path class="st3" d="M61.9,89.9v-4.7h-1.7v-0.9h4.4v0.9h-1.7v4.7H61.9z"/>
</g>
<g>
<path class="st3" d="M65.6,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H65.6z"/>
</g>
<g>
<path class="st3" d="M70.7,89.9v-5.6h1V89h2.5v0.9H70.7z"/>
</g>
<g>
<path class="st3" d="M74.9,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H74.9z"/>
</g>
<g>
<path class="st3" d="M79.8,87.1c0-1.7,1.3-2.9,2.9-2.9c1.1,0,1.8,0.6,2.2,1.3l-0.8,0.4c-0.3-0.5-0.8-0.8-1.4-0.8
c-1.1,0-1.9,0.8-1.9,2c0,1.2,0.8,2,1.9,2c0.6,0,1.1-0.4,1.4-0.8l0.8,0.4c-0.4,0.7-1.1,1.3-2.2,1.3C81.1,90,79.8,88.8,79.8,87.1z
"/>
</g>
<g>
<path class="st3" d="M85.5,87.1c0-1.7,1.2-2.9,2.9-2.9c1.7,0,2.9,1.2,2.9,2.9S90,90,88.3,90C86.7,90,85.5,88.8,85.5,87.1z
M90.2,87.1c0-1.2-0.7-2-1.9-2c-1.1,0-1.9,0.9-1.9,2c0,1.1,0.7,2,1.9,2C89.5,89.1,90.2,88.3,90.2,87.1z"/>
</g>
<g>
<path class="st3" d="M96.9,89.9v-4.3l-1.7,4.3h-0.4l-1.7-4.3v4.3h-1v-5.6h1.4l1.5,3.8l1.5-3.8h1.4v5.6H96.9z"/>
</g>
<g>
<path class="st3" d="M103,89.9v-5.6h1v5.6H103z"/>
</g>
<g>
<path class="st3" d="M109.7,89.9l-2.9-4v4h-1v-5.6h1l2.9,3.9v-3.9h1v5.6H109.7z"/>
</g>
<g>
<path class="st3" d="M112.4,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8v2.4H112.4z"/>
</g>
<g>
<path class="st3" d="M120.3,89.9l-1.2-2.1h-1v2.1h-1v-5.6h2.5c1.1,0,1.8,0.7,1.8,1.8c0,1-0.7,1.5-1.3,1.6l1.4,2.2H120.3z
M120.4,86.1c0-0.5-0.4-0.9-1-0.9h-1.4V87h1.4C120,87,120.4,86.6,120.4,86.1z"/>
</g>
<g>
<path class="st3" d="M126.6,89.9l-0.4-1.1h-2.6l-0.4,1.1h-1.1l2.2-5.6h1.2l2.2,5.6H126.6z M124.9,85.3l-1,2.7h2L124.9,85.3z"/>
</g>
<g>
<path class="st3" d="M131.4,89.9v-5.6h2.1c1.1,0,1.7,0.8,1.7,1.6c0,0.9-0.6,1.6-1.7,1.6h-1.6v2.3H131.4z M134.7,86
c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C134.2,87.2,134.7,86.6,134.7,86z"/>
</g>
<g>
<path class="st3" d="M139.4,89.9l-1.6-2.3h-1.2v2.3h-0.5v-5.6h2.1c1,0,1.7,0.6,1.7,1.6c0,1-0.7,1.6-1.6,1.6l1.6,2.3H139.4z
M139.4,86c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C138.9,87.2,139.4,86.7,139.4,86z"/>
</g>
<g>
<path class="st3" d="M141.2,87.1c0-1.6,1.1-2.9,2.7-2.9c1.6,0,2.7,1.3,2.7,2.9c0,1.6-1.1,2.9-2.7,2.9
C142.3,90,141.2,88.8,141.2,87.1z M146.1,87.1c0-1.4-0.9-2.5-2.2-2.5c-1.4,0-2.2,1-2.2,2.5c0,1.4,0.9,2.5,2.2,2.5
C145.2,89.6,146.1,88.5,146.1,87.1z"/>
</g>
<g>
<path class="st3" d="M147,89.3l0.3-0.4c0.3,0.3,0.6,0.6,1.1,0.6c0.8,0,1.2-0.5,1.2-1.3v-4h0.5v4c0,1.2-0.8,1.7-1.7,1.7
C147.9,90,147.4,89.8,147,89.3z"/>
</g>
<g>
<path class="st3" d="M151.8,89.9v-5.6h3.5v0.4h-3.1v2.1h3v0.4h-3v2.2h3.1v0.4H151.8z"/>
</g>
<g>
<path class="st3" d="M156.3,87.1c0-1.7,1.3-2.9,2.8-2.9c0.9,0,1.6,0.4,2,1l-0.4,0.3c-0.4-0.5-1-0.8-1.6-0.8
c-1.3,0-2.3,1-2.3,2.5c0,1.4,1,2.5,2.3,2.5c0.7,0,1.3-0.3,1.6-0.8l0.4,0.3c-0.5,0.6-1.2,1-2,1C157.5,90,156.3,88.8,156.3,87.1z"
/>
</g>
<g>
<path class="st3" d="M163.5,89.9v-5.2h-1.8v-0.4h4.1v0.4H164v5.2H163.5z"/>
</g>
</g>
<g>
<polygon class="st3" points="33.7,86.5 41.2,79 48.6,86.5 49.8,86.5 41.2,77.9 32.6,86.5 "/>
<polygon class="st3" points="48.6,87.8 41.2,95.2 33.7,87.8 32.6,87.8 41.2,96.4 49.8,87.8 "/>
<polygon class="st3" points="40.3,86.5 47.8,79 55.3,86.5 56.4,86.5 47.8,77.9 39.2,86.5 "/>
<polygon class="st3" points="55.3,87.8 47.8,95.2 40.3,87.8 39.2,87.8 47.8,96.4 56.4,87.8 "/>
</g>
</g>
</g>
<g>
<path class="st3" d="M51.2,41.3c2,1.1,3.6,2.6,4.7,4.5c1.1,1.9,1.7,4,1.7,6.4c0,2.3-0.6,4.5-1.7,6.4c-1.1,1.9-2.7,3.4-4.7,4.6
c-2,1.1-4.2,1.7-6.6,1.7c-2.4,0-4.6-0.6-6.6-1.7c-2-1.1-3.6-2.6-4.7-4.6c-1.1-1.9-1.7-4.1-1.7-6.4c0-2.3,0.6-4.5,1.7-6.4
c1.1-1.9,2.7-3.4,4.7-4.5c2-1.1,4.2-1.6,6.6-1.6C47,39.6,49.2,40.2,51.2,41.3z M40.5,44.9c-1.3,0.7-2.3,1.7-3,3
c-0.7,1.3-1.1,2.7-1.1,4.2s0.4,3,1.1,4.2c0.8,1.3,1.8,2.3,3,3c1.3,0.7,2.7,1.1,4.1,1.1c1.5,0,2.8-0.4,4.1-1.1c1.3-0.7,2.3-1.8,3-3
c0.7-1.3,1.1-2.7,1.1-4.2s-0.4-2.9-1.1-4.2c-0.7-1.3-1.7-2.3-3-3c-1.3-0.7-2.6-1.1-4.1-1.1C43.2,43.8,41.8,44.2,40.5,44.9z"/>
<path class="st3" d="M76.9,46.8c1.3,0.8,2.4,1.9,3.1,3.4c0.7,1.4,1.1,3.1,1.1,5c0,1.9-0.4,3.5-1.1,4.9c-0.7,1.4-1.8,2.5-3.1,3.3
c-1.3,0.8-2.9,1.2-4.6,1.2c-1.4,0-2.6-0.3-3.7-0.8c-1.1-0.5-2-1.3-2.7-2.4v9.8h-4.6V45.7H66v3.1c0.7-1.1,1.5-1.8,2.6-2.4
c1.1-0.5,2.3-0.8,3.7-0.8C74,45.6,75.6,46,76.9,46.8z M75.1,59.1c1-1.1,1.5-2.4,1.5-4.1c0-1.7-0.5-3-1.5-4.1
c-1-1.1-2.2-1.6-3.8-1.6c-1.6,0-2.8,0.5-3.8,1.6c-1,1-1.5,2.4-1.5,4.1c0,1.7,0.5,3,1.5,4.1c1,1.1,2.3,1.6,3.8,1.6
C72.8,60.7,74.1,60.2,75.1,59.1z"/>
<path class="st3" d="M99.3,48.1c1.5,1.7,2.3,4.1,2.3,7.2c0,0.6,0,1.1,0,1.4H87.7c0.3,1.3,0.9,2.4,1.9,3.1c0.9,0.8,2.1,1.1,3.5,1.1
c1,0,1.9-0.2,2.7-0.5c0.9-0.4,1.6-0.9,2.3-1.6l2.5,2.6c-0.9,1-2.1,1.8-3.4,2.4c-1.3,0.6-2.8,0.8-4.5,0.8c-1.9,0-3.6-0.4-5.1-1.2
c-1.5-0.8-2.6-1.9-3.4-3.3c-0.8-1.4-1.2-3.1-1.2-5c0-1.9,0.4-3.5,1.2-5c0.8-1.4,1.9-2.6,3.4-3.4c1.4-0.8,3.1-1.2,4.9-1.2
C95.5,45.6,97.8,46.4,99.3,48.1z M97.4,53.6c0-1.4-0.5-2.5-1.4-3.3c-0.9-0.8-2-1.2-3.4-1.2c-1.3,0-2.4,0.4-3.3,1.2
c-0.9,0.8-1.5,1.9-1.7,3.3H97.4z"/>
<path class="st3" d="M121.5,47.5c1.2,1.3,1.9,3.1,1.9,5.3v11.7h-4.6V54.1c0-1.3-0.4-2.3-1.1-3.1c-0.7-0.8-1.8-1.1-3-1.1
c-1.5,0-2.7,0.5-3.6,1.5s-1.3,2.3-1.3,3.8v9.2h-4.5V45.7h4.5v3.5c1.3-2.4,3.5-3.6,6.7-3.7C118.5,45.5,120.2,46.2,121.5,47.5z"/>
<path class="st3" d="M156.5,39.9h4.9l-8.3,24.5h-4.9l-5.6-18.6l-5.7,18.6h-4.8l-8.3-24.5h5l5.8,19.4l5.7-19.4h4.6l5.8,19.5
L156.5,39.9z"/>
<path class="st3" d="M168,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C166.9,37.7,167.6,37.9,168,38.4z M164,45.7h4.5v18.7H164
V45.7z"/>
<path class="st3" d="M174,39.9h16.9l0,4.1h-12.2v6.6h11.1v4.1h-11.1v9.7H174V39.9z"/>
<path class="st3" d="M197.9,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C196.8,37.7,197.4,37.9,197.9,38.4z M193.8,45.7h4.5v18.7
h-4.5V45.7z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="refresh" content="0; URL=http://192.168.178.22/hotspot" />
</head>
<body style="background-color: white">
<a style="color: black; font-family: arial, helvetica, sans-serif;" href="http://192.168.178.22/hotspot">HotSpot Login</a>
</body>
</html>

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(radius-client C)
INCLUDE(GNUInstallDirs)
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
ADD_EXECUTABLE(radius-client radius.c)
TARGET_LINK_LIBRARIES(radius-client radcli ubox blobmsg_json)
INSTALL(TARGETS radius-client
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)
ADD_LIBRARY(uam SHARED uam.c)
TARGET_LINK_LIBRARIES(uam ubox)
INSTALL(TARGETS uam LIBRARY DESTINATION lib)

View File

@@ -0,0 +1,305 @@
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <radcli/radcli.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
enum {
RADIUS_TYPE,
RADIUS_SERVER,
RADIUS_USERNAME,
RADIUS_PASSWORD,
RADIUS_ACCT_SESSION,
RADIUS_CLIENT_IP,
RADIUS_CALLED_STATION,
RADIUS_CALLING_STATION,
RADIUS_NAS_IP,
RADIUS_NAS_ID,
__RADIUS_MAX,
};
static const struct blobmsg_policy radius_policy[__RADIUS_MAX] = {
[RADIUS_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
[RADIUS_SERVER] = { .name = "server", .type = BLOBMSG_TYPE_STRING },
[RADIUS_USERNAME] = { .name = "username", .type = BLOBMSG_TYPE_STRING },
[RADIUS_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING },
[RADIUS_ACCT_SESSION] = { .name = "acct_session", .type = BLOBMSG_TYPE_STRING },
[RADIUS_CLIENT_IP] = { .name = "client_ip", .type = BLOBMSG_TYPE_STRING },
[RADIUS_CALLED_STATION] = { .name = "called_station", .type = BLOBMSG_TYPE_STRING },
[RADIUS_CALLING_STATION] = { .name = "calling_station", .type = BLOBMSG_TYPE_STRING },
[RADIUS_NAS_IP] = { .name = "nas_ip", .type = BLOBMSG_TYPE_STRING },
[RADIUS_NAS_ID] = { .name = "nas_id", .type = BLOBMSG_TYPE_STRING },
};
static struct config {
char *type;
char *server;
char *username;
char *password;
char *acct_session;
struct sockaddr_in client_ip;
char *called_station;
char *calling_station;
struct sockaddr_in nas_ip;
char *nas_id;
} config;
static struct blob_buf b = {};
static struct blob_attr *tb[__RADIUS_MAX] = {};
static int
result(rc_handle const *rh, int accept, VALUE_PAIR *pair)
{
struct blob_buf b = {};
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "access-accept", accept);
if (pair) {
void *c = blobmsg_open_table(&b, "reply");
char name[33], value[256];
VALUE_PAIR *vp;
for (vp = pair; vp != NULL; vp = vp->next) {
if (rc_avpair_tostr(rh, vp, name, sizeof(name), value,
sizeof(value)) == -1)
break;
blobmsg_add_string(&b, name, value);
}
blobmsg_close_table(&b, c);
}
printf("%s", blobmsg_format_json(b.head, true));
return accept;
}
static void
config_load(void)
{
if (tb[RADIUS_TYPE])
config.type = blobmsg_get_string(tb[RADIUS_TYPE]);
if (tb[RADIUS_SERVER])
config.server = blobmsg_get_string(tb[RADIUS_SERVER]);
if (tb[RADIUS_USERNAME])
config.username = blobmsg_get_string(tb[RADIUS_USERNAME]);
if (tb[RADIUS_PASSWORD])
config.password = blobmsg_get_string(tb[RADIUS_PASSWORD]);
if (tb[RADIUS_ACCT_SESSION])
config.acct_session = blobmsg_get_string(tb[RADIUS_ACCT_SESSION]);
if (tb[RADIUS_CLIENT_IP]) {
inet_pton(AF_INET, blobmsg_get_string(tb[RADIUS_CLIENT_IP]), &(config.client_ip.sin_addr));
config.client_ip.sin_addr.s_addr = ntohl(config.client_ip.sin_addr.s_addr);
}
if (tb[RADIUS_CALLED_STATION])
config.called_station = blobmsg_get_string(tb[RADIUS_CALLED_STATION]);
if (tb[RADIUS_CALLING_STATION])
config.calling_station = blobmsg_get_string(tb[RADIUS_CALLING_STATION]);
if (tb[RADIUS_NAS_IP]) {
inet_pton(AF_INET, blobmsg_get_string(tb[RADIUS_NAS_IP]), &(config.nas_ip.sin_addr));
config.nas_ip.sin_addr.s_addr = ntohl(config.nas_ip.sin_addr.s_addr);
}
if (tb[RADIUS_NAS_ID])
config.nas_id = blobmsg_get_string(tb[RADIUS_NAS_ID]);
}
static rc_handle *
radius_init(void)
{
rc_handle *rh = rc_new();
if (rh == NULL)
return NULL;
rh = rc_config_init(rh);
if (rh == NULL)
return NULL;
rc_add_config(rh, "authserver", config.server, "code", __LINE__);
rc_add_config(rh, "servers", "/tmp/radius.servers", "code", __LINE__);
rc_add_config(rh, "dictionary", "/etc/radcli/dictionary", "code", __LINE__);
rc_add_config(rh, "radius_timeout", "5", "code", __LINE__);
rc_add_config(rh, "radius_retries", "1", "code", __LINE__);
rc_add_config(rh, "bindaddr", "*", "code", __LINE__);
if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0)
return NULL;
return rh;
}
static int
auth(void)
{
VALUE_PAIR *send = NULL, *received;
rc_handle *rh = NULL;
if (!config.server || !config.username || !config.password)
return result(NULL, 0, NULL);
rh = radius_init();
if (!rh)
return result(NULL, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_NAME, config.username, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_PASSWORD, config.password, -1, 0) == NULL)
return result(rh, 0, NULL);
rc_apply_config(rh);
if (rc_auth(rh, 0, send, &received, NULL) == OK_RC)
return result(rh, 1, received);
return result(rh, 0, NULL);
}
static int
uam_auth(void)
{
VALUE_PAIR *send = NULL, *received;
rc_handle *rh = NULL;
if (!config.server || !config.username || !config.password ||
!config.acct_session || !config.called_station ||
!config.calling_station || !config.nas_id)
return result(NULL, 0, NULL);
rh = radius_init();
if (!rh)
return result(NULL, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_NAME, config.username, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_PASSWORD, config.password, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_ACCT_SESSION_ID, config.acct_session, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_FRAMED_IP_ADDRESS, &config.client_ip.sin_addr, 4, 0) == NULL)
return result(rh, 0, NULL);
//if (rc_avpair_add(rh, &send, PW_NAS_PORT_TYPE, , -1, 0) == NULL)
// return result(rh, 0, NULL);
//if (rc_avpair_add(rh, &send, PW_NAS_PORT, , -1, 0) == NULL)
// return result(rh, 0, NULL);
// if (rc_avpair_add(rh, &send, PW_NAS_PORT_ID_STRING, , -1, 0) == NULL)
// return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_CALLED_STATION_ID, config.called_station, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_CALLING_STATION_ID, config.calling_station, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_NAS_IP_ADDRESS, &config.nas_ip.sin_addr, 4, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_NAS_IDENTIFIER, config.nas_id, -1, 0) == NULL)
return result(rh, 0, NULL);
rc_apply_config(rh);
if (rc_auth(rh, 0, send, &received, NULL) == OK_RC)
return result(rh, 1, received);
return result(rh, 0, NULL);
}
static int
uam_acct(void)
{
VALUE_PAIR *send = NULL, *received;
rc_handle *rh = NULL;
if (!config.server || !config.username || !config.password ||
!config.acct_session || !config.called_station ||
!config.calling_station || !config.nas_id)
return result(NULL, 0, NULL);
rh = radius_init();
if (!rh)
return result(NULL, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_NAME, config.username, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_USER_PASSWORD, config.password, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_ACCT_SESSION_ID, config.acct_session, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_FRAMED_IP_ADDRESS, &config.client_ip.sin_addr, 4, 0) == NULL)
return result(rh, 0, NULL);
//if (rc_avpair_add(rh, &send, PW_NAS_PORT_TYPE, , -1, 0) == NULL)
// return result(rh, 0, NULL);
//if (rc_avpair_add(rh, &send, PW_NAS_PORT, , -1, 0) == NULL)
// return result(rh, 0, NULL);
// if (rc_avpair_add(rh, &send, PW_NAS_PORT_ID_STRING, , -1, 0) == NULL)
// return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_CALLED_STATION_ID, config.called_station, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_CALLING_STATION_ID, config.calling_station, -1, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_NAS_IP_ADDRESS, &config.nas_ip.sin_addr, 4, 0) == NULL)
return result(rh, 0, NULL);
if (rc_avpair_add(rh, &send, PW_NAS_IDENTIFIER, config.nas_id, -1, 0) == NULL)
return result(rh, 0, NULL);
rc_apply_config(rh);
if (rc_auth(rh, 0, send, &received, NULL) == OK_RC)
return result(rh, 1, received);
return result(rh, 0, NULL);
}
int
main(int argc, char **argv)
{
if (argc != 2)
return result(NULL, 0, NULL);
blob_buf_init(&b, 0);
if (!blobmsg_add_json_from_file(&b, argv[1]))
return result(NULL, 0, NULL);
blobmsg_parse(radius_policy, __RADIUS_MAX, tb, blob_data(b.head), blob_len(b.head));
config_load();
if (!config.type)
return result(NULL, 0, NULL);
if (!strcmp(config.type, "auth"))
return auth();
if (!strcmp(config.type, "uam-auth"))
return uam_auth();
if (!strcmp(config.type, "uam-acct"))
return uam_acct();
return result(NULL, 0, NULL);
}

View File

@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: ISC */
/* Copyright (C) 2022 John Crispin <john@phrozen.org> */
#include <ucode/module.h>
#include <stdio.h>
#include <string.h>
#include <libubox/md5.h>
#define MIN(a, b) ((a > b) ? b : a)
static int
hex_to_str(char *in, char *out, int olen)
{
int ilen = strlen(in);
int len = 0;
while (ilen >= 2 && olen > 1) {
int c;
sscanf(in, "%2x", &c);
*out++ = (char) c;
in += 2;
ilen -= 2;
len++;
}
*out = '\0';
return len;
}
static uc_value_t *
uc_password(uc_vm_t *vm, size_t nargs)
{
uc_value_t *_chal = uc_fn_arg(0);
uc_value_t *_pass = uc_fn_arg(1);
uc_value_t *_secret = uc_fn_arg(2);
char pass[32];
char chal[32];
char uamchal[32];
char cleartext[32] = {};
int plen;
int clen;
char *secret;
int i;
md5_ctx_t md5 = {};
if (!_chal || !_pass || !_secret)
return ucv_boolean_new(false);
plen = hex_to_str(ucv_to_string(vm, _pass), pass, sizeof(pass));
clen = hex_to_str(ucv_to_string(vm, _chal), chal, sizeof(chal));
secret = ucv_to_string(vm, _secret);
md5_begin(&md5);
md5_hash(chal, clen, &md5);
md5_hash(secret, strlen(secret), &md5);
md5_end(uamchal, &md5);
for (i = 0; i < MIN(plen, 16); i++)
cleartext[i] = pass[i] ^ uamchal[i];
return ucv_string_new(cleartext);
}
static uc_value_t *
uc_md5(uc_vm_t *vm, size_t nargs)
{
uc_value_t *_str = uc_fn_arg(0);
uc_value_t *_secret = uc_fn_arg(1);
char _md[32] = {};
char md[33] = {};
char *secret;
char *str;
md5_ctx_t md5 = {};
int i = 0;
if (!_str || !_secret)
return ucv_boolean_new(false);
str = ucv_to_string(vm, _str);
secret = ucv_to_string(vm, _secret);
md5_begin(&md5);
md5_hash(str, strlen(str), &md5);
md5_hash(secret, strlen(secret), &md5);
md5_end(_md, &md5);
for (i = 0; i < 16; i++)
sprintf(&md[2 * i], "%02X", _md[i]);
return ucv_string_new(md);
}
static const uc_function_list_t global_fns[] = {
{ "password", uc_password },
{ "md5", uc_md5 },
};
void
uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
uc_function_list_register(scope, global_fns);
}