From bcf5cfbb5e11804b898d968b266316b4cbab6dfe Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 7 Jan 2020 13:56:26 -0800 Subject: [PATCH] Fixed agent 'msg' dispatching. --- agents/recoverycore.js | 423 +++++++++++++++++++---------------------- meshuser.js | 2 +- webserver.js | 11 +- 3 files changed, 202 insertions(+), 234 deletions(-) diff --git a/agents/recoverycore.js b/agents/recoverycore.js index cdd29028..b7ad67fd 100644 --- a/agents/recoverycore.js +++ b/agents/recoverycore.js @@ -1,35 +1,30 @@ var http = require('http'); var childProcess = require('child_process'); -var meshCoreObj = { "action": "coreinfo", "value": "MeshCore Recovery", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript +var meshCoreObj = { action: 'coreinfo', value: "MeshCore Recovery", caps: 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript var nextTunnelIndex = 1; var tunnels = {}; var fs = require('fs'); //attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); }); -function sendConsoleText(msg) -{ - require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": msg }); +function sendConsoleText(msg) { + require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg }); } // Return p number of spaces function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } +setInterval(function () { sendConsoleText('Timer!'); }, 2000); + var path = { - join: function () - { + join: function () { var x = []; - for (var i in arguments) - { + for (var i in arguments) { var w = arguments[i]; - if (w != null) - { + 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); } - } + if (i != 0) { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } } x.push(w); } } @@ -53,16 +48,14 @@ function objToString(x, p, pad, ret) { } // Split a string taking into account the quoats. Used for command line parsing -function splitArgs(str) -{ +function splitArgs(str) { var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); return myArray; } // Parse arguments string array into an object -function parseArgs(argv) -{ +function parseArgs(argv) { var results = { '_': [] }, current = null; for (var i = 1, len = argv.length; i < len; i++) { var x = argv[i]; @@ -77,8 +70,7 @@ function parseArgs(argv) return results; } // Get server target url with a custom path -function getServerTargetUrl(path) -{ +function getServerTargetUrl(path) { var x = require('MeshAgent').ServerUrl; //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl); if (x == null) { return null; } @@ -89,16 +81,13 @@ function getServerTargetUrl(path) } // Get server url. If the url starts with "*/..." change it, it not use the url as is. -function getServerTargetUrlEx(url) -{ +function getServerTargetUrlEx(url) { if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); } return url; } -require('MeshAgent').on('Connected', function () -{ - require('os').name().then(function (v) - { +require('MeshAgent').on('Connected', function () { + require('os').name().then(function (v) { sendConsoleText("Mesh Agent Receovery Console, OS: " + v); require('MeshAgent').SendCommand(meshCoreObj); }); @@ -127,209 +116,192 @@ function onTunnelUpgrade(response, s, head) { } } -require('MeshAgent').AddCommandHandler(function (data) -{ - if (typeof data == 'object') - { +require('MeshAgent').AddCommandHandler(function (data) { + if (typeof data == 'object') { // If this is a console command, parse it and call the console handler - switch (data.action) - { + switch (data.action) { case 'msg': { - switch (data.type) - { - case 'console': { // Process a console command - if (data.value && data.sessionid) - { - var args = splitArgs(data.value); - processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); + switch (data.type) { + case 'console': { // Process a console command + if (data.value && data.sessionid) { + var args = splitArgs(data.value); + processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid); + } + break; } - break; - } case 'tunnel': { - if (data.value != null) { // Process a new tunnel connection request - // Create a new tunnel object - var xurl = getServerTargetUrlEx(data.value); - if (xurl != null) { - var woptions = http.parseUri(xurl); - woptions.rejectUnauthorized = 0; - //sendConsoleText(JSON.stringify(woptions)); - var tunnel = http.request(woptions); - tunnel.on('upgrade', function (response, s, head) - { - this.s = s; - s.httprequest = this; - s.tunnel = this; - s.on('end', function () - { - if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. + if (data.value != null) { // Process a new tunnel connection request + // Create a new tunnel object + var xurl = getServerTargetUrlEx(data.value); + if (xurl != null) { + var woptions = http.parseUri(xurl); + woptions.rejectUnauthorized = 0; + //sendConsoleText(JSON.stringify(woptions)); + var tunnel = http.request(woptions); + tunnel.on('upgrade', function (response, s, head) { + this.s = s; + s.httprequest = this; + s.tunnel = this; + s.on('end', function () { + 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 (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } - if (this.httprequest.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; } + // 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.downloadFile) { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; } - //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); - delete tunnels[this.httprequest.index]; + //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); + delete tunnels[this.httprequest.index]; - // Clean up WebSocket - this.removeAllListeners('data'); - }); - s.on('data', function (data) - { - // If this is upload data, save it to file - if (this.httprequest.uploadFile) - { - try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. - this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data - return; - } - - if (this.httprequest.state == 0) { - // 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); } - } else { - // Handle tunnel data - if (this.httprequest.protocol == 0) - { - // Take a look at the protocol - this.httprequest.protocol = parseInt(data); - if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } - if (this.httprequest.protocol == 1) - { - // Remote terminal using native pipes - if (process.platform == "win32") - { - this.httprequest._term = require('win-terminal').Start(80, 25); - this.httprequest._term.pipe(this, { dataTypeSkip: 1 }); - this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false }); - this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); }); - } - else - { - this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); - this.httprequest.process.tunnel = this; - this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); }); - this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); }); - this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - this.prependListener('end', function () { this.httprequest.process.kill(); }); - } - - this.on('end', function () { - if (process.platform == "win32") - { - // Unpipe the web socket - this.unpipe(this.httprequest._term); - this.httprequest._term.unpipe(this); - - // Clean up - this.httprequest._term.end(); - this.httprequest._term = null; - } - }); + // Clean up WebSocket + this.removeAllListeners('data'); + }); + s.on('data', function (data) { + // If this is upload data, save it to file + if (this.httprequest.uploadFile) { + try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out. + this.write(new Buffer(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data + return; } - } - else if (this.httprequest.protocol == 5) - { - // Process files commands - var cmd = null; - try { cmd = JSON.parse(data); } catch (e) { }; - if (cmd == null) { return; } - if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { return; } // If this is control data, handle it now. - if (cmd.action == undefined) { return; } - console.log('action: ', cmd.action); - //sendConsoleText('CMD: ' + JSON.stringify(cmd)); + if (this.httprequest.state == 0) { + // 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); } + } else { + // Handle tunnel data + if (this.httprequest.protocol == 0) { + // Take a look at the protocol + this.httprequest.protocol = parseInt(data); + if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; } + if (this.httprequest.protocol == 1) { + // Remote terminal using native pipes + if (process.platform == "win32") { + this.httprequest._term = require('win-terminal').Start(80, 25); + this.httprequest._term.pipe(this, { dataTypeSkip: 1 }); + this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false }); + this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); }); + } + else { + this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); + this.httprequest.process.tunnel = this; + this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); }); + this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); }); + this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + this.prependListener('end', function () { this.httprequest.process.kill(); }); + } - if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows - //console.log(objToString(cmd, 0, ' ')); - switch (cmd.action) - { - case 'ls': - // Send the folder content to the browser - var response = getDirectoryInfo(cmd.path); - if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } - this.write(new Buffer(JSON.stringify(response))); - break; - case 'mkdir': { - // Create a new empty folder - fs.mkdirSync(cmd.path); - break; - } - case 'rm': { - // Delete, possibly recursive delete - for (var i in cmd.delfiles) - { - try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { } + this.on('end', function () { + if (process.platform == "win32") { + // Unpipe the web socket + this.unpipe(this.httprequest._term); + this.httprequest._term.unpipe(this); + + // Clean up + this.httprequest._term.end(); + this.httprequest._term = null; + } + }); } - break; } - case 'rename': { - // Rename a file or folder - var oldfullpath = path.join(cmd.path, cmd.oldname); - var newfullpath = path.join(cmd.path, cmd.newname); - try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } - break; - } - case 'upload': { - // Upload a file, browser to agent - if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } - if (cmd.path == undefined) break; - var filepath = cmd.name ? path.join(cmd.path, cmd.name) : cmd.path; - try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } - this.httprequest.uploadFileid = cmd.reqid; - if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } - break; - } - case 'copy': { - // Copy a bunch of files from scpath to dspath - for (var i in cmd.names) { - var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); - if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } } + else if (this.httprequest.protocol == 5) { + // Process files commands + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { return; } // If this is control data, handle it now. + if (cmd.action == undefined) { return; } + console.log('action: ', cmd.action); + + //sendConsoleText('CMD: ' + JSON.stringify(cmd)); + + if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows + //console.log(objToString(cmd, 0, ' ')); + switch (cmd.action) { + case 'ls': + // Send the folder content to the browser + var response = getDirectoryInfo(cmd.path); + if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } + this.write(new Buffer(JSON.stringify(response))); + break; + case 'mkdir': { + // Create a new empty folder + fs.mkdirSync(cmd.path); + break; + } + case 'rm': { + // Delete, possibly recursive delete + for (var i in cmd.delfiles) { + try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { } + } + break; + } + case 'rename': { + // Rename a file or folder + var oldfullpath = path.join(cmd.path, cmd.oldname); + var newfullpath = path.join(cmd.path, cmd.newname); + try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); } + break; + } + case 'upload': { + // Upload a file, browser to agent + if (this.httprequest.uploadFile != undefined) { fs.closeSync(this.httprequest.uploadFile); this.httprequest.uploadFile = undefined; } + if (cmd.path == undefined) break; + var filepath = cmd.name ? path.join(cmd.path, cmd.name) : cmd.path; + try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(new Buffer(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; } + this.httprequest.uploadFileid = cmd.reqid; + if (this.httprequest.uploadFile) { this.write(new Buffer(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); } + break; + } + case 'copy': { + // Copy a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); + if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } } + } + break; + } + case 'move': { + // Move a bunch of files from scpath to dspath + for (var i in cmd.names) { + var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); + if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } + } + break; + } } - break; - } - case 'move': { - // Move a bunch of files from scpath to dspath - for (var i in cmd.names) { - var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]); - if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } } - } - break; } } - } - } - }); - }); - tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); } - tunnel.sessionid = data.sessionid; - tunnel.rights = data.rights; - tunnel.state = 0; - tunnel.url = xurl; - tunnel.protocol = 0; - tunnel.tcpaddr = data.tcpaddr; - tunnel.tcpport = data.tcpport; - tunnel.end(); - // Put the tunnel in the tunnels list - var index = nextTunnelIndex++; - tunnel.index = index; - tunnels[index] = tunnel; + }); + }); + tunnel.onerror = function (e) { sendConsoleText("ERROR: " + JSON.stringify(e)); } + tunnel.sessionid = data.sessionid; + tunnel.rights = data.rights; + tunnel.state = 0; + tunnel.url = xurl; + tunnel.protocol = 0; + tunnel.tcpaddr = data.tcpaddr; + tunnel.tcpport = data.tcpport; + tunnel.end(); + // Put the tunnel in the tunnels list + var index = nextTunnelIndex++; + tunnel.index = index; + tunnels[index] = tunnel; - //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); + //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); + } + } + break; } - } - break; - } - default: - // Unknown action, ignore it. - break; + default: + // Unknown action, ignore it. + break; + } + break; } - break; - } default: // Unknown action, ignore it. break; @@ -337,20 +309,16 @@ require('MeshAgent').AddCommandHandler(function (data) } }); -function processConsoleCommand(cmd, args, rights, sessionid) -{ - try - { +function processConsoleCommand(cmd, args, rights, sessionid) { + try { var response = null; - switch (cmd) - { + switch (cmd) { case 'help': - response = 'Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo.'; + response = "Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo."; break; - case 'osinfo': { // Return the operating system information var i = 1; - if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; } + if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = "Calling " + i + " times."; } for (var j = 0; j < i; j++) { var pr = require('os').name(); pr.sessionid = sessionid; @@ -363,34 +331,34 @@ function processConsoleCommand(cmd, args, rights, sessionid) break; } case 'dbget': { // Return the data store value for a given key - if (db == null) { response = 'Database not accessible.'; break; } + if (db == null) { response = "Database not accessible."; break; } if (args['_'].length != 1) { - response = 'Proper usage: dbget (key)'; // Display the value for a given database key + response = "Proper usage: dbget (key)"; // Display the value for a given database key } else { response = db.Get(args['_'][0]); } break; } case 'dbset': { // Set a data store key and value pair - if (db == null) { response = 'Database not accessible.'; break; } + if (db == null) { response = "Database not accessible."; break; } if (args['_'].length != 2) { - response = 'Proper usage: dbset (key) (value)'; // Set a database key + response = "Proper usage: dbset (key) (value)"; // Set a database key } else { var r = db.Put(args['_'][0], args['_'][1]); - response = 'Key set: ' + r; + response = "Key set: " + r; } break; } case 'dbcompact': { // Compact the data store - if (db == null) { response = 'Database not accessible.'; break; } + if (db == null) { response = "Database not accessible."; break; } var r = db.Compact(); - response = 'Database compacted: ' + r; + response = "Database compacted: " + r; break; } case 'tunnels': { // Show the list of current tunnels response = ''; - for (var i in tunnels) { response += 'Tunnel #' + i + ', ' + tunnels[i].url + '\r\n'; } - if (response == '') { response = 'No websocket sessions.'; } + for (var i in tunnels) { response += "Tunnel #" + i + ", " + tunnels[i].url + '\r\n'; } + if (response == '') { response = "No websocket sessions."; } break; } case 'netinfo': { // Show network interface information @@ -404,13 +372,12 @@ function processConsoleCommand(cmd, args, rights, sessionid) break; } } - } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); } + } catch (e) { response = "Command returned an exception error: " + e; console.log(e); } if (response != null) { sendConsoleText(response, sessionid); } } // Get a formated response for a given directory path -function getDirectoryInfo(reqpath) -{ +function getDirectoryInfo(reqpath) { var response = { path: reqpath, dir: [] }; if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { // List all the drives in the root, or the root itself diff --git a/meshuser.js b/meshuser.js index 0bb9c3df..6dfdff15 100644 --- a/meshuser.js +++ b/meshuser.js @@ -645,7 +645,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } else if ((command.fileop == 'rename') && (common.IsFilenameValid(command.oldname) == true) && (common.IsFilenameValid(command.newname) == true)) { // Rename - try { fs.renameSync(path + "/" + command.oldname, path + "/" + command.newname); } catch (e) { } + try { fs.renameSync(path + '/' + command.oldname, path + '/' + command.newname); } catch (e) { } } else if ((command.fileop == 'copy') || (command.fileop == 'move')) { if (common.validateArray(command.names, 1) == false) return; diff --git a/webserver.js b/webserver.js index 24e1f767..e83a2355 100644 --- a/webserver.js +++ b/webserver.js @@ -4262,16 +4262,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // TODO: Add multi-server support } } - } else { // Route this command to the mesh + } else { // Route this command to all users with MESHRIGHT_AGENTCONSOLE rights to this device group command.nodeid = nodeid; var cmdstr = JSON.stringify(command); - if (obj.GetMeshRights(userid, meshid) == 0) return; // TODO: Check if this is ok - // Find all connected users for this mesh and send the message + // Find all connected user sessions with access to this device for (var userid in obj.wssessions) { var xsessions = obj.wssessions[userid]; - // Send the message to all users on this server - for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } } + if (obj.GetMeshRights(userid, meshid) != 0) { + // Send the message to all sessions for this user on this server + for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } } + } } // Send the message to all users of other servers