Improved session recording support.

This commit is contained in:
Ylian Saint-Hilaire 2019-08-13 11:49:05 -07:00
parent 3b1ece80bd
commit fd09bebebc
14 changed files with 123 additions and 47 deletions

View File

@ -1982,7 +1982,7 @@ function OnWebSocket(msg, s, head) {
s.on('data', function (msg) {
if (this.parent.tunneling == false) {
msg = msg.toString();
if (msg == 'c') {
if ((msg == 'c') || (msg == 'cr')) {
this.parent.tunneling = true; this.pipe(this.parent.tcp); this.parent.tcp.pipe(this); debug(1, 'Tunnel active');
} else if ((msg.length > 6) && (msg.substring(0, 6) == 'error:')) {
console.log(msg.substring(6));

View File

@ -1042,7 +1042,7 @@ function createMeshCore(agent)
if (this.httprequest.state == 0) {
// Check if this is a relay connection
if (data == 'c') { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
} else {
// Handle tunnel data
if (this.httprequest.protocol == 0) { // 1 = Terminal, 2 = Desktop, 5 = Files, 6 = PowerShell

View File

@ -188,7 +188,7 @@ require('MeshAgent').AddCommandHandler(function (data)
if (this.httprequest.state == 0) {
// Check if this is a relay connection
if (data == 'c') { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid); }
} else {
// Handle tunnel data
if (this.httprequest.protocol == 0)

View File

@ -188,8 +188,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
var firstBlock = 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(), protocol: req.query.p, nodeid: req.query.nodeid });
recordingEntry(fd, 1, ((req.query.browser) ? 2 : 0), firstBlock, function () {
relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false };
ws.send('c'); // Send connect to both peers
relayinfo.peer1.ws.send('c');
ws.send('cr'); // Send connect to both peers, 'cr' indicates the session is being recorded.
relayinfo.peer1.ws.send('cr');
});
}
});
@ -285,7 +285,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
var peer = (relayinfo.peer1 == obj) ? relayinfo.peer2 : relayinfo.peer1;
// Close the recording file
if (ws.logfile != null) { parent.parent.fs.close(ws.logfile.fd); ws.logfile = null; peer.ws.logfile = null; }
if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, tag) { parent.parent.fs.close(fd); tag.ws.logfile = null; tag.pws.logfile = null; }, { ws: ws, pws: peer.ws }); }
// Disconnect the peer
try { if (peer.relaySessionCounted) { parent.relaySessionCount--; delete peer.relaySessionCounted; } } catch (ex) { console.log(ex); }
@ -327,7 +327,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
}
// Record a new entry in a recording log
function recordingEntry(fd, type, flags, data, func) {
function recordingEntry(fd, type, flags, data, func, tag) {
try {
if (typeof data == 'string') {
// String write
@ -337,7 +337,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
header.writeInt32BE(blockData.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, blockData]);
parent.parent.fs.write(fd, block, 0, block.length, func);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
} else {
// Binary write
var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
@ -346,9 +346,9 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
header.writeInt32BE(data.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, data]);
parent.parent.fs.write(fd, block, 0, block.length, func);
parent.parent.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
}
} catch (ex) { console.log(ex); func(); }
} catch (ex) { console.log(ex); func(fd, tag); }
}
// Mark this relay session as authenticated if this is the user end.
@ -418,7 +418,7 @@ 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
TYPE 2 bytes, 1 = Header, 2 = Network Data, 3 = EndBlock
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.

File diff suppressed because one or more lines are too long

View File

