From e5563b5842abf6afabfae8f7e0a37825c5322480 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 7 Jun 2018 01:39:16 +0000 Subject: [PATCH] bugfix moving a few functions to the wrong place --- lib/handlers.js | 2 +- lib/relay.js | 391 ++++++++++++++++++++++++------------------------ 2 files changed, 196 insertions(+), 197 deletions(-) diff --git a/lib/handlers.js b/lib/handlers.js index b0ac8e1..20b30f6 100644 --- a/lib/handlers.js +++ b/lib/handlers.js @@ -120,7 +120,7 @@ module.exports.create = function (state) { serveAdmin(req, res, finalhandler(req, res)); }; state.httpTunnelServer = http.createServer(function (req, res) { - //res.setHeader('connection', 'close'); + res.setHeader('connection', 'close'); if (state.extensions.webadmin) { state.extensions.webadmin(state, req, res); } else { diff --git a/lib/relay.js b/lib/relay.js index 947db98..d499bf8 100644 --- a/lib/relay.js +++ b/lib/relay.js @@ -113,228 +113,227 @@ module.exports.create = function (state) { }); } - function next() { + function logName() { + var result = Object.keys(remotes).map(function (jwtoken) { + return remotes[jwtoken].deviceId; + }).join(';'); - function logName() { - var result = Object.keys(remotes).map(function (jwtoken) { - return remotes[jwtoken].deviceId; - }).join(';'); + return result || socketId; + } - return result || socketId; + function sendTunnelMsg(addr, data, service) { + ws.send(Packer.pack(addr, data, service), {binary: true}); + } + + function getBrowserConn(cid) { + var browserConn; + Object.keys(remotes).some(function (jwtoken) { + if (remotes[jwtoken].clients[cid]) { + browserConn = remotes[jwtoken].clients[cid]; + return true; + } + }); + + return browserConn; + } + + function closeBrowserConn(cid) { + var remote; + Object.keys(remotes).some(function (jwtoken) { + if (remotes[jwtoken].clients[cid]) { + remote = remotes[jwtoken]; + return true; + } + }); + if (!remote) { + return; } - function sendTunnelMsg(addr, data, service) { - ws.send(Packer.pack(addr, data, service), {binary: true}); - } + PromiseA.resolve().then(function () { + var conn = remote.clients[cid]; + conn.tunnelClosing = true; + conn.end(); - function getBrowserConn(cid) { - var browserConn; - Object.keys(remotes).some(function (jwtoken) { - if (remotes[jwtoken].clients[cid]) { - browserConn = remotes[jwtoken].clients[cid]; - return true; - } + // If no data is buffered for writing then we don't need to wait for it to drain. + if (!conn.bufferSize) { + return timeoutPromise(500); + } + // Otherwise we want the connection to be able to finish, but we also want to impose + // a time limit for it to drain, since it shouldn't have more than 1MB buffered. + return new PromiseA(function (resolve) { + var timeoutId = setTimeout(resolve, 60*1000); + conn.once('drain', function () { + clearTimeout(timeoutId); + setTimeout(resolve, 500); + }); }); + }).then(function () { + if (remote.clients[cid]) { + console.warn(cid, 'browser connection still present after calling `end`'); + remote.clients[cid].destroy(); + return timeoutPromise(500); + } + }).then(function () { + if (remote.clients[cid]) { + console.error(cid, 'browser connection still present after calling `destroy`'); + delete remote.clients[cid]; + } + }).catch(function (err) { + console.warn('failed to close browser connection', cid, err); + }); + } - return browserConn; - } + function addToken(jwtoken) { - function closeBrowserConn(cid) { - var remote; - Object.keys(remotes).some(function (jwtoken) { - if (remotes[jwtoken].clients[cid]) { - remote = remotes[jwtoken]; - return true; - } - }); - if (!remote) { - return; + function onAuth(token) { + var err; + if (!token) { + err = new Error("invalid access token"); + err.code = "E_INVALID_TOKEN"; + return state.Promise.reject(err); } - PromiseA.resolve().then(function () { - var conn = remote.clients[cid]; - conn.tunnelClosing = true; - conn.end(); + if (!Array.isArray(token.domains)) { + if ('string' === typeof token.name) { + token.domains = [ token.name ]; + } + } - // If no data is buffered for writing then we don't need to wait for it to drain. - if (!conn.bufferSize) { - return timeoutPromise(500); + if (!Array.isArray(token.domains) || !token.domains.length) { + err = new Error("invalid domains array"); + err.code = "E_INVALID_NAME"; + return state.Promise.reject(err); + } + if (token.domains.some(function (name) { return typeof name !== 'string'; })) { + err = new Error("invalid domain name(s)"); + err.code = "E_INVALID_NAME"; + return state.Promise.reject(err); + } + + // Add the custom properties we need to manage this remote, then add it to all the relevant + // domains and the list of all this websocket's remotes. + token.deviceId = (token.device && (token.device.id || token.device.hostname)) || token.domains.join(','); + token.ws = ws; + token.upgradeReq = upgradeReq; + token.clients = {}; + + token.pausedConns = []; + ws._socket.on('drain', function () { + // the websocket library has it's own buffer apart from node's socket buffer, but that one + // is much more difficult to watch, so we watch for the lower level buffer to drain and + // then check to see if the upper level buffer is still too full to write to. Note that + // the websocket library buffer has something to do with compression, so I'm not requiring + // that to be 0 before we start up again. + if (ws.bufferedAmount > 128*1024) { + return; } - // Otherwise we want the connection to be able to finish, but we also want to impose - // a time limit for it to drain, since it shouldn't have more than 1MB buffered. - return new PromiseA(function (resolve) { - var timeoutId = setTimeout(resolve, 60*1000); - conn.once('drain', function () { - clearTimeout(timeoutId); - setTimeout(resolve, 500); - }); + + token.pausedConns.forEach(function (conn) { + if (!conn.manualPause) { + // console.log('resuming', conn.tunnelCid, 'now that the web socket has caught up'); + conn.resume(); + } }); - }).then(function () { - if (remote.clients[cid]) { - console.warn(cid, 'browser connection still present after calling `end`'); - remote.clients[cid].destroy(); - return timeoutPromise(500); - } - }).then(function () { - if (remote.clients[cid]) { - console.error(cid, 'browser connection still present after calling `destroy`'); - delete remote.clients[cid]; - } - }).catch(function (err) { - console.warn('failed to close browser connection', cid, err); + token.pausedConns.length = 0; }); - } - function addToken(jwtoken) { + token.domains.forEach(function (domainname) { + Devices.add(state.deviceLists, domainname, token); + }); - function onAuth(token) { - var err; - if (!token) { - err = new Error("invalid access token"); - err.code = "E_INVALID_TOKEN"; - return state.Promise.reject(err); - } + console.log('[DEBUG] got to firstToken check'); - if (!Array.isArray(token.domains)) { - if ('string' === typeof token.name) { - token.domains = [ token.name ]; - } - } + if (!firstToken || firstToken === jwtoken) { + firstToken = jwtoken; + token.dynamicPorts = []; + token.dynamicNames = []; - if (!Array.isArray(token.domains) || !token.domains.length) { - err = new Error("invalid domains array"); - err.code = "E_INVALID_NAME"; - return state.Promise.reject(err); - } - if (token.domains.some(function (name) { return typeof name !== 'string'; })) { - err = new Error("invalid domain name(s)"); - err.code = "E_INVALID_NAME"; - return state.Promise.reject(err); - } - - // Add the custom properties we need to manage this remote, then add it to all the relevant - // domains and the list of all this websocket's remotes. - token.deviceId = (token.device && (token.device.id || token.device.hostname)) || token.domains.join(','); - token.ws = ws; - token.upgradeReq = upgradeReq; - token.clients = {}; - - token.pausedConns = []; - ws._socket.on('drain', function () { - // the websocket library has it's own buffer apart from node's socket buffer, but that one - // is much more difficult to watch, so we watch for the lower level buffer to drain and - // then check to see if the upper level buffer is still too full to write to. Note that - // the websocket library buffer has something to do with compression, so I'm not requiring - // that to be 0 before we start up again. - if (ws.bufferedAmount > 128*1024) { - return; - } - - token.pausedConns.forEach(function (conn) { - if (!conn.manualPause) { - // console.log('resuming', conn.tunnelCid, 'now that the web socket has caught up'); - conn.resume(); - } - }); - token.pausedConns.length = 0; - }); - - token.domains.forEach(function (domainname) { - Devices.add(state.deviceLists, domainname, token); - }); - - console.log('[DEBUG] got to firstToken check'); - - if (!firstToken || firstToken === jwtoken) { - firstToken = jwtoken; - token.dynamicPorts = []; - token.dynamicNames = []; - - function onDynTcpReady() { - var serviceport = this.address().port; - console.info('[DynTcpConn] Port', serviceport, 'now open for', token.deviceId); - token.dynamicPorts.push(serviceport); - Devices.add(state.deviceLists, serviceport, token); - var hri = require('human-readable-ids').hri; - var hrname = hri.random() + '.telebit.cloud'; - token.dynamicNames.push(hrname); - // TODO restrict to authenticated device - // TODO pull servername from config - // TODO remove hrname on disconnect - Devices.add(state.deviceLists, hrname, token); - sendTunnelMsg( - null - , [ 2 - , 'grant' - , [ ['ssh+https', hrname, 443 ] - , ['ssh', 'ssh.telebit.cloud', serviceport ] - , ['tcp', 'tcp.telebit.cloud', serviceport] - , ['https', hrname ] - ] + function onDynTcpReady() { + var serviceport = this.address().port; + console.info('[DynTcpConn] Port', serviceport, 'now open for', token.deviceId); + token.dynamicPorts.push(serviceport); + Devices.add(state.deviceLists, serviceport, token); + var hri = require('human-readable-ids').hri; + var hrname = hri.random() + '.telebit.cloud'; + token.dynamicNames.push(hrname); + // TODO restrict to authenticated device + // TODO pull servername from config + // TODO remove hrname on disconnect + Devices.add(state.deviceLists, hrname, token); + sendTunnelMsg( + null + , [ 2 + , 'grant' + , [ ['ssh+https', hrname, 443 ] + , ['ssh', 'ssh.telebit.cloud', serviceport ] + , ['tcp', 'tcp.telebit.cloud', serviceport] + , ['https', hrname ] ] - , 'control' - ); - } - - try { - token.server = require('net').createServer(onDynTcpConn).listen(0, onDynTcpReady); - token.server.on('error', function (e) { - console.error("Server Error assigning a dynamic port to a new connection:", e); - }); - } catch(e) { - // what a wonderful problem it will be the day that this bug needs to be fixed - // (i.e. there are enough users to run out of ports) - console.error("Error assigning a dynamic port to a new connection:", e); - } + ] + , 'control' + ); } - remotes[jwtoken] = token; - console.info("[ws] authorized", socketId, "for", token.deviceId); - return null; + try { + token.server = require('net').createServer(onDynTcpConn).listen(0, onDynTcpReady); + token.server.on('error', function (e) { + console.error("Server Error assigning a dynamic port to a new connection:", e); + }); + } catch(e) { + // what a wonderful problem it will be the day that this bug needs to be fixed + // (i.e. there are enough users to run out of ports) + console.error("Error assigning a dynamic port to a new connection:", e); + } } - if (remotes[jwtoken]) { - // return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" }; - return state.Promise.resolve(null); - } - - return state.authenticate({ auth: jwtoken }).then(onAuth); - } - - function removeToken(jwtoken) { - var remote = remotes[jwtoken]; - if (!remote) { - return { message: 'specified token not present', code: 'E_INVALID_TOKEN'}; - } - - // Prevent any more browser connections being sent to this remote, and any existing - // connections from trying to send more data across the connection. - remote.domains.forEach(function (domainname) { - Devices.remove(state.deviceLists, domainname, remote); - }); - remote.dynamicPorts.forEach(function (portnumber) { - Devices.remove(state.deviceLists, portnumber, remote); - }); - remote.ws = null; - remote.upgradeReq = null; - if (remote.server) { - remote.serverPort = remote.server.address().port; - remote.server.close(function () { - console.log("[DynTcpConn] closing server for ", remote.serverPort); - remote.serverPort = null; - }); - remote.server = null; - } - - // Close all of the existing browser connections associated with this websocket connection. - Object.keys(remote.clients).forEach(function (cid) { - closeBrowserConn(cid); - }); - delete remotes[jwtoken]; - console.log("[ws] removed token '" + remote.deviceId + "' from", socketId); + remotes[jwtoken] = token; + console.info("[ws] authorized", socketId, "for", token.deviceId); return null; } + if (remotes[jwtoken]) { + // return { message: "token sent multiple times", code: "E_TOKEN_REPEAT" }; + return state.Promise.resolve(null); + } + + return state.authenticate({ auth: jwtoken }).then(onAuth); + } + + function removeToken(jwtoken) { + var remote = remotes[jwtoken]; + if (!remote) { + return { message: 'specified token not present', code: 'E_INVALID_TOKEN'}; + } + + // Prevent any more browser connections being sent to this remote, and any existing + // connections from trying to send more data across the connection. + remote.domains.forEach(function (domainname) { + Devices.remove(state.deviceLists, domainname, remote); + }); + remote.dynamicPorts.forEach(function (portnumber) { + Devices.remove(state.deviceLists, portnumber, remote); + }); + remote.ws = null; + remote.upgradeReq = null; + if (remote.server) { + remote.serverPort = remote.server.address().port; + remote.server.close(function () { + console.log("[DynTcpConn] closing server for ", remote.serverPort); + remote.serverPort = null; + }); + remote.server = null; + } + + // Close all of the existing browser connections associated with this websocket connection. + Object.keys(remote.clients).forEach(function (cid) { + closeBrowserConn(cid); + }); + delete remotes[jwtoken]; + console.log("[ws] removed token '" + remote.deviceId + "' from", socketId); + return null; + } + + function next() { var commandHandlers = { add_token: addToken , auth: addToken