add amt relay events and fixamt recordings #6652

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed Apr 13, 2025 at 19:11 UTC c24928255421d4e75b3fdd518f67ec6bb14aacc0
5 files changed +262 -123
meshrelay.js
+67 -65
@@ -78,6 +78,72 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
78 }
79 }
80
81 +// Record a new entry in a recording log
82 +function recordingEntry (logfile, type, flags, data, func, tag) {
83 + try {
84 + if (logfile.text) {
85 + // Text recording format
86 + var out = '';
87 + const utcDate = new Date(Date.now());
88 + if (type == 1) {
89 + // End of start
90 + out = data + '\r\n' + utcDate.toUTCString() + ', ' + "<<<START>>>" + '\r\n';
91 + } else if (type == 3) {
92 + // End of log
93 + out = utcDate.toUTCString() + ', ' + "<<<END>>>" + '\r\n';
94 + } else if (typeof data == 'string') {
95 + // Log message
96 + if (logfile.text == 1) {
97 + out = utcDate.toUTCString() + ', ' + data + '\r\n';
98 + } else if (logfile.text == 2) {
99 + try {
100 + var x = JSON.parse(data);
101 + if (typeof x.action == 'string') {
102 + if ((x.action == 'chat') && (typeof x.msg == 'string')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + x.msg + '\r\n'); }
103 + else if ((x.action == 'file') && (typeof x.name == 'string') && (typeof x.size == 'number')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + "File Transfer" + ', \"' + x.name + '\" (' + x.size + ' ' + "bytes" + ')\r\n'); }
104 + } else if (x.ctrlChannel == null) { out = utcDate.toUTCString() + ', ' + data + '\r\n'; }
105 + } catch (ex) {
106 + out = utcDate.toUTCString() + ', ' + data + '\r\n';
107 + }
108 + }
109 + }
110 + if (out != null) {
111 + // Log this event
112 + const block = Buffer.from(out);
113 + require('fs').write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
114 + logfile.size += block.length;
115 + } else {
116 + // Skip logging this.
117 + func(logfile, tag);
118 + }
119 + } else {
120 + // Binary recording format
121 + if (typeof data == 'string') {
122 + // String write
123 + var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
124 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
125 + header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
126 + header.writeInt32BE(blockData.length, 4); // Size
127 + header.writeIntBE(new Date(), 10, 6); // Time
128 + var block = Buffer.concat([header, blockData]);
129 + require('fs').write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
130 + logfile.size += block.length;
131 + } else {
132 + // Binary write
133 + var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
134 + header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
135 + header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
136 + header.writeInt32BE(data.length, 4); // Size
137 + header.writeIntBE(new Date(), 10, 6); // Time
138 + var block = Buffer.concat([header, data]);
139 + require('fs').write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
140 + logfile.size += block.length;
141 + }
142 + }
143 + } catch (ex) { console.log(ex); func(logfile, tag); }
144 +}
145 +module.exports.recordingEntry = recordingEntry;
146 +
147 function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
148 const currentTime = Date.now();
149 if (cookie) {
@@ -751,6 +817,7 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
817 setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
818 recordingEntry(logfile, 3, 0, 'MeshCentralMCREC', function (logfile, tag) {
819 parent.parent.fs.closeSync(logfile.fd);
820 + parent.parent.debug('relay', 'Relay: Finished recording to file: ' + tag.logfile.filename);
821
822 // Now that the recording file is closed, check if we need to index this file.
823 if (domain.sessionrecording.index && domain.sessionrecording.index !== false) { parent.parent.certificateOperations.acceleratorPerformOperation('indexMcRec', tag.logfile.filename); }
@@ -796,71 +863,6 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
863 if (obj.pid != null) { parent.parent.RemoveAllEventDispatch(obj); }
864 }
865
799 - // Record a new entry in a recording log
800 - function recordingEntry(logfile, type, flags, data, func, tag) {
801 - try {
802 - if (logfile.text) {
803 - // Text recording format
804 - var out = '';
805 - const utcDate = new Date(Date.now());
806 - if (type == 1) {
807 - // End of start
808 - out = data + '\r\n' + utcDate.toUTCString() + ', ' + "<<<START>>>" + '\r\n';
809 - } else if (type == 3) {
810 - // End of log
811 - out = utcDate.toUTCString() + ', ' + "<<<END>>>" + '\r\n';
812 - } else if (typeof data == 'string') {
813 - // Log message
814 - if (logfile.text == 1) {
815 - out = utcDate.toUTCString() + ', ' + data + '\r\n';
816 - } else if (logfile.text == 2) {
817 - try {
818 - var x = JSON.parse(data);
819 - if (typeof x.action == 'string') {
820 - if ((x.action == 'chat') && (typeof x.msg == 'string')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + x.msg + '\r\n'); }
821 - else if ((x.action == 'file') && (typeof x.name == 'string') && (typeof x.size == 'number')) { out = utcDate.toUTCString() + ', ' + (((flags & 2) ? '--> ' : '<-- ') + "File Transfer" + ', \"' + x.name + '\" (' + x.size + ' ' + "bytes" + ')\r\n'); }
822 - } else if (x.ctrlChannel == null) { out = utcDate.toUTCString() + ', ' + data + '\r\n'; }
823 - } catch (ex) {
824 - out = utcDate.toUTCString() + ', ' + data + '\r\n';
825 - }
826 - }
827 - }
828 - if (out != null) {
829 - // Log this event
830 - const block = Buffer.from(out);
831 - parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
832 - logfile.size += block.length;
833 - } else {
834 - // Skip logging this.
835 - func(logfile, tag);
836 - }
837 - } else {
838 - // Binary recording format
839 - if (typeof data == 'string') {
840 - // String write
841 - var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
842 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
843 - header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
844 - header.writeInt32BE(blockData.length, 4); // Size
845 - header.writeIntBE(new Date(), 10, 6); // Time
846 - var block = Buffer.concat([header, blockData]);
847 - parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
848 - logfile.size += block.length;
849 - } else {
850 - // Binary write
851 - var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
852 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
853 - header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
854 - header.writeInt32BE(data.length, 4); // Size
855 - header.writeIntBE(new Date(), 10, 6); // Time
856 - var block = Buffer.concat([header, data]);
857 - parent.parent.fs.write(logfile.fd, block, 0, block.length, function () { func(logfile, tag); });
858 - logfile.size += block.length;
859 - }
860 - }
861 - } catch (ex) { console.log(ex); func(logfile, tag); }
862 - }
863 -
866 // If this session has a expire time, setup the expire timer now.
867 setExpireTimer();
868
views/default.handlebars
+1 -1
@@ -9740,7 +9740,7 @@
9740 var rdpflags = 0;
9741 for (var i = 1; i < 10; i++) { if ((i != 5) && (Q('d7rdp' + i).checked)) { rdpflags |= (1 << (i - 1)); } }
9742 desktopsettings.rdpflags = rdpflags;
9743 - localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
9743 + putstore('desktopsettings', JSON.stringify(desktopsettings));
9744 applyDesktopSettings();
9745 updateDesktopButtons();
9746 if (desktop) {
views/default3.handlebars
+1 -1
@@ -10506,7 +10506,7 @@
10506 var rdpflags = 0;
10507 for (var i = 1; i < 10; i++) { if ((i != 5) && (Q('d7rdp' + i).checked)) { rdpflags |= (1 << (i - 1)); } }
10508 desktopsettings.rdpflags = rdpflags;
10509 - localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
10509 + putstore('desktopsettings', JSON.stringify(desktopsettings));
10510 applyDesktopSettings();
10511 updateDesktopButtons();
10512 if (desktop) {
views/player.handlebars
+1 -1
@@ -577,7 +577,7 @@
577 var view = new Uint8Array(data.length);
578 for (var i = 0; i < data.length; i++) { view[i] = data.charCodeAt(i); }
579
580 - if ((readState == 0) && (rstr2hex(data) == '4100000000000000')) {
580 + if ((readState == 0) && (rstr2hex(data).startsWith('4100000000000000'))) {
581 // We are not authenticated, KVM data starts here.
582 readState = 1;
583 if (data.length > 8) { amtDesktop.ProcessBinaryData(view.slice(8).buffer); }
webserver.js
+192 -55
@@ -4727,8 +4727,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4727 // Fetch information about the target
4728 obj.db.Get(req.query.host, function (err, docs) {
4729 if (docs.length == 0) { console.log('ERR: Node not found'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4730 - var node = docs[0];
4730 + var xusername = '', xdevicename = '', xdevicename2 = null, node = null;
4731 + node = docs[0]; xdevicename2 = node.name; xdevicename = '-' + parent.common.makeFilename(node.name); ws.id = getRandomPassword(); ws.time = Date.now();
4732 if (!node.intelamt) { console.log('ERR: Not AMT node'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
4733 + var ciraconn = parent.mpsserver.GetConnectionToNode(req.query.host, null, false);
4734
4735 // Check if this user has permission to manage this computer
4736 if ((obj.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_REMOTECONTROL) == 0) { console.log('ERR: Access denied (3)'); try { ws.close(); } catch (e) { } return; }
@@ -4782,7 +4784,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4784
4785 if (record == true) {
4786 var now = new Date(Date.now());
4785 - var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + obj.common.zeroPad(now.getUTCMonth() + 1, 2) + '-' + obj.common.zeroPad(now.getUTCDate(), 2) + '-' + obj.common.zeroPad(now.getUTCHours(), 2) + '-' + obj.common.zeroPad(now.getUTCMinutes(), 2) + '-' + obj.common.zeroPad(now.getUTCSeconds(), 2) + '-' + getRandomPassword() + '.mcrec'
4787 + // Get the username and make it acceptable as a filename
4788 + if (user._id) { xusername = '-' + parent.common.makeFilename(user._id.split('/')[2]); }
4789 + var xsessionid = ws.id;
4790 + var recFilename = 'relaysession' + ((domain.id == '') ? '' : '-') + domain.id + '-' + now.getUTCFullYear() + '-' + obj.common.zeroPad(now.getUTCMonth() + 1, 2) + '-' + obj.common.zeroPad(now.getUTCDate(), 2) + '-' + obj.common.zeroPad(now.getUTCHours(), 2) + '-' + obj.common.zeroPad(now.getUTCMinutes(), 2) + '-' + obj.common.zeroPad(now.getUTCSeconds(), 2) + xusername + xdevicename + '-' + xsessionid + '.mcrec';
4791 var recFullFilename = null;
4792 if (domain.sessionrecording.filepath) {
4793 try { obj.fs.mkdirSync(domain.sessionrecording.filepath); } catch (e) { }
@@ -4794,16 +4799,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4799 var fd = obj.fs.openSync(recFullFilename, 'w');
4800 if (fd != null) {
4801 // Write the recording file header
4797 - var firstBlock = JSON.stringify({ magic: 'MeshCentralRelaySession', ver: 1, userid: user._id, username: user.name, ipaddr: req.clientIp, nodeid: node._id, intelamt: true, protocol: (req.query.p == 2) ? 101 : 100, time: new Date().toLocaleString() })
4798 - recordingEntry(fd, 1, 0, firstBlock, function () { });
4799 - ws.logfile = { fd: fd, lock: false };
4802 + parent.debug('relay', 'Relay: Started recording to file: ' + recFullFilename);
4803 + var metadata = {
4804 + magic: 'MeshCentralRelaySession',
4805 + ver: 1,
4806 + userid: user._id,
4807 + username: user.name,
4808 + sessionid: ws.id,
4809 + ipaddr1: req.clientIp,
4810 + time: new Date().toLocaleString(),
4811 + protocol: (req.query.p == 2) ? 101 : 100,
4812 + nodeid: node._id,
4813 + intelamt: true
4814 + };
4815 + if (ciraconn != null) { metadata.ipaddr2 = ciraconn.remoteAddr; }
4816 + else if ((conn & 4) != 0) { metadata.ipaddr2 = node.host; }
4817 + if (xdevicename2 != null) { metadata.devicename = xdevicename2; }
4818 + var firstBlock = JSON.stringify(metadata)
4819 + ws.logfile = { fd: fd, lock: false, filename: recFullFilename, startTime: Date.now(), size: 0, text: 0, req: req };
4820 + obj.meshRelayHandler.recordingEntry(ws.logfile, 1, 0, firstBlock, function () { });
4821 + if (node != null) { ws.logfile.nodeid = node._id; ws.logfile.meshid = node.meshid; ws.logfile.name = node.name; ws.logfile.icon = node.icon; }
4822 if (req.query.p == 2) { ws.send(Buffer.from(String.fromCharCode(0xF0), 'binary')); } // Intel AMT Redirection: Indicate the session is being recorded
4823 }
4824 }
4825 }
4826
4827 // If Intel AMT CIRA connection is available, use it
4806 - var ciraconn = parent.mpsserver.GetConnectionToNode(req.query.host, null, false);
4828 if (ciraconn != null) {
4829 parent.debug('web', 'Opening relay CIRA channel connection to ' + req.query.host + '.');
4830
@@ -4865,7 +4886,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4886 try { ws.send(data); } catch (e) { }
4887 } else {
4888 // Log to recording file
4868 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } }); // TODO: Add TLS support
4889 + obj.meshRelayHandler.recordingEntry(ws.logfile, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } }); // TODO: Add TLS support
4890 }
4891 }
4892 };
@@ -4897,7 +4918,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4918 try { ws.send(data); } catch (e) { }
4919 } else {
4920 // Log to recording file
4900 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } });
4921 + obj.meshRelayHandler.recordingEntry(ws.logfile, 2, 0, data, function () { try { ws.send(data); } catch (ex) { console.log(ex); } });
4922 }
4923 }
4924 };
@@ -4921,7 +4942,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4942 try { ws.forwardclient.write(data); } catch (ex) { }
4943 } else {
4944 // Log to recording file
4924 - recordingEntry(ws.logfile.fd, 2, 2, data, function () { try { ws.forwardclient.write(data); } catch (ex) { } });
4945 + obj.meshRelayHandler.recordingEntry(ws.logfile, 2, 2, data, function () { try { ws.forwardclient.write(data); } catch (ex) { } });
4946 }
4947 });
4948
@@ -4930,6 +4951,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4951 console.log('CIRA server websocket error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
4952 parent.debug('webrelay', 'Websocket relay closed on error.');
4953
4954 + // Log the disconnection
4955 + if (ws.time) {
4956 + var msg = 'Ended relay session', msgid = 9, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
4957 + var nodeid = node._id;
4958 + var meshid = node.meshid;
4959 + if (user) {
4960 + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip, Math.floor((Date.now() - ws.time) / 1000)], msg: msg + ' \"' + ws.id + '\" from ' + req.clientIp + ' to ' + ip + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: ((req.query.p == 2) ? 101 : 100), nodeid: nodeid };
4961 + obj.parent.DispatchEvent(['*', user._id, nodeid, meshid], obj, event);
4962 + }
4963 + }
4964 +
4965 // Websocket closed, close the CIRA channel and TLS session.
4966 if (ws.forwardclient) {
4967 if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
@@ -4939,13 +4971,47 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4971 }
4972
4973 // Close the recording file
4942 - if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
4974 + if (ws.logfile != null) {
4975 + setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
4976 + obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
4977 + obj.fs.close(logfile.fd);
4978 + parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
4979 + // Compute session length
4980 + var sessionLength = null;
4981 + if (ws.logfile.startTime != null) { sessionLength = Math.round((Date.now() - ws.logfile.startTime) / 1000); }
4982 + // Add a event entry about this recording
4983 + var basefile = parent.path.basename(ws.logfile.filename);
4984 + var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: ws.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: ws.logfile.size };
4985 + if (user) { event.userids = [user._id]; } else if (peer.user) { event.userids = [peer.user._id]; }
4986 + var xprotocol = (((ws.logfile.req == null) || (ws.logfile.req.query == null)) ? null : (ws.logfile.req.query.p == 2) ? 101 : 100);
4987 + if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
4988 + var mesh = obj.meshes[ws.logfile.meshid];
4989 + if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
4990 + if (ws.logfile.startTime) { event.startTime = ws.logfile.startTime; event.lengthTime = sessionLength; }
4991 + if (ws.logfile.name) { event.name = ws.logfile.name; }
4992 + if (ws.logfile.icon) { event.icon = ws.logfile.icon; }
4993 + obj.parent.DispatchEvent(['*', 'recording', ws.logfile.nodeid, ws.logfile.meshid], obj, event);
4994 + delete ws.logfile;
4995 + }, ws);
4996 + }, 5000);
4997 + }
4998 });
4999
5000 // If the web socket is closed, close the associated TCP connection.
4946 - ws.on('close', function (req) {
5001 + ws.on('close', function () {
5002 parent.debug('webrelay', 'Websocket relay closed.');
5003
5004 + // Log the disconnection
5005 + if (ws.time) {
5006 + var msg = 'Ended relay session', msgid = 9, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
5007 + var nodeid = node._id;
5008 + var meshid = node.meshid;
5009 + if (user) {
5010 + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip, Math.floor((Date.now() - ws.time) / 1000)], msg: msg + ' \"' + ws.id + '\" from ' + req.clientIp + ' to ' + ip + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: ((req.query.p == 2) ? 101 : 100), nodeid: nodeid };
5011 + obj.parent.DispatchEvent(['*', user._id, nodeid, meshid], obj, event);
5012 + }
5013 + }
5014 +
5015 // Websocket closed, close the CIRA channel and TLS session.
5016 if (ws.forwardclient) {
5017 if (ws.forwardclient.close) { ws.forwardclient.close(); } // NonTLS, close the CIRA channel
@@ -4955,7 +5021,30 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5021 }
5022
5023 // Close the recording file
4958 - if (ws.logfile != null) { recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd, ws) { obj.fs.close(fd); delete ws.logfile; }, ws); }
5024 + if (ws.logfile != null) {
5025 + setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
5026 + obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
5027 + obj.fs.close(logfile.fd);
5028 + parent.debug('relay', 'Relay1: Finished recording to file: ' + ws.logfile.filename);
5029 + // Compute session length
5030 + var sessionLength = null;
5031 + if (ws.logfile.startTime != null) { sessionLength = Math.round((Date.now() - ws.logfile.startTime) / 1000); }
5032 + // Add a event entry about this recording
5033 + var basefile = parent.path.basename(ws.logfile.filename);
5034 + var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: ws.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: ws.logfile.size };
5035 + if (user) { event.userids = [user._id]; }
5036 + var xprotocol = (((ws.logfile.req == null) || (ws.logfile.req.query == null)) ? null : (ws.logfile.req.query.p == 2) ? 101 : 100);
5037 + if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
5038 + var mesh = obj.meshes[ws.logfile.meshid];
5039 + if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
5040 + if (ws.logfile.startTime) { event.startTime = ws.logfile.startTime; event.lengthTime = sessionLength; }
5041 + if (ws.logfile.name) { event.name = ws.logfile.name; }
5042 + if (ws.logfile.icon) { event.icon = ws.logfile.icon; }
5043 + obj.parent.DispatchEvent(['*', 'recording', ws.logfile.nodeid, ws.logfile.meshid], obj, event);
5044 + delete ws.logfile;
5045 + }, ws);
5046 + }, 5000);
5047 + }
5048 });
5049
5050 // Note that here, req.query.p: 1 = WSMAN with server auth, 2 = REDIR with server auth, 3 = WSMAN without server auth, 4 = REDIR with server auth
@@ -4970,12 +5059,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5059 ws.interceptor = obj.interceptor.CreateRedirInterceptor({ user: node.intelamt.user, pass: node.intelamt.pass });
5060 ws.interceptor.blockAmtStorage = true;
5061 }
4973 -
4974 - return;
4975 - }
4976 -
4977 - // If Intel AMT direct connection is possible, option a direct socket
4978 - if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
5062 + } else if ((conn & 4) != 0) { // If Intel AMT direct connection is possible, option a direct socket
5063 + // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
5064 parent.debug('webrelay', 'Opening relay TCP socket connection to ' + req.query.host + '.');
5065
5066 // When data is received from the web socket, forward the data into the associated TCP connection.
@@ -4991,7 +5076,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5076 try { ws.forwardclient.write(msg); } catch (ex) { }
5077 } else {
5078 // Log to recording file
4994 - recordingEntry(ws.logfile.fd, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
5079 + obj.meshRelayHandler.recordingEntry(ws.logfile, 2, 2, msg, function () { try { ws.forwardclient.write(msg); } catch (ex) { } });
5080 }
5081 });
5082
@@ -4999,28 +5084,84 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5084 ws.on('error', function (err) {
5085 console.log('Error with relay web socket connection from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.');
5086 parent.debug('webrelay', 'Error with relay web socket connection from ' + req.clientIp + '.');
5087 + // Log the disconnection
5088 + if (ws.time) {
5089 + var msg = 'Ended relay session', msgid = 9, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
5090 + var nodeid = node._id;
5091 + var meshid = node.meshid;
5092 + if (user) {
5093 + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip, Math.floor((Date.now() - ws.time) / 1000)], msg: msg + ' \"' + ws.id + '\" from ' + req.clientIp + ' to ' + ip + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: ((req.query.p == 2) ? 101 : 100), nodeid: nodeid };
5094 + obj.parent.DispatchEvent(['*', user._id, nodeid, meshid], obj, event);
5095 + }
5096 + }
5097 if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
5098
5099 // Close the recording file
5100 if (ws.logfile != null) {
5006 - recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
5007 - obj.fs.close(fd);
5008 - ws.logfile = null;
5009 - });
5101 + setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
5102 + obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
5103 + obj.fs.close(logfile.fd);
5104 + parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
5105 + // Compute session length
5106 + var sessionLength = null;
5107 + if (ws.logfile.startTime != null) { sessionLength = Math.round((Date.now() - ws.logfile.startTime) / 1000); }
5108 + // Add a event entry about this recording
5109 + var basefile = parent.path.basename(ws.logfile.filename);
5110 + var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: ws.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: ws.logfile.size };
5111 + if (user) { event.userids = [user._id]; } else if (peer.user) { event.userids = [peer.user._id]; }
5112 + var xprotocol = (((ws.logfile.req == null) || (ws.logfile.req.query == null)) ? null : (ws.logfile.req.query.p == 2) ? 101 : 100);
5113 + if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
5114 + var mesh = obj.meshes[ws.logfile.meshid];
5115 + if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
5116 + if (ws.logfile.startTime) { event.startTime = ws.logfile.startTime; event.lengthTime = sessionLength; }
5117 + if (ws.logfile.name) { event.name = ws.logfile.name; }
5118 + if (ws.logfile.icon) { event.icon = ws.logfile.icon; }
5119 + obj.parent.DispatchEvent(['*', 'recording', ws.logfile.nodeid, ws.logfile.meshid], obj, event);
5120 + delete ws.logfile;
5121 + }, ws);
5122 + }, 5000);
5123 }
5124 });
5125
5126 // If the web socket is closed, close the associated TCP connection.
5127 ws.on('close', function () {
5128 parent.debug('webrelay', 'Closing relay web socket connection to ' + req.query.host + '.');
5129 + // Log the disconnection
5130 + if (ws.time) {
5131 + var msg = 'Ended relay session', msgid = 9, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
5132 + var nodeid = node._id;
5133 + var meshid = node.meshid;
5134 + if (user) {
5135 + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip, Math.floor((Date.now() - ws.time) / 1000)], msg: msg + ' \"' + ws.id + '\" from ' + req.clientIp + ' to ' + ip + ', ' + Math.floor((Date.now() - ws.time) / 1000) + ' second(s)', protocol: ((req.query.p == 2) ? 101 : 100), nodeid: nodeid };
5136 + obj.parent.DispatchEvent(['*', user._id, nodeid, meshid], obj, event);
5137 + }
5138 + }
5139 if (ws.forwardclient) { try { ws.forwardclient.destroy(); } catch (e) { } }
5140
5141 // Close the recording file
5142 if (ws.logfile != null) {
5020 - recordingEntry(ws.logfile.fd, 3, 0, 'MeshCentralMCREC', function (fd) {
5021 - obj.fs.close(fd);
5022 - ws.logfile = null;
5023 - });
5143 + setTimeout(function(){ // wait 5 seconds before finishing file for some reason?
5144 + obj.meshRelayHandler.recordingEntry(ws.logfile, 3, 0, 'MeshCentralMCREC', function (logfile, ws) {
5145 + obj.fs.close(logfile.fd);
5146 + parent.debug('relay', 'Relay: Finished recording to file: ' + ws.logfile.filename);
5147 + // Compute session length
5148 + var sessionLength = null;
5149 + if (ws.logfile.startTime != null) { sessionLength = Math.round((Date.now() - ws.logfile.startTime) / 1000); }
5150 + // Add a event entry about this recording
5151 + var basefile = parent.path.basename(ws.logfile.filename);
5152 + var event = { etype: 'relay', action: 'recording', domain: domain.id, nodeid: ws.logfile.nodeid, msg: "Finished recording session" + (sessionLength ? (', ' + sessionLength + ' second(s)') : ''), filename: basefile, size: ws.logfile.size };
5153 + if (user) { event.userids = [user._id]; } else if (peer.user) { event.userids = [peer.user._id]; }
5154 + var xprotocol = (((ws.logfile.req == null) || (ws.logfile.req.query == null)) ? null : (ws.logfile.req.query.p == 2) ? 101 : 100);
5155 + if (xprotocol != null) { event.protocol = parseInt(xprotocol); }
5156 + var mesh = obj.meshes[ws.logfile.meshid];
5157 + if (mesh != null) { event.meshname = mesh.name; event.meshid = mesh._id; }
5158 + if (ws.logfile.startTime) { event.startTime = ws.logfile.startTime; event.lengthTime = sessionLength; }
5159 + if (ws.logfile.name) { event.name = ws.logfile.name; }
5160 + if (ws.logfile.icon) { event.icon = ws.logfile.icon; }
5161 + obj.parent.DispatchEvent(['*', 'recording', ws.logfile.nodeid, ws.logfile.meshid], obj, event);
5162 + delete ws.logfile;
5163 + }, ws);
5164 + }, 5000);
5165 }
5166 });
5167
@@ -5064,7 +5205,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5205 try { ws.send(data); } catch (e) { }
5206 } else {
5207 // Log to recording file
5067 - recordingEntry(ws.logfile.fd, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
5208 + obj.meshRelayHandler.recordingEntry(ws.logfile, 2, 0, data, function () { try { ws.send(data); } catch (e) { } });
5209 }
5210 });
5211
@@ -5092,9 +5233,30 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5233 ws._socket.resume();
5234 });
5235 }
5095 - return;
5236 }
5237
5238 + // Log the connection
5239 + if (user != null) {
5240 + var msg = 'Started relay session', msgid = 13, ip = ((ciraconn != null) ? ciraconn.remoteAddr : (((conn & 4) != 0) ? node.host : req.clientIp));
5241 + var event = { etype: 'relay', action: 'relaylog', domain: domain.id, userid: user._id, username: user.name, msgid: msgid, msgArgs: [ws.id, req.clientIp, ip], msg: msg + ' \"' + obj.id + '\" from ' + req.clientIp + ' to ' + ip, protocol: ((req.query.p == 2) ? 101 : 100), nodeid: node._id };
5242 + obj.parent.DispatchEvent(['*', user._id], obj, event);
5243 +
5244 + // Update user last access time
5245 + if ((user != null)) {
5246 + const timeNow = Math.floor(Date.now() / 1000);
5247 + if (user.access < (timeNow - 300)) { // Only update user access time if longer than 5 minutes
5248 + user.access = timeNow;
5249 + obj.parent.db.SetUser(user);
5250 +
5251 + // Event the change
5252 + var message = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 };
5253 + if (parent.db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
5254 + var targets = ['*', 'server-users', user._id];
5255 + if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
5256 + obj.parent.DispatchEvent(targets, obj, message);
5257 + }
5258 + }
5259 + }
5260 });
5261 }
5262
@@ -9569,7 +9731,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
9731 // Generate a random Intel AMT password
9732 function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
9733 function getRandomAmtPassword() { var p; do { p = Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
9572 - function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
9734 + function getRandomPassword() { return Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); }
9735 function getRandomLowerCase(len) { var r = '', random = obj.crypto.randomBytes(len); for (var i = 0; i < len; i++) { r += String.fromCharCode(97 + (random[i] % 26)); } return r; }
9736
9737 // Generate a 8 digit integer with even random probability for each value.
@@ -9594,31 +9756,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
9756 }
9757 }
9758
9597 - // Record a new entry in a recording log
9598 - function recordingEntry(fd, type, flags, data, func, tag) {
9599 - try {
9600 - if (typeof data == 'string') {
9601 - // String write
9602 - var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
9603 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
9604 - header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
9605 - header.writeInt32BE(blockData.length, 4); // Size
9606 - header.writeIntBE(new Date(), 10, 6); // Time
9607 - var block = Buffer.concat([header, blockData]);
9608 - obj.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
9609 - } else {
9610 - // Binary write
9611 - var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
9612 - header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
9613 - header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
9614 - header.writeInt32BE(data.length, 4); // Size
9615 - header.writeIntBE(new Date(), 10, 6); // Time
9616 - var block = Buffer.concat([header, data]);
9617 - obj.fs.write(fd, block, 0, block.length, function () { func(fd, tag); });
9618 - }
9619 - } catch (ex) { console.log(ex); func(fd, tag); }
9620 - }
9621 -
9759 // Perform a IP match against a list
9760 function isIPMatch(ip, matchList) {
9761 const ipcheck = require('ipcheck');