uspot: refactor code

* add a common.uc class
* add ucode ubus calls

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2022-09-02 15:44:20 +02:00
parent a8bb06fb1c
commit e692aea19e
4 changed files with 189 additions and 205 deletions

View File

@@ -0,0 +1,137 @@
'use strict';
let ubus = require('ubus');
let fs = require('fs');
let uci = require('uci').cursor();
let config = uci.get_all('uspot');
let file = fs.open(config.config.web_root == 1 ? '/tmp/ucentral/www-uspot/header.html' : '/usr/share/uspot/header', 'r');
let header = file.read('all');
file.close();
file = fs.open(config.config.web_root == 1 ? '/tmp/ucentral/www-uspot/footer.html' : '/usr/share/uspot/footer', 'r');
let footer = file.read('all');
file.close();
function PO(id, english) {
return english;
}
return {
fs,
rtnl: require('rtnl'),
uam: require('uam'),
uci,
config,
header,
footer,
// wrapper for scraping external tools stdout
fs_popen: function(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
allow_client: function(ctx) {
ctx.ubus.call('spotfilter', 'client_set', {
"interface": "hotspot",
"address": replace(ctx.mac, '-', ':'),
"state": 1,
"dns_state": 1,
"accounting": [ "dl", "ul"],
"data": {
"connect": time()
}
});
if (ctx.query_string.userurl)
include('redir.uc', { redir_location: ctx.query_string.userurl });
else
include('allow.uc', ctx);
},
// generate the default radius auth payload
radius_init: function(ctx) {
return {
server: sprintf('%s:%s:%s', this.config.radius.auth_server, this.config.radius.auth_port, this.config.radius.auth_secret),
acct_session: "0123456789",
client_ip: ctx.env.REMOTE_ADDR,
called_station: ctx.mac,
calling_station: this.config.uam.nasmac,
nas_ip: ctx.env.SERVER_ADDR,
nas_id: this.config.uam.nasid
};
},
radius_call: function(ctx, payload) {
let cfg = fs.open('/tmp/' + ctx.mac + '.json', 'w');
cfg.write(payload);
cfg.close();
return this.fs_popen('/usr/bin/radius-client /tmp/' + ctx.mac + '.json');
},
handle_request: function(env) {
let mac;
let form_data = {};
let query_string = {};
let post_data = '';
let ctx = { env, header: this.header, footer: this.footer, mac, form_data, post_data, query_string, config: this.config, PO };
// lookup the peers MAC
let macs = this.rtnl.request(this.rtnl.const.RTM_GETNEIGH, this.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);
return NULL;
}
// check if a client is already connected
ctx.ubus = ubus.connect();
let connected = ctx.ubus.call('spotfilter', 'client_get', {
'interface': 'hotspot',
'address': ctx.mac
});
if (connected?.state) {
include('connected.uc', ctx);
return NULL;
}
// split QUERY_STRING
if (env.QUERY_STRING)
for (let chunk in split(env.QUERY_STRING, '&')) {
let m = match(chunk, /^([^=]+)=(.*)$/);
if (!m) continue;
ctx.query_string[m[1]] = replace(m[2], /%([[:xdigit:]][[:xdigit:]])/g, (m, h) => chr(hex(h) || 0x20));
}
// 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];
}
return ctx;
}
};

View File

@@ -2,104 +2,45 @@
'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');
push(REQUIRE_SEARCH_PATH, "/usr/share/uspot/*.uc");
let portal = require('common');
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}\'');
if (ctx.query_string.userurl)
include('uam.uc', { uam_location: ctx.query_string.userurl });
else
include('allow.uc', ctx);
}
// log the client in via radius
function auth_client(ctx) {
let password;
let payload = {
server: sprintf('%s:%s:%s',config.radius.auth_server, config.radius.auth_port, config.radius.auth_secret),
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
};
let payload = portal.radius_init(ctx);
if (ctx.query_string.username && ctx.query_string.response) {
let challenge = uam.md5(config.uam.challenge, ctx.mac);
let challenge = uam.md5(portal.config.uam.challenge, ctx.mac);
payload.type = 'uam-chap-auth';
payload.username = ctx.query_string.username;
payload.chap_password = ctx.query_string.response;
if (config.uam.secret)
payload.chap_challenge = uam.chap_challenge(challenge, config.uam.uam_secret);
if (portal.config.uam.secret)
payload.chap_challenge = uam.chap_challenge(challenge, portal.config.uam.uam_secret);
else
payload.chap_challenge = challenge;
} else if (ctx.query_string.username && ctx.query_string.password) {
payload.type = 'uam-auth';
payload.username = ctx.mac;
payload.password = uam.password(uam.md5(config.uam.challenge, ctx.mac), ctx.query_string.password, config.uam.uam_secret);
payload.password = uam.password(uam.md5(portal.config.uam.challenge, ctx.mac), ctx.query_string.password, portal.config.uam.uam_secret);
}
let cfg = fs.open('/tmp/' + ctx.mac + '.json', 'w');
cfg.write(payload);
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();
let reply = portal.radius_call(ctx, payload);
if (reply['access-accept']) {
allow_client(ctx);
portal.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 };
let ctx = portal.handle_request(env);
// 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 m = match(chunk, /^([^=]+)=(.*)$/);
if (!m) continue;
ctx.query_string[m[1]] = replace(m[2], /%([[:xdigit:]][[:xdigit:]])/g, (m, h) => chr(hex(h) || 0x20));
}
auth_client(ctx);
if (ctx)
auth_client(ctx);
};
%}

