Added auto agent crash dump server upload option.
Ylian Saint-Hilaire committed
Jul 7, 2020 at 23:56 UTC
815fa1b0bbe394fb050b54d2e90f733ef8cd932e
3 files changed
+131
-12
agents/meshcore.js
+31
-8
@@ -959,6 +959,7 @@ function createMeshCore(agent) {
959
break;
960
}
961
case 'coredump':
962
+ // Set the current agent coredump situation.
963
if (data.value === true) {
964
// TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace.
965
process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp');
@@ -967,12 +968,12 @@ function createMeshCore(agent) {
968
}
969
break;
970
case 'getcoredump':
971
+ // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent.
972
var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) };
971
- if (process.platform == 'win32') {
972
- r.exists = r.value ? fs.existsSync(process.coreDumpLocation) : false;
973
- } else {
974
- r.exists = (r.value && (process.cwd() != '//')) ? fs.existsSync(process.cwd() + 'core') : false;
975
- }
973
+ var coreDumpPath = null;
974
+ if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; }
975
+ if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); }
976
+ if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); }
977
mesh.SendCommand(JSON.stringify(r));
978
default:
979
// Unknown action, ignore it.
@@ -1913,6 +1914,17 @@ function createMeshCore(agent) {
1914
}
1915
break;
1916
}
1917
+ case 'markcoredump': {
1918
+ // If we are asking for the coredump file, set the right path.
1919
+ var coreDumpPath = null;
1920
+ if (process.platform == 'win32') {
1921
+ if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; }
1922
+ } else {
1923
+ if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; }
1924
+ }
1925
+ if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
1926
+ break;
1927
+ }
1928
case 'rename': {
1929
// Rename a file or folder
1930
var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
@@ -1925,13 +1937,20 @@ function createMeshCore(agent) {
1937
// Download a file
1938
var sendNextBlock = 0;
1939
if (cmd.sub == 'start') { // Setup the download
1940
+ if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
1941
+ if (process.platform == 'win32') {
1942
+ if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
1943
+ } else {
1944
+ if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
1945
+ }
1946
+ }
1947
MeshServerLog('Download: \"' + cmd.path + '\"', this.httprequest);
1929
- if (this.filedownload != null) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
1948
+ if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
1949
this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
1950
try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
1951
if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
1952
} else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
1934
- if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
1953
+ if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
1954
}
1955
// Send the next download block(s)
1956
while (sendNextBlock > 0) {
@@ -2278,7 +2297,7 @@ function createMeshCore(agent) {
2297
}
2298
case 'coredump':
2299
if (args['_'].length != 1) {
2281
- response = "Proper usage: coredump on|off|status"; // Display usage
2300
+ response = "Proper usage: coredump on|off|status|clear"; // Display usage
2301
} else {
2302
switch (args['_'][0].toLowerCase())
2303
{
@@ -2300,6 +2319,10 @@ function createMeshCore(agent) {
2319
}
2320
}
2321
break;
2322
+ case 'clear':
2323
+ db.Put('CoreDumpTime', null);
2324
+ response = 'coredump db cleared';
2325
+ break;
2326
default:
2327
response = "Proper usage: coredump on|off|status"; // Display usage
2328
break;
meshagent.js
+8
@@ -1375,6 +1375,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1375
//console.log('CoreDump for agent ' + obj.remoteaddrport);
1376
obj.coreDumpPresent = true;
1377
// TODO: We need to look at getting the dump uploaded to the server.
1378
+ if (typeof command.agenthashhex == 'string') { obj.RequestCoreDump(command.agenthashhex); }
1379
}
1380
break;
1381
}
@@ -1555,6 +1556,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1556
});
1557
}
1558
1559
+ // Request that the core dump file on this agent be uploaded to the server
1560
+ obj.RequestCoreDump = function (agenthashhex) {
1561
+ if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); }
1562
+ const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey);
1563
+ obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}');
1564
+ }
1565
+
1566
// Generate a random Intel AMT password
1567
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
1568
function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
webserver.js
+92
-4
@@ -2998,9 +2998,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2998
// See if we need to create the folder
2999
var domainx = 'domain';
3000
if (domain.id.length > 0) { domainx = 'domain-' + usersplit[1]; }
3001
- try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { }
3002
- try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { }
3003
- try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { }
3001
+ try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { }
3002
+ try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (ex) { }
3003
+ try { obj.fs.mkdirSync(xfile.fullpath); } catch (ex) { }
3004
3005
// Upload method where all the file data is within the fields.
3006
var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*');
@@ -3423,7 +3423,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3423
}
3424
3425
});
3426
- }
3426
+ }
3427
3428
// Handle a Intel AMT activation request
3429
function handleAmtActivateWebSocket(ws, req) {
@@ -3614,6 +3614,90 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3614
ws.on('close', function (req) { });
3615
}
3616
3617
+ // Setup agent to/from server file transfer handler
3618
+ function handleAgentFileTransfer(ws, req) {
3619
+ var domain = checkAgentIpAddress(ws, req);
3620
+ if (domain == null) { parent.debug('web', 'Got agent file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); ws.close(); return; }
3621
+ if (req.query.c == null) { parent.debug('web', 'Got agent file transfer connection without a cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
3622
+ var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout
3623
+ if ((c == null) || (c.a != 'aft')) { parent.debug('web', 'Got agent file transfer connection with invalid cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; }
3624
+ ws.xcmd = c.b; ws.xarg = c.c, ws.xfilelen = 0;
3625
+ ws.send('c'); // Indicate connection of the tunnel. In this case, we are the termination point.
3626
+ ws.send('5'); // Indicate we want to perform file transfers (5 = Files).
3627
+ if (ws.xcmd == 'coredump') {
3628
+ // Check the agent core dump folder if not already present.
3629
+ var coreDumpPath = obj.path.join(parent.datapath, 'coredumps');
3630
+ if (obj.fs.existsSync(coreDumpPath) == false) { try { obj.fs.mkdirSync(coreDumpPath); } catch (ex) { } }
3631
+ ws.xfilepath = obj.path.join(parent.datapath, 'coredumps', ws.xarg);
3632
+ ws.xid = 'coredump';
3633
+ ws.send(JSON.stringify({ action: 'download', sub: 'start', ask: 'coredump', id: 'coredump' })); // Ask for a directory (test)
3634
+ }
3635
+
3636
+ // When data is received from the web socket, echo it back
3637
+ ws.on('message', function (data) {
3638
+ if (typeof data == 'string') {
3639
+ // Control message
3640
+ var cmd = null;
3641
+ try { cmd = JSON.parse(data); } catch (ex) { }
3642
+ if ((cmd == null) || (cmd.action != 'download') || (cmd.sub == null)) return;
3643
+ switch (cmd.sub) {
3644
+ case 'start': {
3645
+ // Perform an async file open
3646
+ var callback = function onFileOpen(err, fd) {
3647
+ onFileOpen.xws.xfile = fd;
3648
+ onFileOpen.xws.send(JSON.stringify({ action: 'download', sub: 'startack', id: onFileOpen.xws.xid, ack: 1 })); // Ask for a directory (test)
3649
+ };
3650
+ callback.xws = this;
3651
+ obj.fs.open(this.xfilepath, 'w', callback)
3652
+ break;
3653
+ }
3654
+ }
3655
+ } else {
3656
+ // Binary message
3657
+ if (data.length < 4) return;
3658
+ var flags = data.readInt32BE(0);
3659
+ if ((data.length > 4)) {
3660
+ // Write the file
3661
+ this.xfilelen += (data.length - 4);
3662
+ try {
3663
+ var callback = function onFileDataWritten(err, bytesWritten, buffer) {
3664
+ if (onFileDataWritten.xflags & 1) {
3665
+ // End of file
3666
+ parent.debug('web', "Completed downloads of agent dumpfile, " + onFileDataWritten.xws.xfilelen + " bytes.");
3667
+ if (onFileDataWritten.xws.xfile) { try { obj.fs.close(onFileDataWritten.xws.xfile, function (err) { }); } catch (ex) { } }
3668
+ onFileDataWritten.xws.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
3669
+ try { onFileDataWritten.xws.close(); } catch (ex) { }
3670
+ } else {
3671
+ // Send ack
3672
+ onFileDataWritten.xws.send(JSON.stringify({ action: 'download', sub: 'ack', id: onFileDataWritten.xws.xid })); // Ask for a directory (test)
3673
+ }
3674
+ };
3675
+ callback.xws = this;
3676
+ callback.xflags = flags;
3677
+ obj.fs.write(this.xfile, data, 4, data.length - 4, callback);
3678
+ } catch (ex) { }
3679
+ } else {
3680
+ if (flags & 1) {
3681
+ // End of file
3682
+ parent.debug('web', "Completed downloads of agent dumpfile, " + this.xfilelen + " bytes.");
3683
+ if (this.xfile) { try { obj.fs.close(this.xfile, function (err) { }); } catch (ex) { } }
3684
+ this.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file
3685
+ try { this.close(); } catch (ex) { }
3686
+ } else {
3687
+ // Send ack
3688
+ this.send(JSON.stringify({ action: 'download', sub: 'ack', id: this.xid })); // Ask for a directory (test)
3689
+ }
3690
+ }
3691
+ }
3692
+ });
3693
+
3694
+ // If error, do nothing.
3695
+ ws.on('error', function (err) { console.log('Agent file transfer server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); });
3696
+
3697
+ // If closed, do nothing
3698
+ ws.on('close', function (req) { });
3699
+ }
3700
+
3701
// Handle the web socket echo request, just echo back the data sent
3702
function handleEchoWebSocket(ws, req) {
3703
const domain = checkUserIpAddress(ws, req);
@@ -4428,6 +4512,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4512
obj.app.get(url + 'player.htm', handlePlayerRequest);
4513
obj.app.get(url + 'player', handlePlayerRequest);
4514
obj.app.ws(url + 'amtactivate', handleAmtActivateWebSocket);
4515
+ obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
4516
obj.app.ws(url + 'meshrelay.ashx', function (ws, req) {
4517
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) {
4518
if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
@@ -4830,6 +4915,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4915
}
4916
});
4917
});
4918
+
4919
+ // Setup agent to/from server file transfer handler
4920
+ obj.agentapp.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
4921
}
4922
4923
// Memory Tracking