diff --git a/agents/agentrecoverycore.js b/agents/agentrecoverycore.js new file mode 100644 index 00000000..762fa91f --- /dev/null +++ b/agents/agentrecoverycore.js @@ -0,0 +1,466 @@ + +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 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 }); +} +// Return p number of spaces +function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } + +var path = + { + join: function () + { + 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('/'); + } + }; +// Convert an object to string with all functions +function objToString(x, p, pad, ret) { + if (ret == undefined) ret = ''; + if (p == undefined) p = 0; + if (x == null) { return '[null]'; } + if (p > 8) { return '[...]'; } + if (x == undefined) { return '[undefined]'; } + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; } + if (typeof x == 'buffer') { return '[buffer]'; } + if (typeof x != 'object') { return x; } + var r = '{' + (ret ? '\r\n' : ' '); + for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } } + return r + addPad(p, pad) + '}'; +} + +// Split a string taking into account the quoats. Used for command line parsing +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) +{ + var results = { '_': [] }, current = null; + for (var i = 1, len = argv.length; i < len; i++) { + var x = argv[i]; + if (x.length > 2 && x[0] == '-' && x[1] == '-') { + if (current != null) { results[current] = true; } + current = x.substring(2); + } else { + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); } + } + } + if (current != null) { results[current] = true; } + return results; +} +// Get server target url with a custom path +function getServerTargetUrl(path) +{ + var x = require('MeshAgent').ServerUrl; + //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl); + if (x == null) { return null; } + if (path == null) { path = ''; } + x = http.parseUri(x); + if (x == null) return null; + return x.protocol + '//' + x.host + ':' + x.port + '/' + path; +} + +// Get server url. If the url starts with "*/..." change it, it not use the url as is. +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) + { + sendConsoleText("Mesh Agent Receovery Console, OS: " + v); + require('MeshAgent').SendCommand(meshCoreObj); + }); +}); + +// 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; + } +} + +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) + { + 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); + } + 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 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]; + + // 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') { 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; + } + }); + } + } + 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; + } + } + } + } + }); + }); + 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); + } + } + break; + } + + default: + // Unknown action, ignore it. + break; + } + break; + } + default: + // Unknown action, ignore it. + break; + } + } +}); + +function processConsoleCommand(cmd, args, rights, sessionid) +{ + try + { + var response = null; + switch (cmd) + { + case 'help': + 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.'; } + for (var j = 0; j < i; j++) { + var pr = require('os').name(); + pr.sessionid = sessionid; + pr.then(function (v) { sendConsoleText("OS: " + v, this.sessionid); }); + } + break; + } + case 'dbkeys': { // Return all data store keys + response = JSON.stringify(db.Keys); + break; + } + case 'dbget': { // Return the data store value for a given key + 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 + } 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 (args['_'].length != 2) { + response = 'Proper usage: dbset (key) (value)'; // Set a database key + } else { + var r = db.Put(args['_'][0], args['_'][1]); + response = 'Key set: ' + r; + } + break; + } + case 'dbcompact': { // Compact the data store + if (db == null) { response = 'Database not accessible.'; break; } + var r = db.Compact(); + 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.'; } + break; + } + case 'netinfo': { // Show network interface information + //response = objToString(mesh.NetInfo, 0, ' '); + var interfaces = require('os').networkInterfaces(); + response = objToString(interfaces, 0, ' ', true); + break; + } + default: { // This is an unknown command, return an error message + response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.'; + break; + } + } + } 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) +{ + var response = { path: reqpath, dir: [] }; + if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) { + // List all the drives in the root, or the root itself + var results = null; + try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar. + if (results != null) { + for (var i = 0; i < results.length; ++i) { + var drive = { n: results[i].name, t: 1 }; + if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons. + response.dir.push(drive); + } + } + } else { + // List all the files and folders in this path + if (reqpath == '') { reqpath = '/'; } + var results = null, xpath = path.join(reqpath, '*'); + //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); } + try { results = fs.readdirSync(xpath); } catch (e) { } + if (results != null) { + for (var i = 0; i < results.length; ++i) { + if ((results[i] != '.') && (results[i] != '..')) { + var stat = null, p = path.join(reqpath, results[i]); + //if (process.platform == "win32") { p = p.split('/').join('\\'); } + try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date + if ((stat != null) && (stat != undefined)) { + if (stat.isDirectory() == true) { + response.dir.push({ n: results[i], t: 2, d: stat.mtime }); + } else { + response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime }); + } + } + } + } + } + } + return response; +} +// Delete a directory with a files and directories within it +function deleteFolderRecursive(path, rec) { + if (fs.existsSync(path)) { + if (rec == true) { + fs.readdirSync(path.join(path, '*')).forEach(function (file, index) { + var curPath = path.join(path, file); + if (fs.statSync(curPath).isDirectory()) { // recurse + deleteFolderRecursive(curPath, true); + } else { // delete file + fs.unlinkSync(curPath); + } + }); + } + fs.unlinkSync(path); + } +}; diff --git a/agents/meshcore.js b/agents/meshcore.js index 112055d8..4a0c5097 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -70,7 +70,7 @@ function createMeshCore(agent) { */ // MeshAgent JavaScript Core Module. This code is sent to and running on the mesh agent. - var meshCoreObj = { "action": "coreinfo", "value": "MeshCore v6", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript + var meshCoreObj = { "action": "coreinfo", "value": "MeshCore v6", "caps": 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript, 32 = Temporary Agent, 64 = Recovery Agent // Get the operating system description string try { require('os').name().then(function (v) { meshCoreObj.osdesc = v; }); } catch (ex) { } diff --git a/common.js b/common.js index 2d35597b..2db7f60b 100644 --- a/common.js +++ b/common.js @@ -223,7 +223,7 @@ module.exports.createTaskLimiterQueue = function (maxTasks, maxTaskTime, cleanin //console.log('PendingLaunch ' + id); t.func(t.arg, id, obj); // Start the task } - if ((obj.pending[0].length == 0) && (obj.pending[1].length == 0) && (obj.pending[2].length == 0) && (obj.timer != null)) { + if ((obj.currentCount == 0) && (obj.pending[0].length == 0) && (obj.pending[1].length == 0) && (obj.pending[2].length == 0) && (obj.timer != null)) { // All done, clear the timer clearInterval(obj.timer); obj.timer = null; } diff --git a/meshagent.js b/meshagent.js index b441b444..1967a0a2 100644 --- a/meshagent.js +++ b/meshagent.js @@ -67,7 +67,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { obj.parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete obj.agentUpdate = null; } - if (((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) { // This is a temporary agent, remote it + + // If this is a temporary or recovery agent, or all devices in this group are temporary, remove the agent (0x20 = Temporary, 0x40 = Recovery) + if (((obj.agentInfo) && (obj.agentInfo.capabilities) && ((obj.agentInfo.capabilities & 0x20) || (obj.agentInfo.capabilities & 0x40))) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) { // Delete this node including network interface information and events obj.db.Remove(obj.dbNodeKey); // Remove node with that id obj.db.Remove('if' + obj.dbNodeKey); // Remove interface information @@ -125,7 +127,14 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // We need to check if the core is current. First, figure out what core we need. var corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core; - if (obj.agentCoreCheck == 1001) { corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].rcore; } // Use the recovery core. + + // If this is a recovery agent, use the agent recovery core + if (obj.agentInfo.capabilities & 0x40) { corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].arcore; } + + // If the user asked, use the recovery core. + if (obj.agentCoreCheck == 1001) { corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].rcore; } + + // If we have a core, use it. if (corename != null) { const meshcorehash = obj.parent.parent.defaultMeshCoresHash[corename]; if (agentMeshCoreHash != meshcorehash) { @@ -654,30 +663,45 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (disconnectCount > 4) { // Too many disconnections, this agent has issues. Just clear the core. obj.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0)); - console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); + //console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); // TODO: Log or do something to recover? return; } - // Check if we need to make an native update check - obj.agentExeInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId]; - const corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core; - if (corename == null) { obj.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0)); } // MeshCommand_CoreModule, ask mesh agent to clear the core - - if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { - // Ask the agent for it's executable binary hash - obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); + if ((obj.agentInfo.capabilities & 64) != 0) { + // This is a recovery agent + obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash. } else { - // Check the mesh core, if the agent is capable of running one - if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) { - obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash. + // Check if we need to make an native update check + obj.agentExeInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId]; + const corename = obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core; + if (corename == null) { obj.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0)); } // MeshCommand_CoreModule, ask mesh agent to clear the core + + if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { + // Ask the agent for it's executable binary hash + obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); } else { - agentCoreIsStable(); // No updates needed, agent is ready to go. + // Check the mesh core, if the agent is capable of running one + if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) { + obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash. + } else { + agentCoreIsStable(); // No updates needed, agent is ready to go. + } } } }); } + function recoveryAgentCoreIsStable() { + // Recovery agent is doing ok, lets perform main agent checking. + + // TODO + console.log('recoveryAgentCoreIsStable()'); + + // Close the recovery agent connection when done. + obj.close(1); + } + function agentCoreIsStable() { // Check that the mesh exists var mesh = obj.parent.meshes[obj.dbMeshKey]; @@ -1009,7 +1033,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (command.name && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); } if ((command.caps != null) && (device.agent.core != command.value)) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; } // Don't save this as an event to the db. if ((command.caps != null) && ((device.agent.caps & 0xFFFFFFE7) != (command.caps & 0xFFFFFFE7))) { device.agent.caps = ((device.agent.caps & 24) + (command.caps & 0xFFFFFFE7)); change = 1; } // Allow Javascript on the agent to change all capabilities except console and javascript support, Don't save this as an event to the db. - if ((command.osdesc != null) && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; log = 1; changes.push('os desc'); } + if ((command.osdesc != null) && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; changes.push('os desc'); } // Don't save this as an event to the db. if (command.intelamt) { if (!device.intelamt) { device.intelamt = {}; } if ((command.intelamt.ver != null) && (device.intelamt.ver != command.intelamt.ver)) { changes.push('AMT version'); device.intelamt.ver = command.intelamt.ver; change = 1; log = 1; } diff --git a/meshcentral.js b/meshcentral.js index c5a47a74..7eaac236 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1140,6 +1140,16 @@ function CreateMeshCentralServer(config, args) { } } + // Read the agent recovery core if present + var meshAgentRecoveryCore = null; + if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'agentrecoverycore.js')) == true) { + try { meshAgentRecoveryCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'agentrecoverycore.js')).toString(); } catch (ex) { } + if (meshAgentRecoveryCore != null) { + modulesAdd['windows-agentrecovery'] = 'var addedModules = [];\r\n'; + modulesAdd['linux-agentrecovery'] = 'var addedModules = [];\r\n'; + } + } + if (obj.args.minifycore !== false) { try { meshCore = obj.fs.readFileSync(obj.path.join(meshcorePath, 'meshcore.min.js')).toString(); } catch (e) { } } // Favor minified meshcore if present. if (meshCore == null) { try { meshCore = obj.fs.readFileSync(obj.path.join(meshcorePath, 'meshcore.js')).toString(); } catch (e) { } } // Use non-minified meshcore. if (meshCore != null) { @@ -1179,6 +1189,13 @@ function CreateMeshCentralServer(config, args) { modulesAdd['windows-recovery'] += moduleData; } } + + // Merge this module to agent recovery modules if needed + if (modulesAdd['windows-agentrecovery'] != null) { + if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) { + modulesAdd['windows-agentrecovery'] += moduleData; + } + } } } } @@ -1187,6 +1204,8 @@ function CreateMeshCentralServer(config, args) { for (var i in modulesAdd) { if ((i == 'windows-recovery') || (i == 'linux-recovery')) { obj.defaultMeshCores[i] = obj.common.IntToStr(0) + modulesAdd[i] + meshRecoveryCore; + } else if ((i == 'windows-agentrecovery') || (i == 'linux-agentrecovery')) { + obj.defaultMeshCores[i] = obj.common.IntToStr(0) + modulesAdd[i] + meshAgentRecoveryCore; } else { obj.defaultMeshCores[i] = obj.common.IntToStr(0) + modulesAdd[i] + meshCore; } @@ -1286,34 +1305,34 @@ function CreateMeshCentralServer(config, args) { // List of possible mesh agents obj.meshAgentsArchitectureNumbers = { - 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery' }, - 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole.exe', desc: 'Windows x86-32 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole.exe', desc: 'Windows x86-64 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 3: { id: 3, localname: 'MeshService-signed.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 4: { id: 4, localname: 'MeshService64-signed.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery' }, - 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery' }, - 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 8: { id: 8, localname: 'MeshAgent-Linux-XEN-x86-32', rname: 'meshagent', desc: 'XEN x86-64', update: true, amt: false, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery' }, - 9: { id: 9, localname: 'meshagent_arm', rname: 'meshagent', desc: 'Linux ARM5', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 10: { id: 10, localname: 'MeshAgent-Linux-ARM-PlugPC', rname: 'meshagent', desc: 'Linux ARM PlugPC', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 11: { id: 11, localname: 'meshagent_osx-x86-32', rname: 'meshosx', desc: 'Apple OSX x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 12: { id: 12, localname: 'MeshAgent-Android-x86', rname: 'meshandroid', desc: 'Android x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 13: { id: 13, localname: 'meshagent_pogo', rname: 'meshagent', desc: 'Linux ARM PogoPlug', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 14: { id: 14, localname: 'MeshAgent-Android-APK', rname: 'meshandroid', desc: 'Android Market', update: false, amt: false, platform: 'android', core: 'linux-noamt', rcore: 'linux-recovery' }, // Get this one from Google Play - 15: { id: 15, localname: 'meshagent_poky', rname: 'meshagent', desc: 'Linux Poky x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 16: { id: 16, localname: 'meshagent_osx-x86-64', rname: 'meshagent', desc: 'Apple OSX x86-64', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery' }, - 17: { id: 17, localname: 'MeshAgent-ChromeOS', rname: 'meshagent', desc: 'Google ChromeOS', update: false, amt: false, platform: 'chromeos', core: 'linux-noamt', rcore: 'linux-recovery' }, // Get this one from Chrome store - 18: { id: 18, localname: 'meshagent_poky64', rname: 'meshagent', desc: 'Linux Poky x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 19: { id: 19, localname: 'meshagent_x86_nokvm', rname: 'meshagent', desc: 'Linux x86-32 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery' }, - 20: { id: 20, localname: 'meshagent_x86-64_nokvm', rname: 'meshagent', desc: 'Linux x86-64 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery' }, - 21: { id: 21, localname: 'MeshAgent-WinMinCore-Console-x86-32.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Console x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 22: { id: 22, localname: 'MeshAgent-WinMinCore-Service-x86-64.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Service x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery' }, - 23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false, platform: 'node', core: 'linux-noamt', rcore: 'linux-recovery' }, // Get this one from NPM - 24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, - 25: { id: 25, localname: 'meshagent_armhf', rname: 'meshagent', desc: 'Linux ARM - HardFloat', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery' }, // "armv6l" and "armv7l" - 10003: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery' }, // Unsigned version of the Windows MeshAgent x86 - 10004: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery' } // Unsigned version of the Windows MeshAgent x64 + 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole.exe', desc: 'Windows x86-32 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole.exe', desc: 'Windows x86-64 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 3: { id: 3, localname: 'MeshService-signed.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 4: { id: 4, localname: 'MeshService64-signed.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 8: { id: 8, localname: 'MeshAgent-Linux-XEN-x86-32', rname: 'meshagent', desc: 'XEN x86-64', update: true, amt: false, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 9: { id: 9, localname: 'meshagent_arm', rname: 'meshagent', desc: 'Linux ARM5', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 10: { id: 10, localname: 'MeshAgent-Linux-ARM-PlugPC', rname: 'meshagent', desc: 'Linux ARM PlugPC', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 11: { id: 11, localname: 'meshagent_osx-x86-32', rname: 'meshosx', desc: 'Apple OSX x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 12: { id: 12, localname: 'MeshAgent-Android-x86', rname: 'meshandroid', desc: 'Android x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 13: { id: 13, localname: 'meshagent_pogo', rname: 'meshagent', desc: 'Linux ARM PogoPlug', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 14: { id: 14, localname: 'MeshAgent-Android-APK', rname: 'meshandroid', desc: 'Android Market', update: false, amt: false, platform: 'android', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Get this one from Google Play + 15: { id: 15, localname: 'meshagent_poky', rname: 'meshagent', desc: 'Linux Poky x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 16: { id: 16, localname: 'meshagent_osx-x86-64', rname: 'meshagent', desc: 'Apple OSX x86-64', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 17: { id: 17, localname: 'MeshAgent-ChromeOS', rname: 'meshagent', desc: 'Google ChromeOS', update: false, amt: false, platform: 'chromeos', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Get this one from Chrome store + 18: { id: 18, localname: 'meshagent_poky64', rname: 'meshagent', desc: 'Linux Poky x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 19: { id: 19, localname: 'meshagent_x86_nokvm', rname: 'meshagent', desc: 'Linux x86-32 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 20: { id: 20, localname: 'meshagent_x86-64_nokvm', rname: 'meshagent', desc: 'Linux x86-64 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 21: { id: 21, localname: 'MeshAgent-WinMinCore-Console-x86-32.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Console x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 22: { id: 22, localname: 'MeshAgent-WinMinCore-Service-x86-64.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Service x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery' }, + 23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false, platform: 'node', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Get this one from NPM + 24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, + 25: { id: 25, localname: 'meshagent_armhf', rname: 'meshagent', desc: 'Linux ARM - HardFloat', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // "armv6l" and "armv7l" + 10003: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' }, // Unsigned version of the Windows MeshAgent x86 + 10004: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery' } // Unsigned version of the Windows MeshAgent x64 }; // Update the list of available mesh agents diff --git a/meshuser.js b/meshuser.js index 34e04190..354ba809 100644 --- a/meshuser.js +++ b/meshuser.js @@ -188,8 +188,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use "Connected Users": Object.keys(obj.parent.wssessions).length, "Users Sessions": Object.keys(obj.parent.wssessions2).length, "Relay Sessions": obj.parent.relaySessionCount, - "Relay Errors": obj.parent.relaySessionErrorCount }; + if (obj.parent.relaySessionErrorCount != 0) { serverStats['Relay Errors'] = obj.parent.relaySessionErrorCount; } if (obj.parent.parent.mpsserver != null) { serverStats['Connected Intel® AMT'] = Object.keys(obj.parent.parent.mpsserver.ciraConnections).length; } stats.values = { "Server State": serverStats } try { ws.send(JSON.stringify(stats)); } catch (ex) { } @@ -462,7 +462,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use switch (cmd) { case 'help': { - r = 'Available commands: help, args, resetserver, showconfig, usersessions.'; + r = 'Available commands: help, args, resetserver, showconfig, usersessions, tasklimiter, cores.'; break; } case 'args': { @@ -485,6 +485,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use process.exit(0); break; } + case 'tasklimiter': { + if (obj.parent.parent.taskLimiter != null) { + //var obj = { maxTasks: maxTasks, maxTaskTime: (maxTaskTime * 1000), nextTaskId: 0, currentCount: 0, current: {}, pending: [[], [], []], timer: null }; + const tl = obj.parent.parent.taskLimiter; + r += 'MaxTasks: ' + tl.maxTasks + '
'; + r += 'MaxTaskTime: ' + (tl.maxTaskTime / 1000) + ' seconds
'; + r += 'NextTaskId: ' + tl.nextTaskId + '
'; + r += 'CurrentCount: ' + tl.currentCount + '
'; + var c = []; + for (var i in tl.current) { c.push(i); } + r += 'Current: [' + c.join(', ') + ']
'; + r += 'Pending (High/Med/Low): ' + tl.pending[0].length + ', ' + tl.pending[1].length + ', ' + tl.pending[2].length + '
'; + r += 'Timer: ' + (tl.timer != null) + '
'; + } + break; + } + case 'cores': { + if (obj.parent.parent.defaultMeshCores != null) { for (var i in obj.parent.parent.defaultMeshCores) { r += i + ': ' + obj.parent.parent.defaultMeshCores[i].length + ' bytes
'; } } + break; + } case 'showconfig': { // Make a copy of the configuration and hide any secrets var config = obj.common.Clone(obj.parent.parent.config); diff --git a/views/default-min.handlebars b/views/default-min.handlebars index f2754ab1..77fae384 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index daa6946e..d8d285c4 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -2463,13 +2463,17 @@ x += addHtmlValue('Installation Type', ''); x += '
'; + // \/:*?"<>| + var meshfilename = mesh.name + meshfilename = meshfilename.split('\\').join('').split('/').join('').split(':').join('').split('*').join('').split('?').join('').split('"').join('').split('<').join('').split('>').join('').split('|').join('').split(' ').join('').split('\'').join(''); + // Windows agent install //x += "
To add a new computer to device group \"" + EscapeHtml(mesh.name) + "\", download the mesh agent and configuration file and install the agent on the computer to manage.