View File

@@ -2,46 +2,12 @@
'use strict';
let fs = require('fs');
let rtnl = require('rtnl');
let uam = require('uam');
let uci = require('uci').cursor();
let config = uci.get_all('uspot');
let file = fs.open(config.config.web_root == 1 ? '/tmp/ucentral/www-uspot/header.html' : '/usr/share/uspot/header', 'r');
let header = file.read('all');
file.close();
file = fs.open(config.config.web_root == 1 ? '/tmp/ucentral/www-uspot/footer.html' : '/usr/share/uspot/footer', 'r');
let footer = file.read('all');
file.close();
// 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);
}
push(REQUIRE_SEARCH_PATH, "/usr/share/uspot/*.uc");
let portal = require('common');
// delegate an initial connection to the correct handler
function request_start(ctx) {
switch (config?.config?.auth_mode) {
switch (portal.config?.config?.auth_mode) {
case 'click-to-continue':
include('click.uc', ctx);
return;
@@ -52,17 +18,17 @@ function request_start(ctx) {
include('radius.uc', ctx);
return;
case 'uam':
ctx.uam_location = config.uam.uam_server +
ctx.redir_location = portal.config.uam.uam_server +
'?res=notyet' +
'&uamip=' + ctx.env.SERVER_ADDR +
'&uamport=' + config.uam.uam_port +
'&challenge=' + uam.md5(config.uam.challenge, ctx.mac) +
'&uamport=' + portal.config.uam.uam_port +
'&challenge=' + portal.uam.md5(portal.config.uam.challenge, ctx.mac) +
'&mac=' + replace(ctx.mac, ':', '-') +
'&ip=' + ctx.env.REMOTE_ADDR +
'&called=' + config.uam.nasmac +
'&nasid=' + config.uam.nasid;
ctx.uam_location += '&md=' + uam.md5(ctx.uam_location, config.uam.uam_secret);
include('uam.uc', ctx);
'&called=' + portal.config.uam.nasmac +
'&nasid=' + portal.config.uam.nasid;
ctx.redir_location += '&md=' + portal.uam.md5(ctx.uam_location, portal.config.uam.uam_secret);
include('redir.uc', ctx);
return;
default:
include('error.uc', ctx);
@@ -73,7 +39,7 @@ function request_start(ctx) {
// 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') {
if (portal.config?.config?.auth_mode != 'click-to-continue') {
include('error.uc', ctx);
return;
}
@@ -83,13 +49,13 @@ function request_click(ctx) {
request_start({ ...ctx, error: 1 });
return;
}
allow_client(ctx);
portal.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') {
if (portal/config?.config?.auth_mode != 'credentials') {
include('error.uc', ctx);
return;
}
@@ -101,8 +67,8 @@ function request_credentials(ctx) {
}
// check if the credentials are valid
for (let k in config) {
let cred = config[k];
for (let k in portal.config) {
let cred = portal.config[k];
if (cred['.type'] != 'credentials')
continue;
@@ -110,7 +76,7 @@ function request_credentials(ctx) {
ctx.form_data.password != cred.password)
continue;
allow_client(ctx);
portal.allow_client(ctx);
return;
}
@@ -121,7 +87,7 @@ function request_credentials(ctx) {
// delegate a radius username/password authentication
function request_radius(ctx) {
// make sure this is the right auth_mode
if (config?.config?.auth_mode != 'radius') {
if (portal.config?.config?.auth_mode != 'radius') {
include('error.uc', ctx);
return;
}
@@ -132,29 +98,15 @@ function request_radius(ctx) {
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();
// trigger the radius auth
let payload = radius_init(ctx);
payload.type = 'auth';
payload.username = ctx.form_data.username;
payload.password = ctx.form_data.password;
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();
let reply = portal.radius_call(ctx, payload);
if (reply['access-accept']) {
allow_client(ctx);
portal.allow_client(ctx);
return;
}
@@ -162,70 +114,24 @@ function request_radius(ctx) {
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 };
let ctx = portal.handle_request(env);
// 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 m = match(chunk, /^([^=]+)=(.*)$/);
if (!m) continue;
ctx.query_string[m[1]] = replace(m[2], /%([[:xdigit:]][[:xdigit:]])/g, (m, h) => chr(hex(h) || 0x20));
if (ctx)
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;
}
// 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

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