From 3f5e60b148a777753daf82269933fa7b4dbe9e21 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 5 Aug 2019 20:59:24 -0700 Subject: [PATCH] Completed first version of relay session recording. --- agents/meshcore.min.js | 12 ++--- meshrelay.js | 75 +++++++++++++++++++++++--- package.json | 2 +- public/scripts/agent-redir-ws-0.1.0.js | 2 +- views/default-min.handlebars | 2 +- views/default-mobile-min.handlebars | 2 +- 6 files changed, 77 insertions(+), 18 deletions(-) diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index 6b93e891..05975e7a 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -1072,7 +1072,7 @@ function createMeshCore(agent) pr.then( function () { // Success - MeshServerLog('Starting remote terminal after local user accepted (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Starting remote terminal after local user accepted (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) { // User Notifications is required @@ -1082,7 +1082,7 @@ function createMeshCore(agent) }, function (e) { // User Consent Denied/Failed - MeshServerLog('Failed to start remote terminal after local user rejected (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Failed to start remote terminal after local user rejected (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); this.ws.end(); }); @@ -1163,7 +1163,7 @@ function createMeshCore(agent) function () { // Success - MeshServerLog('Starting remote desktop after local user accepted (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Starting remote desktop after local user accepted (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) { // User Notifications is required @@ -1175,7 +1175,7 @@ function createMeshCore(agent) function (e) { // User Consent Denied/Failed - MeshServerLog('Failed to start remote desktop after local user rejected (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Failed to start remote desktop after local user rejected (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); }); } @@ -1220,7 +1220,7 @@ function createMeshCore(agent) pr.then( function () { // Success - MeshServerLog('Starting remote files after local user accepted (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Starting remote files after local user accepted (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) { // User Notifications is required @@ -1230,7 +1230,7 @@ function createMeshCore(agent) }, function (e) { // User Consent Denied/Failed - MeshServerLog('Failed to start remote files after local user rejected (' + this.httprequest.remoteaddr + ')', this.httprequest); + MeshServerLog('Failed to start remote files after local user rejected (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); }); } else { diff --git a/meshrelay.js b/meshrelay.js index 3f86bf72..c8885f24 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -166,15 +166,24 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Setup session recording var sessionUser = user; if (sessionUser == null) { sessionUser = obj.peer.user; } - if (domain.sessionrecord) { + if (domain.sessionrecording) { try { parent.parent.fs.mkdirSync(parent.parent.recordpath); } catch (e) { } - var recFilename = 'session' + ((domain.id == '')?'':'-') + domain.id + '-' + Date.now() + '-' + sessionUser.name + '-' + obj.id + '.mcrec' + var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + Date.now() + '-' + sessionUser.name + '-' + obj.id + '.mcrec' var recFullFilename = parent.parent.path.join(parent.parent.recordpath, recFilename); - //console.log('OpenLog'); parent.parent.fs.open(recFullFilename, 'w', function (err, fd) { - relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false }; - ws.send('c'); // Send connect to both peers - relayinfo.peer1.ws.send('c'); + // Write the recording file header + var firstBlock = Buffer.from(JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: sessionUser._id, username: sessionUser.name, sessionid: obj.id, ipaddr1: cleanRemoteAddr(ws._socket.remoteAddress), ipaddr2: cleanRemoteAddr(obj.peer.ws._socket.remoteAddress), time: new Date().toLocaleString() })); + var header = Buffer.alloc(16); // Type (2) + Flags (2) + Size(4) + Time(8) + header.writeInt16BE(1, 0); // Type (1 = Header, 2 = Network Data) + header.writeInt16BE(0, 2); // Flags (1 = Binary, 2 = User) + header.writeInt32BE(firstBlock.length, 4); // Size + header.writeIntBE(ws.time, 10, 6); // Time + var block = Buffer.concat([header, firstBlock]); + parent.parent.fs.write(fd, block, 0, block.length, function (err, bytesWritten, buffer) { + relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false }; + ws.send('c'); // Send connect to both peers + relayinfo.peer1.ws.send('c'); + }); }); } else { // Send session start @@ -233,14 +242,27 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Write data to log file then perform relay var xthis = this; try { + //console.log(obj); if (typeof data == 'string') { // String write - parent.parent.fs.write(this.logfile.fd, data, function (err, bytesWritten, buffer) { + var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8) + header.writeInt16BE(2, 0); // Type (1 = Header, 2 = Network Data) + header.writeInt16BE(((req.query.browser) ? 2 : 0), 2); // Flags (1 = Binary, 2 = User) + header.writeInt32BE(blockData.length, 4); // Size + header.writeIntBE(new Date(), 10, 6); // Time + var block = Buffer.concat([header, blockData]); + parent.parent.fs.write(this.logfile.fd, block, 0, block.length, function (err, bytesWritten, buffer) { xthis.peer.send(data, ws.flushSink); }); } else { // Binary write - parent.parent.fs.write(this.logfile.fd, data, 0, data.length, function (err, bytesWritten, buffer) { + var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8) + header.writeInt16BE(2, 0); // Type (1 = Header, 2 = Network Data) + header.writeInt16BE(((req.query.browser) ? 3 : 1), 2); // Flags (1 = Binary, 2 = User) + header.writeInt32BE(data.length, 4); // Size + header.writeIntBE(new Date(), 10, 6); // Time + var block = Buffer.concat([header, data]); + parent.parent.fs.write(this.logfile.fd, block, 0, block.length, function (err, bytesWritten, buffer) { xthis.peer.send(data, ws.flushSink); }); } @@ -368,3 +390,40 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie performRelay(); return obj; }; + +/* +Relay session recording required that "SessionRecording":true be set in the domain section of the config.json. +Once done, a folder "meshcentral-recordings" will be created next to "meshcentral-data" that will contain all +of the recording files with the .mcrec extension. + +The recording files are binary and contain a set of: + +
... + +The header is always 16 bytes long and is encoded like this: + + TYPE 2 bytes, 1 = Header, 2 = Network Data + FLAGS 2 bytes, 0x0001 = Binary, 0x0002 = User + SIZE 4 bytes, Size of the data following this header. + TIME 8 bytes, Time this record was written, number of milliseconds since 1 January, 1970 UTC. + +All values are BigEndian encoded. The first data block is of TYPE 1 and contains a JSON string with information +about this recording. It looks something like this: + +{ + magic: 'MeshCentralRelaySession', + ver: 1, + userid: "user\domain\userid", + username: "username", + sessionid: "RandomValue", + ipaddr1: 1.2.3.4, + ipaddr2: 1.2.3.5, + time: new Date().toLocaleString() +} + +The rest of the data blocks are all network traffic that was relayed thru the server. They are of TYPE 2 and have +a given size and timestamp. When looking at network traffic the flags are imporant. + +- If traffic has the first (0x0001) flag set, the data is binary otherwise it's a string. +- If the traffic has the second (0x0002) flag set, traffic is coming from the user's browser, if not, it's coming from the MeshAgent. +*/ \ No newline at end of file diff --git a/package.json b/package.json index 32c485a1..09243489 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.9-f", + "version": "0.3.9-g", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/agent-redir-ws-0.1.0.js b/public/scripts/agent-redir-ws-0.1.0.js index 49da8823..56d55d27 100644 --- a/public/scripts/agent-redir-ws-0.1.0.js +++ b/public/scripts/agent-redir-ws-0.1.0.js @@ -35,7 +35,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au //obj.debug = function (msg) { console.log(msg); } obj.Start = function (nodeid) { - var url2, url = window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/meshrelay.ashx?id=" + obj.tunnelid; + var url2, url = window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/meshrelay.ashx?browser=1&id=" + obj.tunnelid; //if (serverPublicNamePort) { url2 = window.location.protocol.replace("http", "ws") + "//" + serverPublicNamePort + "/meshrelay.ashx?id=" + obj.tunnelid; } else { url2 = url; } if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; } obj.nodeid = nodeid; diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 1a1a0846..565868eb 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile-min.handlebars b/views/default-mobile-min.handlebars index a40adfbd..2cce30c8 100644 --- a/views/default-mobile-min.handlebars +++ b/views/default-mobile-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file