"; x += "
To add a new computer to device group \"" + EscapeHtml(mesh.name) + "\", download the mesh agent and install it the computer to manage. This agent has server and mesh information embedded within it.

"; //x += addHtmlValue('Mesh Agent', 'Windows (.exe)'); //x += addHtmlValue('Mesh Agent', 'Windows x64 (.exe)'); - x += addHtmlValue('Mesh Agent', 'Windows (.exe)'); - x += addHtmlValue('Mesh Agent', 'Windows x64 (.exe)'); + x += addHtmlValue('Mesh Agent', 'Windows (.exe)'); + x += addHtmlValue('Mesh Agent', 'Windows x64 (.exe)'); if (debugmode > 0) { x += addHtmlValue('Settings File', '' + EscapeHtml(mesh.name) + ' settings (.msh)'); } x += "
"; @@ -2529,7 +2533,7 @@ xdr.open("GET", window.location.href + path + flag); xdr.timeout = 15000; xdr.responseType = "blob"; - xdr.onprogress = function (x) { console.log(x); }; + xdr.onprogress = function (x) { /*console.log(x);*/ }; xdr.onload = function (e) { saveAs(new Blob([e.target.response], { type: "application/octet-stream" }), name); if (xxdialogTag == 'fileDownload') { setDialogMode(0); } }; xdr.onerror = function () { if (xxdialogTag == 'fileDownload') { setDialogMode(0); } alert('Agent downloads timeout.'); }; xdr.ontimeout = function () { if (xxdialogTag == 'fileDownload') { setDialogMode(0); } alert('Unable to download agent.'); };