diff --git a/feeds/ucentral/uspot/files/usr/share/uspot/common.uc b/feeds/ucentral/uspot/files/usr/share/uspot/common.uc new file mode 100644 index 000000000..f0893e97c --- /dev/null +++ b/feeds/ucentral/uspot/files/usr/share/uspot/common.uc @@ -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; + } +}; diff --git a/feeds/ucentral/uspot/files/usr/share/uspot/handler-uam.uc b/feeds/ucentral/uspot/files/usr/share/uspot/handler-uam.uc index 0037e643b..1b4ad5ae8 100644 --- a/feeds/ucentral/uspot/files/usr/share/uspot/handler-uam.uc +++ b/feeds/ucentral/uspot/files/usr/share/uspot/handler-uam.uc @@ -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); }; %} diff --git a/feeds/ucentral/uspot/files/usr/share/uspot/handler.uc b/feeds/ucentral/uspot/files/usr/share/uspot/handler.uc index 1cc299ae4..6b09ed312 100644 --- a/feeds/ucentral/uspot/files/usr/share/uspot/handler.uc +++ b/feeds/ucentral/uspot/files/usr/share/uspot/handler.uc @@ -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; - } }; %} diff --git a/feeds/ucentral/uspot/files/usr/share/uspot/uam.uc b/feeds/ucentral/uspot/files/usr/share/uspot/redir.uc similarity index 58% rename from feeds/ucentral/uspot/files/usr/share/uspot/uam.uc rename to feeds/ucentral/uspot/files/usr/share/uspot/redir.uc index 5f862769d..a1978ffa6 100644 --- a/feeds/ucentral/uspot/files/usr/share/uspot/uam.uc +++ b/feeds/ucentral/uspot/files/usr/share/uspot/redir.uc @@ -1,5 +1,5 @@ Status: 302 Found -Location: {{ uam_location }} +Location: {{ redir_location }} Content-Type: text/html