mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	Added TLS support to web relay, #4172
This commit is contained in:
		
							parent
							
								
									2129ce067b
								
							
						
					
					
						commit
						ab9b83b5f4
					
				
					 3 changed files with 81 additions and 38 deletions
				
			
		
							
								
								
									
										110
									
								
								apprelays.js
									
										
									
									
									
								
							
							
						
						
									
										110
									
								
								apprelays.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -13,7 +13,6 @@
 | 
			
		|||
/*jshint esversion: 6 */
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Protocol numbers
 | 
			
		||||
10 = RDP
 | 
			
		||||
| 
						 | 
				
			
			@ -59,9 +58,18 @@ const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
 | 
			
		|||
const MESHRIGHT_DEVICEDETAILS = 0x00100000; // 1048576
 | 
			
		||||
const MESHRIGHT_ADMIN = 0xFFFFFFFF;
 | 
			
		||||
 | 
			
		||||
// SerialTunnel object is used to embed TLS within another connection.
 | 
			
		||||
function SerialTunnel(options) {
 | 
			
		||||
    var obj = new require('stream').Duplex(options);
 | 
			
		||||
    obj.forwardwrite = null;
 | 
			
		||||
    obj.updateBuffer = function (chunk) { this.push(chunk); };
 | 
			
		||||
    obj._write = function (chunk, encoding, callback) { if (obj.forwardwrite != null) { obj.forwardwrite(chunk); } else { console.err("Failed to fwd _write."); } if (callback) callback(); }; // Pass data written to forward
 | 
			
		||||
    obj._read = function (size) { }; // Push nothing, anything to read should be pushed from updateBuffer()
 | 
			
		||||
    return obj;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Construct a Web relay object
 | 
			
		||||
module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, userid, nodeid, addr, port) {
 | 
			
		||||
module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid) {
 | 
			
		||||
    const obj = {};
 | 
			
		||||
    obj.parent = parent;
 | 
			
		||||
    obj.lastOperation = Date.now();
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +78,7 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
 | 
			
		|||
    obj.nodeid = nodeid;
 | 
			
		||||
    obj.addr = addr;
 | 
			
		||||
    obj.port = port;
 | 
			
		||||
    obj.appid = appid;
 | 
			
		||||
    var pendingRequests = [];
 | 
			
		||||
    var nextTunnelId = 1;
 | 
			
		||||
    var tunnels = {};
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +92,6 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
 | 
			
		|||
 | 
			
		||||
    // Handle new HTTP request
 | 
			
		||||
    obj.handleRequest = function (req, res) {
 | 
			
		||||
        //console.log('handleRequest', req.url);
 | 
			
		||||
        pendingRequests.push([req, res]);
 | 
			
		||||
        handleNextRequest();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +104,6 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
 | 
			
		|||
            count += (tunnels[i].isWebSocket ? 0 : 1);
 | 
			
		||||
            if ((tunnels[i].relayActive == true) && (tunnels[i].res == null)) {
 | 
			
		||||
                // Found a free tunnel, use it
 | 
			
		||||
                //console.log('handleNextRequest-found empty tunnel');
 | 
			
		||||
                const x = pendingRequests.shift();
 | 
			
		||||
                tunnels[i].processRequest(x[0], x[1]);
 | 
			
		||||
                return;
 | 
			
		||||
| 
						 | 
				
			
			@ -106,12 +113,18 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
 | 
			
		|||
        if (count > 0) return;
 | 
			
		||||
 | 
			
		||||
        // Launch a new tunnel
 | 
			
		||||
        //console.log('handleNextRequest-starting new tunnel');
 | 
			
		||||
        const tunnel = module.exports.CreateWebRelay(obj, db, args, domain);
 | 
			
		||||
        tunnel.onclose = function (tunnelId) { delete tunnels[tunnelId]; }
 | 
			
		||||
        tunnel.onclose = function (tunnelId) {
 | 
			
		||||
            delete tunnels[tunnelId];
 | 
			
		||||
            // Count how many non-websocket tunnels are active
 | 
			
		||||
            var count = 0;
 | 
			
		||||
            for (var i in tunnels) { count += (tunnels[i].isWebSocket ? 0 : 1); }
 | 
			
		||||
            // If there are none, discard all pending HTTP requests
 | 
			
		||||
            if (count == 0) { for (var i in pendingRequests) { const x = pendingRequests[i]; x[1].end(); pendingRequests = []; } }
 | 
			
		||||
        }
 | 
			
		||||
        tunnel.onconnect = function (tunnelId) { if (pendingRequests.length > 0) { const x = pendingRequests.shift(); tunnels[tunnelId].processRequest(x[0], x[1]); } }
 | 
			
		||||
        tunnel.oncompleted = function (tunnelId) { if (pendingRequests.length > 0) { const x = pendingRequests.shift(); tunnels[tunnelId].processRequest(x[0], x[1]); } }
 | 
			
		||||
        tunnel.connect(userid, nodeid, addr, port);
 | 
			
		||||
        tunnel.connect(userid, nodeid, addr, port, appid);
 | 
			
		||||
        tunnel.tunnelId = nextTunnelId++;
 | 
			
		||||
        tunnels[tunnel.tunnelId] = tunnel;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +144,6 @@ module.exports.CreateMultiWebRelay = function (parent, db, req, args, domain, us
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Construct a Web relay object
 | 
			
		||||
module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		||||
    //const Net = require('net');
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +153,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
    obj.relayActive = false;
 | 
			
		||||
    obj.closed = false;
 | 
			
		||||
    obj.isWebSocket = false;
 | 
			
		||||
    const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
 | 
			
		||||
 | 
			
		||||
    // Events
 | 
			
		||||
    obj.onclose = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -151,8 +164,6 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
    obj.processRequest = function (req, res) {
 | 
			
		||||
        if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
 | 
			
		||||
 | 
			
		||||
        //console.log('processRequest-start', req.method);
 | 
			
		||||
 | 
			
		||||
        // Construct the HTTP request
 | 
			
		||||
        var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
 | 
			
		||||
        request += 'host: ' + obj.addr + ':' + obj.port + '\r\n';
 | 
			
		||||
| 
						 | 
				
			
			@ -161,17 +172,14 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
        if (parent.webCookie != null) { request += 'cookie: ' + parent.webCookie + '\r\n' } // If we have a sessin cookie, use it.
 | 
			
		||||
        request += '\r\n';
 | 
			
		||||
 | 
			
		||||
        //console.log('request', request);
 | 
			
		||||
 | 
			
		||||
        if ((req.headers['transfer-encoding'] != null) || (req.headers['content-length'] != null)) {
 | 
			
		||||
            // Read the HTTP body and send the request to the device
 | 
			
		||||
            obj.requestBinary = [Buffer.from(request)];
 | 
			
		||||
            req.on('data', function (data) { obj.requestBinary.push(data); });
 | 
			
		||||
            req.on('end', function () { obj.wsClient.send(Buffer.concat(obj.requestBinary)); delete obj.requestBinary; });
 | 
			
		||||
            req.on('end', function () { send(Buffer.concat(obj.requestBinary)); delete obj.requestBinary; });
 | 
			
		||||
        } else {
 | 
			
		||||
            // Request has no body, send it now
 | 
			
		||||
            obj.wsClient.send(Buffer.from(request));
 | 
			
		||||
            //console.log('processRequest-sent-nobody');
 | 
			
		||||
            send(Buffer.from(request));
 | 
			
		||||
        }
 | 
			
		||||
        obj.res = res;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +189,11 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
        if (obj.closed == true) return;
 | 
			
		||||
        obj.closed = true;
 | 
			
		||||
 | 
			
		||||
        if (obj.tls) {
 | 
			
		||||
            try { obj.tls.end(); } catch (ex) { console.log(ex); }
 | 
			
		||||
            delete obj.tls;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        // Event the session ending
 | 
			
		||||
        if ((obj.startTime) && (obj.meshid != null)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -215,10 +228,11 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    // Start the looppback server
 | 
			
		||||
    obj.connect = function (userid, nodeid, addr, port) {
 | 
			
		||||
    obj.connect = function (userid, nodeid, addr, port, appid) {
 | 
			
		||||
        if (obj.relayActive || obj.closed) return;
 | 
			
		||||
        obj.addr = addr;
 | 
			
		||||
        obj.port = port;
 | 
			
		||||
        obj.appid = appid;
 | 
			
		||||
 | 
			
		||||
        // Encode a cookie for the mesh relay
 | 
			
		||||
        const cookieContent = { userid: userid, domainid: domain.id, nodeid: nodeid, tcpport: port };
 | 
			
		||||
| 
						 | 
				
			
			@ -236,21 +250,36 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
            obj.wsClient = new WebSocket(url, options);
 | 
			
		||||
            obj.wsClient.on('open', function () { parent.parent.debug('relay', 'TCP: Relay websocket open'); });
 | 
			
		||||
            obj.wsClient.on('message', function (data) { // Make sure to handle flow control.
 | 
			
		||||
                if (obj.relayActive == false) {
 | 
			
		||||
                if (obj.tls) {
 | 
			
		||||
                    // WS --> TLS
 | 
			
		||||
                    processRawHttpData(data);
 | 
			
		||||
                } else if (obj.relayActive == false) {
 | 
			
		||||
                    if ((data == 'c') || (data == 'cr')) {
 | 
			
		||||
                        obj.relayActive = true;
 | 
			
		||||
                        if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection
 | 
			
		||||
                        if (appid == 2) {
 | 
			
		||||
                            // TLS needs to be setup
 | 
			
		||||
                            obj.ser = new SerialTunnel();
 | 
			
		||||
                            obj.ser.forwardwrite = function (data) { if (data.length > 0) { try { obj.wsClient.send(data); } catch (ex) { } } }; // TLS ---> WS
 | 
			
		||||
 | 
			
		||||
                            // TLSSocket to encapsulate TLS communication, which then tunneled via SerialTunnel
 | 
			
		||||
                            const tlsoptions = { socket: obj.ser, rejectUnauthorized: false };
 | 
			
		||||
                            obj.tls = require('tls').connect(tlsoptions, function () {
 | 
			
		||||
                                parent.parent.debug('relay', "Web Relay Secure TLS Connection");
 | 
			
		||||
                                obj.relayActive = true;
 | 
			
		||||
                                if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection
 | 
			
		||||
                            });
 | 
			
		||||
                            obj.tls.setEncoding('binary');
 | 
			
		||||
                            obj.tls.on('error', function (err) { parent.parent.debug('relay', "Web Relay TLS Connection Error", err); obj.close(); });
 | 
			
		||||
 | 
			
		||||
                            // Decrypted tunnel from TLS communcation to be forwarded to the browser
 | 
			
		||||
                            obj.tls.on('data', function (data) { processHttpData(data); }); // TLS ---> Browser
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // No TLS needed, tunnel is now active
 | 
			
		||||
                            obj.relayActive = true;
 | 
			
		||||
                            if (obj.onconnect) { obj.onconnect(obj.tunnelId); } // Event connection
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (typeof data == 'string') {
 | 
			
		||||
                        // Forward any ping/pong commands to the browser
 | 
			
		||||
                        var cmd = null;
 | 
			
		||||
                        try { cmd = JSON.parse(data); } catch (ex) { }
 | 
			
		||||
                        if ((cmd != null) && (cmd.ctrlChannel == '102938') && (cmd.type == 'ping')) { cmd.type = 'pong'; obj.wsClient.send(JSON.stringify(cmd)); }
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    // Relay WS --> TCP, event data coming in
 | 
			
		||||
                    processHttpData(data.toString('binary'));
 | 
			
		||||
                    processRawHttpData(data);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            obj.wsClient.on('close', function () { parent.parent.debug('relay', 'TCP: Relay websocket closed'); obj.close(); });
 | 
			
		||||
| 
						 | 
				
			
			@ -260,6 +289,23 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function processRawHttpData(data) {
 | 
			
		||||
        if (typeof data == 'string') {
 | 
			
		||||
            // Forward any ping/pong commands to the browser
 | 
			
		||||
            var cmd = null;
 | 
			
		||||
            try { cmd = JSON.parse(data); } catch (ex) { }
 | 
			
		||||
            if ((cmd != null) && (cmd.ctrlChannel == '102938') && (cmd.type == 'ping')) { cmd.type = 'pong'; obj.wsClient.send(JSON.stringify(cmd)); }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (obj.tls) {
 | 
			
		||||
            // If TLS is in use, WS --> TLS
 | 
			
		||||
            if (data.length > 0) { try { obj.ser.updateBuffer(data); } catch (ex) { console.log(ex); } }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Relay WS --> TCP, event data coming in
 | 
			
		||||
            processHttpData(data.toString('binary'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process incoming HTTP data
 | 
			
		||||
    obj.socketAccumulator = '';
 | 
			
		||||
    obj.socketParseState = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -335,12 +381,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
 | 
			
		|||
        if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Send data thru the relay tunnel
 | 
			
		||||
    function send(data) {
 | 
			
		||||
        if (obj.relayActive = - false) return false;
 | 
			
		||||
        obj.wsClient.send(data);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    // Send data thru the relay tunnel. Written to use TLS if needed.
 | 
			
		||||
    function send(data) { try { if (obj.tls) { obj.tls.write(data); } else { obj.wsClient.send(data); } } catch (ex) { } }
 | 
			
		||||
 | 
			
		||||
    parent.parent.debug('relay', 'TCP: Request for web relay');
 | 
			
		||||
    return obj;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4576,7 +4576,7 @@
 | 
			
		|||
            if ((((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && (node.agent.id != 14)) {
 | 
			
		||||
                if (webRelayPort != 0) {
 | 
			
		||||
                    x += '<a href=# onclick=p10WebRouter("' + node._id + '",1,80)>' + "HTTP" + '</a> ';
 | 
			
		||||
                    //x += '<a href=# onclick=p10WebRouter("' + node._id + '",2,443)>' + "HTTPS" + '</a> ';
 | 
			
		||||
                    x += '<a href=# onclick=p10WebRouter("' + node._id + '",2,443)>' + "HTTPS" + '</a> ';
 | 
			
		||||
                }
 | 
			
		||||
                if ((node.agent.id > 0) && (node.agent.id < 5)) {
 | 
			
		||||
                    if (navigator.platform.toLowerCase() == 'win32') {
 | 
			
		||||
| 
						 | 
				
			
			@ -7148,7 +7148,7 @@
 | 
			
		|||
                    if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0)) {
 | 
			
		||||
                        if (webRelayPort != 0) {
 | 
			
		||||
                            x += '<a href=# onclick=p10WebRouter("' + node._id + '",1,80)>' + "HTTP" + '</a> ';
 | 
			
		||||
                            //x += '<a href=# onclick=p10WebRouter("' + node._id + '",2,443)>' + "HTTPS" + '</a> ';
 | 
			
		||||
                            x += '<a href=# onclick=p10WebRouter("' + node._id + '",2,443)>' + "HTTPS" + '</a> ';
 | 
			
		||||
                        }
 | 
			
		||||
                        if ((node.agent.id > 0) && (node.agent.id < 5)) {
 | 
			
		||||
                            if (navigator.platform.toLowerCase() == 'win32') {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -146,12 +146,13 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
 | 
			
		|||
            const nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
 | 
			
		||||
            const addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
 | 
			
		||||
            const port = parseInt(req.query.p);
 | 
			
		||||
            const appid = parseInt(req.query.appid);
 | 
			
		||||
 | 
			
		||||
            // Check to see if we already have a multi-relay session that matches exactly this device and port for this user
 | 
			
		||||
            var relayMultiTunnel = null;
 | 
			
		||||
            for (var i in relayMultiTunnels) {
 | 
			
		||||
                const xrelayMultiTunnel = relayMultiTunnels[i];
 | 
			
		||||
                if ((xrelayMultiTunnel.domain.id == domain.id) && (xrelayMultiTunnel.userid == userid) && (xrelayMultiTunnel.nodeid == nodeid) && (xrelayMultiTunnel.addr == addr) && (xrelayMultiTunnel.port == port)) {
 | 
			
		||||
                if ((xrelayMultiTunnel.domain.id == domain.id) && (xrelayMultiTunnel.userid == userid) && (xrelayMultiTunnel.nodeid == nodeid) && (xrelayMultiTunnel.addr == addr) && (xrelayMultiTunnel.port == port) && (xrelayMultiTunnel.appid == appid)) {
 | 
			
		||||
                    relayMultiTunnel = xrelayMultiTunnel; // We found an exact match
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +162,7 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
 | 
			
		|||
                req.session.rid = relayMultiTunnel.multiTunnelId;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Create the multi-tunnel
 | 
			
		||||
                relayMultiTunnel = require('./apprelays.js').CreateMultiWebRelay(parent, db, req, args, domain, userid, nodeid, addr, port);
 | 
			
		||||
                relayMultiTunnel = require('./apprelays.js').CreateMultiWebRelay(parent, db, req, args, domain, userid, nodeid, addr, port, appid);
 | 
			
		||||
                relayMultiTunnel.onclose = function (multiTunnelId) { delete obj.relayTunnels[multiTunnelId]; }
 | 
			
		||||
                relayMultiTunnel.multiTunnelId = nextMultiTunnelId++;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue