1. Updated recovery core 'files' implementation

2. Removed dead code from recovery core
3. Fixed bug in switch statement in meshcore
This commit is contained in:
Bryan Roe 2021-01-17 18:36:53 -08:00
parent fb9c146ccc
commit 70df889935
2 changed files with 348 additions and 185 deletions

View File

@ -2551,7 +2551,8 @@ function createMeshCore(agent)
if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); }
break; break;
} }
case 'rename': { case 'rename':
{
// Rename a file or folder // Rename a file or folder
var oldfullpath = obj.path.join(cmd.path, cmd.oldname); var oldfullpath = obj.path.join(cmd.path, cmd.oldname);
var newfullpath = obj.path.join(cmd.path, cmd.newname); var newfullpath = obj.path.join(cmd.path, cmd.newname);
@ -2559,7 +2560,8 @@ function createMeshCore(agent)
try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
break; break;
} }
case 'findfile': { case 'findfile':
{
// Search for files // Search for files
var r = require('file-search').find('"' + cmd.path + '"', cmd.filter); var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; } if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
@ -2571,10 +2573,13 @@ function createMeshCore(agent)
r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } }); r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
break; break;
} }
case 'cancelfindfile': { case 'cancelfindfile':
{
if (this._search) { this._search.cancel(); this._search = null; } if (this._search) { this._search.cancel(); this._search = null; }
break;
} }
case 'download': { case 'download':
{
// Download a file // Download a file
var sendNextBlock = 0; var sendNextBlock = 0;
if (cmd.sub == 'start') if (cmd.sub == 'start')
@ -2610,29 +2615,8 @@ function createMeshCore(agent)
} }
break; break;
} }
/* case 'upload':
case 'download': { {
// Packet download of a file, agent to browser
if (cmd.path == undefined) break;
var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
//console.log('Download: ' + filepath);
try { this.httprequest.downloadFile = fs.openSync(filepath, 'rbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'downloaderror', reqid: cmd.reqid }))); break; }
this.httprequest.downloadFileId = cmd.reqid;
this.httprequest.downloadFilePtr = 0;
if (this.httprequest.downloadFile) { this.write(Buffer.from(JSON.stringify({ action: 'downloadstart', reqid: this.httprequest.downloadFileId }))); }
break;
}
case 'download2': {
// Stream download of a file, agent to browser
if (cmd.path == undefined) break;
var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
try { this.httprequest.downloadFile = fs.createReadStream(filepath, { flags: 'rbN' }); } catch (e) { console.log(e); }
this.httprequest.downloadFile.pipe(this);
this.httprequest.downloadFile.end = function () { }
break;
}
*/
case 'upload': {
// Upload a file, browser to agent // Upload a file, browser to agent
if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; } if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
if (cmd.path == undefined) break; if (cmd.path == undefined) break;
@ -2644,7 +2628,8 @@ function createMeshCore(agent)
if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
break; break;
} }
case 'uploaddone': { case 'uploaddone':
{
// Indicates that an upload is done // Indicates that an upload is done
if (this.httprequest.uploadFile) if (this.httprequest.uploadFile)
{ {
@ -2656,7 +2641,8 @@ function createMeshCore(agent)
} }
break; break;
} }
case 'uploadcancel': { case 'uploadcancel':
{
// Indicates that an upload is canceled // Indicates that an upload is canceled
if (this.httprequest.uploadFile) if (this.httprequest.uploadFile)
{ {
@ -2669,7 +2655,8 @@ function createMeshCore(agent)
} }
break; break;
} }
case 'copy': { case 'copy':
{
// Copy a bunch of files from scpath to dspath // Copy a bunch of files from scpath to dspath
for (var i in cmd.names) for (var i in cmd.names)
{ {
@ -2679,7 +2666,8 @@ function createMeshCore(agent)
} }
break; break;
} }
case 'move': { case 'move':
{
// Move a bunch of files from scpath to dspath // Move a bunch of files from scpath to dspath
for (var i in cmd.names) for (var i in cmd.names)
{ {

View File

@ -56,6 +56,55 @@ function sendAgentMessage(msg, icon)
require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages }); require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages });
} }
// Add to the server event log
function MeshServerLog(msg, state)
{
if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
if (state)
{
if (state.userid) { msg.userid = state.userid; }
if (state.username) { msg.username = state.username; }
if (state.sessionid) { msg.sessionid = state.sessionid; }
if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
}
require('MeshAgent').SendCommand(msg);
}
// Add to the server event log, use internationalized events
function MeshServerLogEx(id, args, msg, state)
{
var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
if (state)
{
if (state.userid) { msg.userid = state.userid; }
if (state.username) { msg.username = state.username; }
if (state.sessionid) { msg.sessionid = state.sessionid; }
if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
}
require('MeshAgent').SendCommand(msg);
}
function pathjoin()
{
var x = [];
for (var i in arguments)
{
var w = arguments[i];
if (w != null)
{
while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
if (i != 0)
{
while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
}
x.push(w);
}
}
if (x.length == 0) return '/';
return x.join('/');
}
function bsd_execv(name, agentfilename, sessionid) function bsd_execv(name, agentfilename, sessionid)
{ {
var child = require('child_process').execFile('/bin/sh', ['sh']); var child = require('child_process').execFile('/bin/sh', ['sh']);
@ -370,29 +419,6 @@ require('MeshAgent').on('Connected', function () {
}); });
}); });
// Tunnel callback operations
function onTunnelUpgrade(response, s, head) {
this.s = s;
s.httprequest = this;
s.end = onTunnelClosed;
s.tunnel = this;
//sendConsoleText('onTunnelUpgrade');
if (this.tcpport != null) {
// This is a TCP relay connection, pause now and try to connect to the target.
s.pause();
s.data = onTcpRelayServerTunnelData;
var connectionOptions = { port: parseInt(this.tcpport) };
if (this.tcpaddr != null) { connectionOptions.host = this.tcpaddr; } else { connectionOptions.host = '127.0.0.1'; }
s.tcprelay = net.createConnection(connectionOptions, onTcpRelayTargetTunnelConnect);
s.tcprelay.peerindex = this.index;
} else {
// This is a normal connect for KVM/Terminal/Files
s.data = onTunnelData;
}
}
// Called when receiving control data on websocket // Called when receiving control data on websocket
function onTunnelControlData(data, ws) function onTunnelControlData(data, ws)
{ {
@ -491,15 +517,44 @@ require('MeshAgent').AddCommandHandler(function (data)
if (data.value != null) if (data.value != null)
{ // Process a new tunnel connection request { // Process a new tunnel connection request
// Create a new tunnel object // Create a new tunnel object
if (data.rights != 4294967295)
{
MeshServerLog('Tunnel Error: RecoveryCore requires admin rights for tunnels');
break;
}
var xurl = getServerTargetUrlEx(data.value); var xurl = getServerTargetUrlEx(data.value);
if (xurl != null) if (xurl != null)
{ {
var woptions = http.parseUri(xurl); var woptions = http.parseUri(xurl);
woptions.rejectUnauthorized = 0; woptions.rejectUnauthorized = 0;
woptions.perMessageDeflate = false;
woptions.checkServerIdentity = function checkServerIdentity(certs)
{
// If the tunnel certificate matches the control channel certificate, accept the connection
try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }
// Check that the certificate is the one expected by the server, fail if not.
if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
}
woptions.checkServerIdentity.servertlshash = data.servertlshash;
//sendConsoleText(JSON.stringify(woptions)); //sendConsoleText(JSON.stringify(woptions));
var tunnel = http.request(woptions); var tunnel = http.request(woptions);
tunnel.on('upgrade', function (response, s, head) tunnel.on('upgrade', function (response, s, head)
{ {
if (require('MeshAgent').idleTimeout != null)
{
s.setTimeout(require('MeshAgent').idleTimeout * 1000);
s.on('timeout', function ()
{
this.ping();
this.setTimeout(require('MeshAgent').idleTimeout * 1000);
});
}
this.s = s; this.s = s;
s.httprequest = this; s.httprequest = this;
s.tunnel = this; s.tunnel = this;
@ -508,9 +563,8 @@ require('MeshAgent').AddCommandHandler(function (data)
if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.
// If there is a upload or download active on this connection, close the file // If there is a upload or download active on this connection, close the file
if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; } if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
delete tunnels[this.httprequest.index]; delete tunnels[this.httprequest.index];
@ -521,29 +575,62 @@ require('MeshAgent').AddCommandHandler(function (data)
s.on('data', function (data) s.on('data', function (data)
{ {
// If this is upload data, save it to file // If this is upload data, save it to file
if (this.httprequest.uploadFile) if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123))
{ {
try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. // Save the data to file being uploaded.
this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data if (data[0] == 0)
{
// If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
} else
{
// If data does not start with zero, save as-is.
try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
}
this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
return; return;
} }
if (this.httprequest.state == 0) if (this.httprequest.state == 0)
{ {
// Check if this is a relay connection // Check if this is a relay connection
if ((data == 'c') || (data == 'cr')) { 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 }
else
{ {
// Handle tunnel data // Handle tunnel data
if (this.httprequest.protocol == 0) if (this.httprequest.protocol == 0)
{ { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer
if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
// Take a look at the protocol // Take a look at the protocol
if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
this.httprequest.protocol = parseInt(data); this.httprequest.protocol = parseInt(data);
if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) if (this.httprequest.protocol == 10)
{ {
// Remote terminal using native pipes //
// Basic file transfer
//
var stats = null;
if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
if (this.httprequest.downloadFile)
{
//sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
this.write(JSON.stringify({ op: 'ok', size: stats.size }));
this.httprequest.downloadFile.pipe(this);
this.httprequest.downloadFile.end = function () { }
} else
{
//sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
this.write(JSON.stringify({ op: 'cancel' }));
}
}
else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9))
{
//
// Remote Terminal
//
if (process.platform == "win32") if (process.platform == "win32")
{ {
var cols = 80, rows = 25; var cols = 80, rows = 25;
@ -630,12 +717,14 @@ require('MeshAgent').AddCommandHandler(function (data)
if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
this.write(Buffer.from(JSON.stringify(response))); this.write(Buffer.from(JSON.stringify(response)));
break; break;
case 'mkdir': { case 'mkdir':
{
// Create a new empty folder // Create a new empty folder
fs.mkdirSync(cmd.path); fs.mkdirSync(cmd.path);
break; break;
} }
case 'rm': { case 'rm':
{
// Delete, possibly recursive delete // Delete, possibly recursive delete
for (var i in cmd.delfiles) for (var i in cmd.delfiles)
{ {
@ -643,23 +732,109 @@ require('MeshAgent').AddCommandHandler(function (data)
} }
break; break;
} }
case 'rename': { case 'rename':
{
// Rename a file or folder // Rename a file or folder
var oldfullpath = path.join(cmd.path, cmd.oldname); var oldfullpath = path.join(cmd.path, cmd.oldname);
var newfullpath = path.join(cmd.path, cmd.newname); var newfullpath = path.join(cmd.path, cmd.newname);
try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
break; break;
} }
case 'upload': { case 'findfile':
{
// Search for files
var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
this._search = r;
r.socket = this;
r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
r.socket.path = cmd.path; // Search path
r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } });
r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
break;
}
case 'cancelfindfile':
{
if (this._search) { this._search.cancel(); this._search = null; }
break;
}
case 'download':
{
// Download a file
var sendNextBlock = 0;
if (cmd.sub == 'start')
{ // Setup the download
if ((cmd.path == null) && (cmd.ask == 'coredump'))
{ // If we are asking for the coredump file, set the right path.
if (process.platform == 'win32')
{
if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
} else
{
if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
}
}
MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
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; }
if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
} else if ((this.filedownload != null) && (cmd.id == this.filedownload.id))
{ // Download commands
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; }
}
// Send the next download block(s)
while (sendNextBlock > 0)
{
sendNextBlock--;
var buf = Buffer.alloc(16384);
var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
this.filedownload.ptr += len;
if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
this.write(buf.slice(0, len + 4)); // Write as binary
}
break;
}
case 'upload':
{
// Upload a file, browser to agent // Upload a file, browser to agent
if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
if (cmd.path == undefined) break; if (cmd.path == undefined) break;
var filepath = cmd.name ? path.join(cmd.path, cmd.name) : cmd.path; var filepath = cmd.name ? pathjoin(cmd.path, cmd.name) : cmd.path;
this.httprequest.uploadFilePath = filepath;
MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
this.httprequest.uploadFileid = cmd.reqid; this.httprequest.uploadFileid = cmd.reqid;
if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
break; break;
} }
case 'uploaddone':
{
// Indicates that an upload is done
if (this.httprequest.uploadFile)
{
fs.closeSync(this.httprequest.uploadFile);
this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
delete this.httprequest.uploadFile;
delete this.httprequest.uploadFileid;
delete this.httprequest.uploadFilePath;
}
break;
}
case 'uploadcancel':
{
// Indicates that an upload is canceled
if (this.httprequest.uploadFile)
{
fs.closeSync(this.httprequest.uploadFile);
fs.unlinkSync(this.httprequest.uploadFilePath);
this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
delete this.httprequest.uploadFile;
delete this.httprequest.uploadFileid;
delete this.httprequest.uploadFilePath;
}
break;
}
case 'copy': { case 'copy': {
// Copy a bunch of files from scpath to dspath // Copy a bunch of files from scpath to dspath
for (var i in cmd.names) for (var i in cmd.names)