@ -84,6 +84,7 @@
var recFilePtr = 0;
var recFileStartTime = 0;
var recFileLastTime = 0;
var recFileEndTime = 0;
var recFileMetadata = null;
var recFileProtocol = 0;
var agentDesktop = null;
@ -106,18 +107,24 @@
}
function readNextBlock(func) {
if ((recFilePtr + 16) > recFile.size) { func(-1); } else {
if ((recFilePtr + 16) > recFile.size) { QS('progressbar').width = '100%'; func(-1); } else {
var fr = new FileReader();
fr.onload = function () {
var type = ReadShort(this.result, 0);
var flags = ReadShort(this.result, 2);
var size = ReadInt(this.result, 4);
var time = (ReadInt(this.result, 8) << 32) + ReadInt(this.result, 12);
if ((recFilePtr + 16 + size) > recFile.size) { func(-1); } else {
if ((recFilePtr + 16 + size) > recFile.size) { QS('progressbar').width = '100%'; func(-1); } else {
var fr2 = new FileReader();
fr2.onload = function () {
recFilePtr += (16 + size);
QS('progressbar').width = Math.floor(100 * (recFilePtr / recFile.size)) + '%';
if (recFileEndTime == 0) {
// File pointer progress bar
QS('progressbar').width = Math.floor(100 * (recFilePtr / recFile.size)) + '%';
} else {
// Time progress bar
QS('progressbar').width = Math.floor(((recFileLastTime - recFileStartTime) / (recFileEndTime - recFileStartTime)) * 100) + '%';
}
func(type, flags, time, this.result);
};
fr2.readAsBinaryString(recFile.slice(recFilePtr + 16, recFilePtr + 16 + size));
@ -127,6 +134,20 @@
}
}
function readLastBlock(func) {
if (recFile.size < 32) { func(-1); } else {
var fr = new FileReader();
fr.onload = function () {
var type = ReadShort(this.result, 0);
var flags = ReadShort(this.result, 2);
var size = ReadInt(this.result, 4);
var time = (ReadInt(this.result, 8) << 32) + ReadInt(this.result, 12);
if ((type == 3) && (size == 16) && (this.result.substring(16, 32) == 'MeshCentralMCREC')) { func(type, flags, time); } else { func(-1); }
};
fr.readAsBinaryString(recFile.slice(recFile.size - 32, recFile.size));
}
}
function addInfo(name, value) { if (value == null) return ''; return addInfoNoEsc(name, EscapeHtml(value)); }
function addInfoNoEsc(name, value) {
@ -141,6 +162,7 @@
if ((recFileMetadata == null) || (recFileMetadata.magic != 'MeshCentralRelaySession') || (recFileMetadata.ver != 1)) { cleanup(); return; }
var x = '';
x += addInfo('Time', recFileMetadata.time);
if (recFileEndTime != 0) { var secs = Math.floor((recFileEndTime - time) / 1000); x += addInfo('Duration', secs + ' second' + ((secs > 1) ? 's' : '')); }
x += addInfo('Username', recFileMetadata.username);
x += addInfo('UserID', recFileMetadata.userid);
x += addInfo('SessionID', recFileMetadata.sessionid);
@ -178,6 +200,7 @@
amtDesktop = CreateAmtRemoteDesktop('Desk');
amtDesktop.onScreenSizeChange = deskAdjust;
amtDesktop.State = 3;
amtDesktop.Start();
deskAdjust();
}
QV('metadatadiv', true);
@ -213,15 +236,20 @@
}
if ((type == 2) && flagBinary && !flagUser) {
// Device --> User data
if (recFileProtocol == 2) {
// MeshCentral Remote Desktop
agentDesktop.ProcessData(data);
} else if (recFileProtocol == 101) {
// Intel AMT KVM
//if ((readState == 0) && (rstr2hex(data) == '140000000400000000')) { readState = 1; }
if ((readState == 0) && (rstr2hex(data) == '4100000000000000')) { readState = 1; }
if ((readState == 0) && (rstr2hex(data) == '4100000000000000')) { readState = 1; } // We are not authenticated, KVM data starts here.
else if (readState == 1) { amtDesktop.ProcessData(data); }
//console.log(rstr2hex(data));
}
} else if ((type == 2) && flagBinary && flagUser) {
// User --> Device data
if (recFileProtocol == 101) {
// Intel AMT KVM
if (rstr2hex(data) == '0000000008080001000700070003050200000000') { amtDesktop.bpp = 1; } // Switch to 1 byte per pixel.
}
}
@ -238,6 +266,7 @@
readState = 0;
waitTimerArgs = null;
currentDeltaTimeTotalSec = 0;
recFileEndTime = 0;
if (waitTimer != null) { clearTimeout(waitTimer); waitTimer = null; }
QH('deskstatus', '');
QE('PlayButton', false);
@ -267,6 +296,7 @@
recFile = files[0];
recFilePtr = 0;
readNextBlock(processFirstBlock);
readLastBlock(function (type, flags, time) { if (type == 3) { recFileEndTime = time; } else { recFileEndTime = 0; } });
}
var dragtimer = null;
@ -309,6 +339,7 @@
recFile = files[0];
recFilePtr = 0;
readNextBlock(processFirstBlock);
readLastBlock(function (type, flags, time) { if (type == 3) { recFileEndTime = time; } else { recFileEndTime = 0; } });
Q('OpenFileButton').blur();
}
@ -358,13 +389,18 @@
QE('RestartButton', false);
QS('progressbar').width = '0px';
QH('timespan', '00:00:00');
if (agentDesktop) { agentDesktop.Canvas.clearRect(0, 0, agentDesktop.CanvasId.width, agentDesktop.CanvasId.height); }
if (amtDesktop) { amtDesktop.canvas.clearRect(0, 0, amtDesktop.CanvasId.width, amtDesktop.CanvasId.height); amtDesktop = CreateAmtRemoteDesktop('Desk'); amtDesktop.onScreenSizeChange = deskAdjust; amtDesktop.State = 3; }
if (agentDesktop) {
agentDesktop.Canvas.clearRect(0, 0, agentDesktop.CanvasId.width, agentDesktop.CanvasId.height);
} else if (amtDesktop) {
amtDesktop.canvas.clearRect(0, 0, amtDesktop.CanvasId.width, amtDesktop.CanvasId.height);
amtDesktop = CreateAmtRemoteDesktop('Desk');
amtDesktop.onScreenSizeChange = deskAdjust;
amtDesktop.State = 3;
amtDesktop.Start();
}
}
function clearConsoleMsg() {
console.log('clearConsoleMsg');
}
function clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); }
// Toggle the web page to full screen
function toggleAspectRatio(toggle) {
@ -404,9 +440,9 @@
} else {
var wNew = ((deskW * parentH) / deskH) + 'px';
//if (webPageFullScreen || fullscreen) {
//QS('Desk').height = null;
//QS('Desk').height = null;
//} else {
QS('Desk').height = '100%';
QS('Desk').height = '100%';
//}
QS('Desk').width = wNew;
}

