mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-09-11 08:55:32 +03:00
Web relay can now handle connection:close responses.
This commit is contained in:
parent
5eca4eecee
commit
a151dcbfe6
68
apprelays.js
68
apprelays.js
@ -168,13 +168,19 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
|
|||||||
if (x[2] == true) { tunnels[tunnelId].processWebSocket(x[0], x[1]); } else { tunnels[tunnelId].processRequest(x[0], x[1]); }
|
if (x[2] == true) { tunnels[tunnelId].processWebSocket(x[0], x[1]); } else { tunnels[tunnelId].processRequest(x[0], x[1]); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tunnel.oncompleted = function (tunnelId) {
|
tunnel.oncompleted = function (tunnelId, closed) {
|
||||||
if (tunnels == null) return;
|
if (tunnels == null) return;
|
||||||
parent.parent.debug('webrelay', 'tunnel-oncompleted');
|
if (closed === true) {
|
||||||
errorCount = 0; // Something got completed, clear any error count
|
parent.parent.debug('webrelay', 'tunnel-oncompleted and closed');
|
||||||
if (pendingRequests.length > 0) {
|
} else {
|
||||||
const x = pendingRequests.shift();
|
parent.parent.debug('webrelay', 'tunnel-oncompleted');
|
||||||
if (x[2] == true) { tunnels[tunnelId].processWebSocket(x[0], x[1]); } else { tunnels[tunnelId].processRequest(x[0], x[1]); }
|
}
|
||||||
|
if (closed !== true) {
|
||||||
|
errorCount = 0; // Something got completed, clear any error count
|
||||||
|
if (pendingRequests.length > 0) {
|
||||||
|
const x = pendingRequests.shift();
|
||||||
|
if (x[2] == true) { tunnels[tunnelId].processWebSocket(x[0], x[1]); } else { tunnels[tunnelId].processRequest(x[0], x[1]); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tunnel.connect(userid, nodeid, addr, port, appid);
|
tunnel.connect(userid, nodeid, addr, port, appid);
|
||||||
@ -343,6 +349,13 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
if (obj.closed == true) return;
|
if (obj.closed == true) return;
|
||||||
obj.closed = true;
|
obj.closed = true;
|
||||||
|
|
||||||
|
// If we are processing a http response that terminates when it closes, do this now.
|
||||||
|
if ((obj.socketParseState == 1) && (obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close')) {
|
||||||
|
processHttpResponse(null, obj.socketAccumulator, true, true); // Indicate this tunnel is done and also closed, do not put a new request on this tunnel.
|
||||||
|
obj.socketAccumulator = '';
|
||||||
|
obj.socketParseState = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (obj.tls) {
|
if (obj.tls) {
|
||||||
try { obj.tls.end(); } catch (ex) { console.log(ex); }
|
try { obj.tls.end(); } catch (ex) { console.log(ex); }
|
||||||
delete obj.tls;
|
delete obj.tls;
|
||||||
@ -468,6 +481,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
obj.socketParseState = 0;
|
obj.socketParseState = 0;
|
||||||
obj.socketContentLengthRemaining = 0;
|
obj.socketContentLengthRemaining = 0;
|
||||||
function processHttpData(data) {
|
function processHttpData(data) {
|
||||||
|
//console.log('processHttpData', data.length);
|
||||||
obj.socketAccumulator += data;
|
obj.socketAccumulator += data;
|
||||||
while (true) {
|
while (true) {
|
||||||
//console.log('ACC(' + obj.socketAccumulator + '): ' + obj.socketAccumulator);
|
//console.log('ACC(' + obj.socketAccumulator + '): ' + obj.socketAccumulator);
|
||||||
@ -492,8 +506,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this HTTP request has a body
|
// Check if this HTTP request has a body
|
||||||
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close')) { obj.socketParseState = 1; }
|
|
||||||
if (obj.socketXHeader['content-length'] != null) { obj.socketParseState = 1; }
|
if (obj.socketXHeader['content-length'] != null) { obj.socketParseState = 1; }
|
||||||
|
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close')) { obj.socketParseState = 1; }
|
||||||
if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) { obj.socketParseState = 1; }
|
if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) { obj.socketParseState = 1; }
|
||||||
if (obj.isWebSocket) {
|
if (obj.isWebSocket) {
|
||||||
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'upgrade')) {
|
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'upgrade')) {
|
||||||
@ -510,21 +524,35 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
}
|
}
|
||||||
if (obj.socketParseState == 1) {
|
if (obj.socketParseState == 1) {
|
||||||
var csize = -1;
|
var csize = -1;
|
||||||
if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close')) {
|
if (obj.socketXHeader['content-length'] != null) {
|
||||||
// The body ends with a close, in this case, we will only process the header
|
|
||||||
processHttpResponse(null, null, true);
|
|
||||||
csize = 0;
|
|
||||||
} else if (obj.socketXHeader['content-length'] != null) {
|
|
||||||
// The body length is specified by the content-length
|
// The body length is specified by the content-length
|
||||||
if (obj.socketContentLengthRemaining == 0) { obj.socketContentLengthRemaining = parseInt(obj.socketXHeader['content-length']); } // Set the remaining content-length if not set
|
if (obj.socketContentLengthRemaining == 0) { obj.socketContentLengthRemaining = parseInt(obj.socketXHeader['content-length']); } // Set the remaining content-length if not set
|
||||||
var data = obj.socketAccumulator.substring(0, obj.socketContentLengthRemaining); // Grab the available data, not passed the expected content-length
|
var data = obj.socketAccumulator.substring(0, obj.socketContentLengthRemaining); // Grab the available data, not passed the expected content-length
|
||||||
obj.socketAccumulator = obj.socketAccumulator.substring(data.length); // Remove the data from the accumulator
|
obj.socketAccumulator = obj.socketAccumulator.substring(data.length); // Remove the data from the accumulator
|
||||||
obj.socketContentLengthRemaining -= data.length; // Substract the obtained data from the expected size
|
obj.socketContentLengthRemaining -= data.length; // Substract the obtained data from the expected size
|
||||||
processHttpResponse(null, data, (obj.socketContentLengthRemaining == 0)); // Send any data we have, if we are done, signal the end of the response
|
if (obj.socketContentLengthRemaining > 0) {
|
||||||
if (obj.socketContentLengthRemaining > 0) return; // If more data is needed, return now so we exit the while() loop.
|
// Send any data we have, if we are done, signal the end of the response
|
||||||
|
processHttpResponse(null, data, false);
|
||||||
|
return; // More data is needed, return now so we exit the while() loop.
|
||||||
|
} else {
|
||||||
|
// We are done with this request
|
||||||
|
const closing = (obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close');
|
||||||
|
if (closing) {
|
||||||
|
// We need to close this tunnel.
|
||||||
|
processHttpResponse(null, data, false);
|
||||||
|
obj.close();
|
||||||
|
} else {
|
||||||
|
// Proceed with the next request.
|
||||||
|
processHttpResponse(null, data, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
csize = 0; // We are done
|
csize = 0; // We are done
|
||||||
}
|
} else if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close')) {
|
||||||
else if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) {
|
// The body ends with a close, in this case, we will only process the header
|
||||||
|
processHttpResponse(null, obj.socketAccumulator, false);
|
||||||
|
obj.socketAccumulator = '';
|
||||||
|
return;
|
||||||
|
} else if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) {
|
||||||
// The body is chunked
|
// The body is chunked
|
||||||
var clen = obj.socketAccumulator.indexOf('\r\n');
|
var clen = obj.socketAccumulator.indexOf('\r\n');
|
||||||
if (clen < 0) { return; } // Chunk length not found, exit now and get more data.
|
if (clen < 0) { return; } // Chunk length not found, exit now and get more data.
|
||||||
@ -602,8 +630,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is a fully parsed HTTP response from the remote device
|
// This is a fully parsed HTTP response from the remote device
|
||||||
function processHttpResponse(header, data, done) {
|
function processHttpResponse(header, data, done, closed) {
|
||||||
//console.log('processHttpResponse');
|
//console.log('processHttpResponse', header, data ? data.length : 0, done, closed);
|
||||||
if (obj.isWebSocket == false) {
|
if (obj.isWebSocket == false) {
|
||||||
if (obj.res == null) return;
|
if (obj.res == null) return;
|
||||||
parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
|
parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
|
||||||
@ -611,7 +639,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
// If there is a header, send it
|
// If there is a header, send it
|
||||||
if (header != null) {
|
if (header != null) {
|
||||||
obj.res.status(parseInt(header.Directive[1])); // Set the status
|
obj.res.status(parseInt(header.Directive[1])); // Set the status
|
||||||
const blockHeaders = ['Directive', 'sec-websocket-extensions']; // We do not forward these headers
|
const blockHeaders = ['Directive', 'sec-websocket-extensions', 'connection', 'transfer-encoding']; // We do not forward these headers
|
||||||
for (var i in header) {
|
for (var i in header) {
|
||||||
if (i == 'set-cookie') {
|
if (i == 'set-cookie') {
|
||||||
for (var ii in header[i]) {
|
for (var ii in header[i]) {
|
||||||
@ -653,7 +681,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
|
|||||||
|
|
||||||
// Event completion
|
// Event completion
|
||||||
obj.processedRequestCount++;
|
obj.processedRequestCount++;
|
||||||
if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
|
if (obj.oncompleted) { obj.oncompleted(obj.tunnelId, closed); }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Tunnel is now in web socket pass-thru mode
|
// Tunnel is now in web socket pass-thru mode
|
||||||
|
111
webserver.js
111
webserver.js
@ -6612,61 +6612,60 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||||||
const port = parseInt(req.query.p);
|
const port = parseInt(req.query.p);
|
||||||
const appid = parseInt(req.query.appid);
|
const appid = parseInt(req.query.appid);
|
||||||
|
|
||||||
try {
|
// Check that we have an exact session on any of the relay DNS names
|
||||||
|
var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost;
|
||||||
// Check that we have an exact session on any of the relay DNS names
|
for (var hostIndex in obj.args.relaydns) {
|
||||||
var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost;
|
const host = obj.args.relaydns[hostIndex];
|
||||||
for (var hostIndex in obj.args.relaydns) {
|
xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + host;
|
||||||
const host = obj.args.relaydns[hostIndex];
|
xrelaySession = webRelaySessions[xrelaySessionId];
|
||||||
xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + host;
|
if (xrelaySession == null) {
|
||||||
xrelaySession = webRelaySessions[xrelaySessionId];
|
// We found an unused hostname, save this as it could be useful.
|
||||||
if (xrelaySession == null) {
|
if (freeRelayHost == null) { freeRelayHost = host; }
|
||||||
// We found an unused hostname, save this as it could be useful.
|
} else {
|
||||||
if (freeRelayHost == null) { freeRelayHost = host; }
|
// Check if we already have a relay session that matches exactly what we want
|
||||||
} else {
|
if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
|
||||||
// Check if we already have a relay session that matches exactly what we want
|
// We found an exact match, we are all setup already, redirect to root of that DNS name
|
||||||
if ((xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
|
if (host == req.hostname) {
|
||||||
// We found an exact match, we are all setup already, redirect to root of that DNS name
|
// Request was made on the same host, redirect to root.
|
||||||
if (host == req.hostname) {
|
res.redirect('/');
|
||||||
// Request was made on the same host, redirect to root.
|
} else {
|
||||||
res.redirect('/');
|
// Request was made to a different host
|
||||||
} else {
|
const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
|
||||||
// Request was made to a different host
|
res.redirect('https://' + host + ((httpport != 443) ? (':' + httpport) : '') + '/');
|
||||||
const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
|
|
||||||
res.redirect('https://' + host + ((httpport != 443) ? (':' + httpport) : '') + '/');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep a record of the oldest web relay session, this could be useful.
|
// Keep a record of the oldest web relay session, this could be useful.
|
||||||
if (oldestRelayHost == null) {
|
if (oldestRelayHost == null) {
|
||||||
// Oldest host not set yet, set it
|
// Oldest host not set yet, set it
|
||||||
|
oldestRelayHost = host;
|
||||||
|
oldestRelayTime = xrelaySession.lastOperation;
|
||||||
|
} else {
|
||||||
|
// Check if this host is older then oldest so far
|
||||||
|
if (oldestRelayTime > xrelaySession.lastOperation) {
|
||||||
oldestRelayHost = host;
|
oldestRelayHost = host;
|
||||||
oldestRelayTime = xrelaySession.lastOperation;
|
oldestRelayTime = xrelaySession.lastOperation;
|
||||||
} else {
|
|
||||||
// Check if this host is older then oldest so far
|
|
||||||
if (oldestRelayTime > xrelaySession.lastOperation) {
|
|
||||||
oldestRelayHost = host;
|
|
||||||
oldestRelayTime = xrelaySession.lastOperation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there is a free relay DNS name we can use
|
// Check if there is a free relay DNS name we can use
|
||||||
var selectedHost = null;
|
var selectedHost = null;
|
||||||
if (freeRelayHost != null) {
|
if (freeRelayHost != null) {
|
||||||
// There is a free one, use it.
|
// There is a free one, use it.
|
||||||
selectedHost = freeRelayHost;
|
selectedHost = freeRelayHost;
|
||||||
xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + selectedHost;
|
} else {
|
||||||
} else {
|
// No free ones, close the oldest one
|
||||||
// No free ones, close the oldest one
|
selectedHost = oldestRelayHost;
|
||||||
selectedHost = oldestRelayHost;
|
}
|
||||||
xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + selectedHost;
|
xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + selectedHost;
|
||||||
xrelaySession = webRelaySessions[xrelaySessionId];
|
|
||||||
xrelaySession.close();
|
if (selectedHost == req.hostname) {
|
||||||
delete webRelaySessions[xrelaySessionId];
|
// If this web relay session id is not free, close it now
|
||||||
}
|
xrelaySession = webRelaySessions[xrelaySessionId];
|
||||||
|
if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; }
|
||||||
|
|
||||||
// Create a web relay session
|
// Create a web relay session
|
||||||
const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId);
|
const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId);
|
||||||
@ -6683,16 +6682,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||||||
// Setup the cleanup timer if needed
|
// Setup the cleanup timer if needed
|
||||||
if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
|
if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
|
||||||
|
|
||||||
if (selectedHost == req.hostname) {
|
// Redirect to root.
|
||||||
// Request was made on the same host, redirect to root.
|
res.redirect('/');
|
||||||
res.redirect('/');
|
} else {
|
||||||
|
if (req.query.noredirect != null) {
|
||||||
|
// No redirects allowed, fail here. This is important to make sure there is no redirect cascades
|
||||||
|
res.sendStatus(404);
|
||||||
} else {
|
} else {
|
||||||
// Request was made to a different host
|
// Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name
|
||||||
const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
|
const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
|
||||||
res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + '/');
|
res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (ex) { console.log(ex); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle all incoming requests as web relays
|
// Handle all incoming requests as web relays
|
||||||
@ -6716,7 +6717,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
|||||||
|
|
||||||
// Indicates to ExpressJS that the override public folder should be used to serve static files.
|
// Indicates to ExpressJS that the override public folder should be used to serve static files.
|
||||||
if (parent.config.domains[i].webpublicpath != null) {
|
if (parent.config.domains[i].webpublicpath != null) {
|
||||||
// Use domain public path
|
// Use domain public pathe
|
||||||
obj.app.use(url, obj.express.static(parent.config.domains[i].webpublicpath));
|
obj.app.use(url, obj.express.static(parent.config.domains[i].webpublicpath));
|
||||||
} else if (obj.parent.webPublicOverridePath != null) {
|
} else if (obj.parent.webPublicOverridePath != null) {
|
||||||
// Use override path
|
// Use override path
|
||||||
|
Loading…
Reference in New Issue
Block a user