View File

@ -25,6 +25,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
obj.webchannel = null;
obj.webrtc = null;
obj.debugmode = 0;
obj.serverIsRecording = false;
if (domainUrl == null) { domainUrl = '/'; }
// Console Message
@ -95,7 +96,8 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
obj.xxOnMessage = function (e) {
//console.log('Recv', e.data, e.data.byteLength, obj.State);
if (obj.State < 3) {
if (e.data == 'c') {
if ((e.data == 'c') || (e.data == 'cr')) {
if (e.data == 'cr') { obj.serverIsRecording = true; }
try { obj.socket.send(obj.protocol); } catch (ex) { }
obj.xxStateChange(3);

View File

@ -245,6 +245,11 @@ var CreateAmtRedirect = function (module, authCookie) {
if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); }
cmdsize = obj.amtaccumulator.length;
break;
case 0xF0:
// console.log('Session is being recorded');
obj.serverIsRecording = true;
cmdsize = 1;
break;
default:
console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length);
obj.Stop(4);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -443,6 +443,7 @@
<div class='deskareaicon' title="Toggle View Mode" onclick="toggleAspectRatio(1)">&#8690;</div>
<div class='deskareaicon' title="Rotate Left" onclick="drotate(-1)">&olarr;</div>
<div class='deskareaicon' title="Rotate Right" onclick="drotate(1)">&orarr;</div>
<div id="deskRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px"></div>
<input id="deskFocusBtn" type="button" title="Toggle focus mode, when active only the region around the mouse is updated" onkeypress="return false" onkeydown="return false" value="Focus All" onclick="deskToggleFocus()" style="margin-right:3px;display:none">
<input id="deskSaveBtn" type="button" title="Save a screenshot of the remote desktop" onkeypress="return false" onkeydown="return false" value="Save..." onclick=deskSaveImage() class="mR">
<input id="deskActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() class="mR" />
@ -541,6 +542,7 @@
<tr>
<td class="areaHead">
<div class="toright2">
<div id="termRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
<input id="termActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
</div>
<div>
@ -600,6 +602,7 @@
<td class="areaHead">
<div class="toright2">
<input id="filesActionsBtn" type=button title="Perform power actions on the device" value=Actions onclick=deviceActionFunction() />
<div id="filesRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
</div>
<div>
<input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) type="button" style="display:none">
@ -4816,6 +4819,7 @@
desktopNode = desktop = null;
QV('DeskFocus', false);
QV('termdisplays', false);
QV('deskRecordIcon', false);
deskFocusBtn.value = 'All Focus';
if (fullscreen == true) { deskToggleFull(); }
webRtcDesktopReset();
@ -4823,6 +4827,9 @@
break;
case 2:
break;
case 3:
if (desktop.serverIsRecording == true) { QV('deskRecordIcon', true); }
break;
default:
//console.log('Unknown onDesktopStateChange state', state);
break;
@ -5380,12 +5387,14 @@
// Disconnected, clear the terminal
QE('termSizeList', true);
QH('termtitle', '');
QV('termRecordIcon', false);
xterminal.m.TermResetScreen();
xterminal.m.TermDraw();
if (terminal != null) { terminal.Stop(); terminal = null; }
break;
case 3:
QE('termSizeList', false);
if (xterminal.serverIsRecording == true) { QV('termRecordIcon', true); }
break;
default:
QE('termSizeList', false);
@ -5531,12 +5540,14 @@
p13filetreelocation = [];
QH('p13currentpath', '');
QE('p13FolderUp', false);
QV('filesRecordIcon', false);
p13setActions();
if (files != null) { files.Stop(); files = null; }
break;
case 3:
p13targetpath = '';
files.sendText({ action: 'ls', reqid: 1, path: '' });
if (files.serverIsRecording == true) { QV('filesRecordIcon', true); }
break;
default:
//console.log('Unknown onFilesStateChange state', state);

File diff suppressed because one or more lines are too long

View File

@ -579,7 +579,7 @@
socket.onerror = function (e) { /*console.error(e);*/ }
socket.onclose = function () { disconnect(); }
socket.onmessage = function (msg) {
if ((state < 2) && (typeof msg.data == 'string') && (msg.data == 'c')) {
if ((state < 2) && (typeof msg.data == 'string') && ((msg.data == 'c') || (msg.data == 'cr'))) {
hangUpButtonClick(0, true);
hangUpButtonClick(1, true);
hangUpButtonClick(2, true);

View File

@ -1978,6 +1978,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: cleanRemoteAddr(ws._socket.remoteAddress), nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() })
recordingEntry(fd, 1, 0, firstBlock, function () { });
ws.logfile = { fd: fd, lock: false };
if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded
}
}
@ -2073,7 +2074,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
// Close the recording file
if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
if (ws.logfile != null) {
recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) {
obj.fs.close(fd);
ws.logfile = null;
}, ws);
}
});
// If the web socket is closed, close the associated TCP connection.
@ -2082,7 +2088,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (ws.forwardclient && ws.forwardclient.close) { ws.forwardclient.close(); } // TODO: If TLS is used, we need to close the socket that is wrapped by TLS
// Close the recording file
if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
if (ws.logfile != null) {
recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) {
obj.fs.close(fd);
ws.logfile = null;
}, ws);
}
});
ws.forwardclient.onStateChange = function (ciraconn, state) {
@ -2156,7 +2167,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
// Close the recording file
if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
if (ws.logfile != null) {
recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
obj.fs.close(fd);
ws.logfile = null;
});
}
});
// If the web socket is closed, close the associated TCP connection.
@ -2165,7 +2181,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
// Close the recording file
if (ws.logfile != null) { obj.fs.close(ws.logfile.fd); ws.logfile = null; }
if (ws.logfile != null) {
recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
obj.fs.close(fd);
ws.logfile = null;
});
}
});
// Compute target port
@ -3467,7 +3488,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }
// Record a new entry in a recording log
function recordingEntry(fd, type, flags, data, func) {
function recordingEntry(fd, type, flags, data, func, tag) {
try {
if (typeof data == 'string') {
// String write
@ -3477,7 +3498,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
header.writeInt32BE(blockData.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, blockData]);
obj.fs.write(fd, block, 0, block.length, func);
obj.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
} else {
// Binary write
var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
@ -3486,9 +3507,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
header.writeInt32BE(data.length, 4); // Size
header.writeIntBE(new Date(), 10, 6); // Time
var block = Buffer.concat([header, data]);
obj.fs.write(fd, block, 0, block.length, func);
obj.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
}
} catch (ex) { console.log(ex); func(); }
} catch (ex) { console.log(ex); func(fd, tag); }
}
return obj;