From 2cf3f20fb7b75b6fdef4ece6c33d0879941c1f6e Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Sat, 5 Oct 2019 02:50:32 -0400 Subject: [PATCH 01/14] Main calls for server and console, update function list --- agents/meshcore.min.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index d6c15a1a..bc6ad446 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -757,6 +757,17 @@ function createMeshCore(agent) } break; } + case 'plugin': { + if (typeof data.pluginaction == 'string') { + try { + MeshServerLog('Plugin called', data); + require(data.plugin.name).serveraction(data); + } catch(e) { + MeshServerLog('Error calling plugin', data); + } + } + break; + } default: // Unknown action, ignore it. break; @@ -1683,7 +1694,7 @@ function createMeshCore(agent) var response = null; switch (cmd) { case 'help': { // Displays available commands - var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt'; + var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt'; availcommands = availcommands.split(',').sort(); while (availcommands.length > 0) { if (f.length > 100) { fin += (f + ',\r\n'); f = ''; } @@ -2339,6 +2350,19 @@ function createMeshCore(agent) } break; } + case 'plugin': { + if (typeof args['_'][0] == 'string') { + try { + response = require(args['_'][0]).consoleaction(args, rights, sessionid); + } catch(e) { + response = 'There was an error in the plugin'; + } + } else { + response = 'Proper usage: plugin [pluginName] [args].'; + } + + break; + } default: { // This is an unknown command, return an error message response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.'; break; From be87880dc5d4d82ffe6f1e75a1f8d7386a19f12d Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Tue, 8 Oct 2019 04:18:40 -0400 Subject: [PATCH 02/14] More plugin hooks / development of base --- agents/meshcore.min.js | 33 ++++++---- meshagent.js | 12 ++++ meshcentral.js | 8 ++- pluginHandler.js | 137 +++++++++++++++++++++++++++++++++++++++ views/default.handlebars | 21 +++++- webserver.js | 6 +- 6 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 pluginHandler.js diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index bc6ad446..b43ccd66 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -757,17 +757,6 @@ function createMeshCore(agent) } break; } - case 'plugin': { - if (typeof data.pluginaction == 'string') { - try { - MeshServerLog('Plugin called', data); - require(data.plugin.name).serveraction(data); - } catch(e) { - MeshServerLog('Error calling plugin', data); - } - } - break; - } default: // Unknown action, ignore it. break; @@ -841,6 +830,19 @@ function createMeshCore(agent) } case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; } case 'pong': { break; } + case 'plugin': { + if (typeof data.pluginaction == 'string') { + try { + + MeshServerLog('Plugin called', data); + /* Not yet implmented + require(data.plugin.name).serveraction(data);*/ + } catch(e) { + MeshServerLog('Error calling plugin', data); + } + } + break; + } default: // Unknown action, ignore it. break; @@ -2353,9 +2355,12 @@ function createMeshCore(agent) case 'plugin': { if (typeof args['_'][0] == 'string') { try { - response = require(args['_'][0]).consoleaction(args, rights, sessionid); + // pass off the action to the plugin + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js + // to control the output / actions here. + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); } catch(e) { - response = 'There was an error in the plugin'; + response = 'There was an error in the plugin (' + e + ')'; } } else { response = 'Proper usage: plugin [pluginName] [args].'; @@ -2475,6 +2480,8 @@ function createMeshCore(agent) //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (ex) { pr = null; } } // Pending reboot if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } } + + // TODO: add plugin hook here } diff --git a/meshagent.js b/meshagent.js index d7e58fa7..0873962b 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1345,6 +1345,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { }); break; } + case 'plugin': { + if (typeof command.plugin == 'string') { + try { + var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent); + pluginHandler.plugins[command.plugin].serveraction(command, obj, parent); + } catch (e) { + + console.log('Error loading plugin handler ('+ e + ')'); + } + } + break; + } default: { parent.agentStats.unknownAgentActionCount++; console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + command.action + '.'); diff --git a/meshcentral.js b/meshcentral.js index 919ca9b0..a1583df1 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -865,6 +865,11 @@ function CreateMeshCentralServer(config, args) { // Dispatch an event that the server is now running obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }); + obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj); + + // Plugin hook. Need to run something at server startup? This is the place. + obj.pluginHandler.callHook("server_startup"); + // Load the login cookie encryption key from the database if allowed if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) { obj.db.Get('LoginCookieEncryptionKey', function (err, docs) { @@ -1347,7 +1352,8 @@ function CreateMeshCentralServer(config, args) { } } } - + obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj); + obj.pluginHandler.addMeshCoreModules(modulesAdd); // Merge the cores and compute the hashes for (var i in modulesAdd) { if ((i == 'windows-recovery') || (i == 'linux-recovery')) { diff --git a/pluginHandler.js b/pluginHandler.js new file mode 100644 index 00000000..6688e485 --- /dev/null +++ b/pluginHandler.js @@ -0,0 +1,137 @@ +/** +* @description MeshCentral plugin module +* @author Ryan Blenis +* @copyright +* @license Apache-2.0 +* @version v0.0.1 +*/ + +/*xjslint node: true */ +/*xjslint plusplus: true */ +/*xjslint maxlen: 256 */ +/*jshint node: true */ +/*jshint strict: false */ +/*jshint esversion: 6 */ +"use strict"; + +module.exports.pluginHandler = function (parent) { + var obj = {}; + + obj.fs = require('fs'); + obj.path = require('path'); + obj.parent = parent; + obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins'); + obj.enabled = obj.parent.config.settings.plugins.enabled; + obj.loadList = obj.parent.config.settings.plugins.list; + obj.plugins = {}; + obj.exports = {}; + + if (obj.enabled) { + obj.loadList.forEach(function(plugin, index) { + if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) { + try { + obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj); + obj.exports[plugin] = obj.plugins[plugin].exports; + } catch (e) { + console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled"); + } + } + }); + } + + obj.prepExports = function() { + var str = 'function() {\r\n'; + str += ' var obj = {};\r\n'; + + for (const p of Object.keys(obj.plugins)) { + str += ' obj.'+ p +' = {};\r\n'; + for (const l of Object.values(obj.exports[p])) { + str += ' obj.'+ p +'.'+ l + ' = '+ obj.plugins[p][l].toString()+'\r\n'; + } + } + str += 'return obj; };\r\n'; + return str; + } + + obj.callHook = function(hookName, ...args) { + for (var p in obj.plugins) { + if (typeof obj.plugins[p][hookName] == 'function') { + try { + obj.plugins[p][hookName](args); + } catch (e) { + console.log('Error ocurred while running plugin hook' + p + ':' + hookName + ' (' + e + ')'); + } + } + } + }; + + obj.addMeshCoreModules = function(modulesAdd) { + if (obj.enabled !== true) return; + for (var plugin in obj.plugins) { + var moduleDirPath = null; + var modulesDir = null; + //if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(obj.pluginPath, 'modules_meshcore_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Favor minified modules if present. + if (modulesDir == null) { try { moduleDirPath = obj.path.join(obj.pluginPath, plugin + '/modules_meshcore'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Use non-minified mofules. + if (modulesDir != null) { + for (var i in modulesDir) { + if (modulesDir[i].toLowerCase().endsWith('.js')) { + var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3); + if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files. + var moduleData = [ 'try { addModule("', moduleName, '", "', obj.parent.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (e) { }\r\n' ]; + + // Merge this module + // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms. + if (moduleName.startsWith('amt-') || (moduleName == 'smbios')) { + // Add to IA / Intel AMT cores only + modulesAdd['windows-amt'].push(...moduleData); + modulesAdd['linux-amt'].push(...moduleData); + } else if (moduleName.startsWith('win-')) { + // Add to Windows cores only + modulesAdd['windows-amt'].push(...moduleData); + } else if (moduleName.startsWith('linux-')) { + // Add to Linux cores only + modulesAdd['linux-amt'].push(...moduleData); + modulesAdd['linux-noamt'].push(...moduleData); + } else { + // Add to all cores + modulesAdd['windows-amt'].push(...moduleData); + modulesAdd['linux-amt'].push(...moduleData); + modulesAdd['linux-noamt'].push(...moduleData); + } + + // Merge this module to recovery modules if needed + if (modulesAdd['windows-recovery'] != null) { + if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) { + modulesAdd['windows-recovery'].push(...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'].push(...moduleData); + } + } + } + } + } + } + }; + + obj.deviceViewPanel = function() { + var panel = {}; + for (var p in obj.plugins) { + if (typeof obj.plugins[p][hookName] == 'function') { + try { + panel[p].header = obj.plugins[p].on_device_header(); + panel[p].content = obj.plugins[p].on_device_page(); + } catch (e) { + console.log('Error ocurred while getting plugin views ' + p + ':' + ' (' + e + ')'); + } + } + } + return panel; + } + + return obj; +}; \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 096cafee..bea503a1 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -122,6 +122,7 @@ Details Intel® AMT Console + Plugins   @@ -841,6 +842,12 @@
+

@@ -1015,7 +1029,8 @@ var nightMode = (getstore('_nightMode', '0') == '1'); var sessionActivity = Date.now(); var updateSessionTimer = null; - var pluginHandler = {{{pluginHandler}}}; + var pluginHandlerBuilder = {{{pluginHandler}}}; + var pluginHandler = new pluginHandlerBuilder(); // Console Message Display Timers var p11DeskConsoleMsgTimer = null; @@ -2309,8 +2324,7 @@ case 'plugin': { if (typeof message.plugin == 'string') { try { - var ph = pluginHandler(); - ph[message.plugin][message.method](server, message); + pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); } @@ -4149,6 +4163,7 @@ QH('p15deviceName', 'Console - ' + nname); QH('p16deviceName', nname); QH('p17deviceName', nname); + QH('p19deviceName', nname); // Node attributes var x = ''; @@ -4404,6 +4419,8 @@ p11clearConsoleMsg(); p12clearConsoleMsg(); p13clearConsoleMsg(); + + pluginHandler.onDeviceRefeshEnd(nodeid, panel, refresh, event); } setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching. if (!panel) panel = 10; From 4e90d358a41fd40f36c0e0e112ba8de779a9f7bc Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 03:06:37 -0400 Subject: [PATCH 04/14] Fix header showing multiple times when context switching --- pluginHandler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pluginHandler.js b/pluginHandler.js index 7a621009..2756ae87 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -60,7 +60,9 @@ module.exports.pluginHandler = function (parent) { }; obj.registerPluginTab = function(pluginRegInfo) { var d = pluginRegInfo(); - QA('p19headers', ''+d.tabTitle+''); + if (!Q(d.tabId)) { + QA('p19headers', ''+d.tabTitle+''); + } }; obj.callPluginPage = function(id) { var pages = Q('p19pages').querySelectorAll("#p19pages>div"); From e9d598264243cc8691c7d83dd78551ba0482636e Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 03:22:35 -0400 Subject: [PATCH 05/14] Add error catching in case the user doesn't have the config options set. --- pluginHandler.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pluginHandler.js b/pluginHandler.js index 2756ae87..2eb93294 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -21,11 +21,18 @@ module.exports.pluginHandler = function (parent) { obj.path = require('path'); obj.parent = parent; obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins'); - obj.enabled = obj.parent.config.settings.plugins.enabled; - obj.loadList = obj.parent.config.settings.plugins.list; obj.plugins = {}; obj.exports = {}; + try { + obj.enabled = obj.parent.config.settings.plugins.enabled; + obj.loadList = obj.parent.config.settings.plugins.list; + } catch (e) { // Config file options not present, disable self + obj.enabled = false; + obj.loadList = {}; + console.log('Plugin options not added to the config file. Plugins disabled. Please see the documentation.'); + } + if (obj.enabled) { obj.loadList.forEach(function(plugin, index) { if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) { From f257c2b8195e3049d7b00b97a0dc0a4522781e83 Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 04:54:37 -0400 Subject: [PATCH 06/14] Debug code cleanup --- agents/meshcore.min.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index 1bbd93e5..24bb6915 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -1497,22 +1497,12 @@ function createMeshCore(agent) if (cmd == null) { return; } if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. if (cmd.action == undefined) { return; } - //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 'plugin': { try { require(cmd.plugin).consoleaction(cmd, null, null, this); - } catch (e) { - /*var fs = require('fs'); - var logStream = fs.createWriteStream('log.txt', {'flags': 'a'}); - logStream.write('\nCouldnt load plugin '+e+e.stack); - logStream.end('\n')*/ - throw e; - } + } catch (e) { throw e; } break; } From 7baf812c47ea4722128c7b58e281ee976670536c Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 9 Oct 2019 15:56:27 -0700 Subject: [PATCH 07/14] MQTT improvements, multi-line toast fix. --- agents/meshcore.js | 10 ++--- agents/meshcore.min.js | 10 ++--- meshagent.js | 72 +------------------------------ meshuser.js | 5 ++- mqttbroker.js | 83 ++++-------------------------------- package.json | 2 +- views/default-min.handlebars | 2 +- views/default.handlebars | 9 ++-- webserver.js | 74 ++++++++++++++++++++++++++++++++ 9 files changed, 105 insertions(+), 162 deletions(-) diff --git a/agents/meshcore.js b/agents/meshcore.js index d6c15a1a..6de857a6 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -798,6 +798,7 @@ function createMeshCore(agent) // Display a toast message if (data.title && data.msg) { MeshServerLog('Displaying toast message, title=' + data.title + ', message=' + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); try { require('toaster').Toast(data.title, data.msg); } catch (ex) { } } break; @@ -1139,7 +1140,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting Terminal Access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting Terminal Access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1229,7 +1230,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting KVM Access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting KVM Access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1287,7 +1288,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting remote file access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting remote file access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1788,8 +1789,7 @@ function createMeshCore(agent) break; } case 'toast': { - if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else - { + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); } break; diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index d6c15a1a..6de857a6 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -798,6 +798,7 @@ function createMeshCore(agent) // Display a toast message if (data.title && data.msg) { MeshServerLog('Displaying toast message, title=' + data.title + ', message=' + data.msg, data); + data.msg = data.msg.split('\r').join('\\r').split('\n').join('\\n'); try { require('toaster').Toast(data.title, data.msg); } catch (ex) { } } break; @@ -1139,7 +1140,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting Terminal Access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting Terminal Access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1229,7 +1230,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting KVM Access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting KVM Access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1287,7 +1288,7 @@ function createMeshCore(agent) // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); - var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting remote file access. Grant access?', 10); + var pr = require('message-box').create('MeshCentral', this.httprequest.username + ' requesting remote file access. Grant access?', 30); pr.ws = this; this.pause(); @@ -1788,8 +1789,7 @@ function createMeshCore(agent) break; } case 'toast': { - if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else - { + if (args['_'].length < 1) { response = 'Proper usage: toast "message"'; } else { require('toaster').Toast('MeshCentral', args['_'][0]).then(sendConsoleText, sendConsoleText); } break; diff --git a/meshagent.js b/meshagent.js index d7e58fa7..2ef67cb1 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1064,76 +1064,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { switch (command.action) { case 'msg': { - // Route a message. - // If this command has a sessionid, that is the target. - if (command.sessionid != null) { - if (typeof command.sessionid != 'string') break; - var splitsessionid = command.sessionid.split('/'); - // Check that we are in the same domain and the user has rights over this node. - if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) { - // Check if this user has rights to get this message - //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! - - // See if the session is connected. If so, go ahead and send this message to the target node - var ws = parent.wssessions2[command.sessionid]; - if (ws != null) { - command.nodeid = obj.dbNodeKey; // Set the nodeid, required for responses. - delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed. - try { ws.send(JSON.stringify(command)); } catch (ex) { } - } else if (parent.parent.multiServer != null) { - // See if we can send this to a peer server - var serverid = parent.wsPeerSessions2[command.sessionid]; - if (serverid != null) { - command.fromNodeid = obj.dbNodeKey; - parent.parent.multiServer.DispatchMessageSingleServer(command, serverid); - } - } - } - } else if (command.userid != null) { // If this command has a userid, that is the target. - if (typeof command.userid != 'string') break; - var splituserid = command.userid.split('/'); - // Check that we are in the same domain and the user has rights over this node. - if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) { - // Check if this user has rights to get this message - //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! - - // See if the session is connected - var sessions = parent.wssessions[command.userid]; - - // Go ahead and send this message to the target node - if (sessions != null) { - command.nodeid = obj.dbNodeKey; // Set the nodeid, required for responses. - delete command.userid; // Remove the userid, since we are sending to that userid, so it's implyed. - for (i in sessions) { sessions[i].send(JSON.stringify(command)); } - } - - if (parent.parent.multiServer != null) { - // TODO: Add multi-server support - } - } - } else { // Route this command to the mesh - command.nodeid = obj.dbNodeKey; - var cmdstr = JSON.stringify(command); - for (var userid in parent.wssessions) { // Find all connected users for this mesh and send the message - var user = parent.users[userid]; - if ((user != null) && (user.links != null)) { - var rights = user.links[obj.dbMeshKey]; - if (rights != null) { // TODO: Look at what rights are needed for message routing - var xsessions = parent.wssessions[userid]; - // Send the message to all users on this server - for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } } - } - } - } - - // Send the message to all users of other servers - if (parent.parent.multiServer != null) { - delete command.nodeid; - command.fromNodeid = obj.dbNodeKey; - command.meshid = obj.dbMeshKey; - parent.parent.multiServer.DispatchMessage(command); - } - } + // Route a message + parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey); break; } case 'coreinfo': diff --git a/meshuser.js b/meshuser.js index f7e35860..afc4a237 100644 --- a/meshuser.js +++ b/meshuser.js @@ -320,7 +320,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var httpport = ((args.aliasport != null) ? args.aliasport : args.port); // Build server information object - var serverinfo = { name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1)), domainauth: ((domain.auth == 'sspi') || (domain.auth == 'ldap')) }; + var serverinfo = { name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1)), domainauth: ((domain.auth == 'sspi') || (domain.auth == 'ldap')), serverTime: Date.now() }; serverinfo.tlshash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate if ((parent.parent.config.domains[domain.id].amtacmactivation != null) && (parent.parent.config.domains[domain.id].amtacmactivation.acmmatch != null)) { var matchingDomains = []; @@ -2014,6 +2014,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use agentSession.sendUpdatedIntelAmtPolicy(); // Send the new Intel AMT policy } + // If any MQTT sessions are connected on this server, switch it now. + if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.changeDeviceMesh(node._id, command.meshid); } + // Add the connection state const state = parent.parent.GetConnectivityState(node._id); if (state) { diff --git a/mqttbroker.js b/mqttbroker.js index 2009dec2..559d46c9 100644 --- a/mqttbroker.js +++ b/mqttbroker.js @@ -58,6 +58,7 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Set the client nodeid and meshid client.xdbNodeKey = 'node/' + xdomainid + '/' + xnodeid; client.xdbMeshKey = 'mesh/' + xdomainid + '/' + xmeshid; + client.xdomainid = xdomainid; // Check if this node exists in the database db.Get(client.xdbNodeKey, function (err, nodes) { @@ -107,7 +108,7 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { aedes.authorizePublish = function (client, packet, callback) { // Handle a published message obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); - handleMessage(client.xdbNodeKey, client.xdbMeshKey, packet.topic, packet.payload); + handleMessage(client.xdbNodeKey, client.xdbMeshKey, client.xdomainid, packet.topic, packet.payload); // We don't accept that any client message be published, so don't call the callback. } @@ -128,9 +129,9 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { } // Handle messages coming from clients - function handleMessage(nodeid, meshid, topic, message) { + function handleMessage(nodeid, meshid, domainid, topic, message) { // Handle messages here - if (topic == 'console') { routeMessage({ action: 'msg', type: 'console', value: message.toString(), source: 'MQTT' }, nodeid, meshid); return; } // Handle console messages + if (topic == 'console') { parent.webserver.routeAgentCommand({ action: 'msg', type: 'console', value: message.toString(), source: 'MQTT' }, domainid, nodeid, meshid); return; } // Handle console messages //console.log('handleMessage', nodeid, topic, message.toString()); //obj.publish(nodeid, 'echoTopic', "Echo: " + message.toString()); @@ -139,78 +140,10 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Clean a IPv6 address that encodes a IPv4 address function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } } - // Route a message - function routeMessage(command, dbNodeKey, dbMeshKey) { - // Route a message. - // If this command has a sessionid, that is the target. - if (command.sessionid != null) { - if (typeof command.sessionid != 'string') return; - var splitsessionid = command.sessionid.split('/'); - // Check that we are in the same domain and the user has rights over this node. - if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) { - // Check if this user has rights to get this message - //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! - - // See if the session is connected. If so, go ahead and send this message to the target node - var ws = parent.webserver.wssessions2[command.sessionid]; - if (ws != null) { - command.nodeid = dbNodeKey; // Set the nodeid, required for responses. - delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed. - try { ws.send(JSON.stringify(command)); } catch (ex) { } - } else if (parent.multiServer != null) { - // See if we can send this to a peer server - var serverid = parent.webserver.wsPeerSessions2[command.sessionid]; - if (serverid != null) { - command.fromNodeid = dbNodeKey; - parent.multiServer.DispatchMessageSingleServer(command, serverid); - } - } - } - } else if (command.userid != null) { // If this command has a userid, that is the target. - if (typeof command.userid != 'string') return; - var splituserid = command.userid.split('/'); - // Check that we are in the same domain and the user has rights over this node. - if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) { - // Check if this user has rights to get this message - //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! - - // See if the session is connected - var sessions = parent.webserver.wssessions[command.userid]; - - // Go ahead and send this message to the target node - if (sessions != null) { - command.nodeid = dbNodeKey; // Set the nodeid, required for responses. - delete command.userid; // Remove the userid, since we are sending to that userid, so it's implyed. - for (i in sessions) { sessions[i].send(JSON.stringify(command)); } - } - - if (parent.multiServer != null) { - // TODO: Add multi-server support - } - } - } else { // Route this command to the mesh - command.nodeid = dbNodeKey; - var cmdstr = JSON.stringify(command); - for (var userid in parent.webserver.wssessions) { // Find all connected users for this mesh and send the message - var user = parent.webserver.users[userid]; - if ((user != null) && (user.links != null)) { - var rights = user.links[dbMeshKey]; - if (rights != null) { // TODO: Look at what rights are needed for message routing - var xsessions = parent.webserver.wssessions[userid]; - // Send the message to all users on this server - for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } } - } - } - } - - // Send the message to all users of other servers - if (parent.multiServer != null) { - delete command.nodeid; - command.fromNodeid = dbNodeKey; - command.meshid = dbMeshKey; - parent.multiServer.DispatchMessage(command); - } - } + // Change a node to a new meshid + obj.changeDeviceMesh = function(nodeid, newMeshId) { + var nodes = obj.connections[nodeid]; + if (nodes != null) { for (var i in nodes) { nodes[i].xdbMeshKey = newMeshId; } } } return obj; diff --git a/package.json b/package.json index c0d92ea3..05dcb330 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.1-x", + "version": "0.4.1-y", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index fe7c9452..28619be2 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

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

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 3282ae44..7ffa3a1d 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1447,6 +1447,7 @@ case 'serverinfo': { serverinfo = message.serverinfo; if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); } + if (debugmode == 1) { console.log("Server time: ", printDateTime(new Date(serverinfo.serverTime))); } break; } case 'userinfo': { @@ -6476,7 +6477,7 @@ if ((consoleNode.conn & 16) != 0) { onlineText += ', MQTT is online' } QH('p15statetext', onlineText); QE('p15consoleText', online); - QE('p15uploadCore', online); + QE('p15uploadCore', ((consoleNode.conn & 1) != 0)); QV('p15outputselecttd', (consoleNode.conn & 17) == 17); } else { QH('p15statetext', 'Access Denied'); @@ -6502,7 +6503,7 @@ var consoleHistory = []; function p15consoleSend(e) { if (e && e.keyCode != 13) return; - var v = Q('p15consoleText').value, t = '
> ' + v + '
'; + var v = Q('p15consoleText').value, t = '
> ' + EscapeHtml(v) + '
'; if (xxcurrentView == 115) { // Send the command to the server - TODO: In the future, we may support multiple servers. @@ -6539,7 +6540,7 @@ function p15consoleReceive(node, data, source) { if (node === 'serverconsole') { // Server console data - data = '
' + EscapeHtml(data) + '
' + data = '
' + data + '
' consoleServerText += data; if (consoleNode == 'server') { Q('p15agentConsoleText').innerHTML += data; @@ -6547,7 +6548,7 @@ } } else { // Agent console data - if (source == 'MQTT') { data = '
MQTT> ' + EscapeHtml(data) + '
'; } else { data = '
' + EscapeHtml(data) + '
' } + if (source == 'MQTT') { data = '
MQTT> ' + EscapeHtml(data) + '
'; } else { data = '
' + data + '
' } if (node.consoleText == null) { node.consoleText = data; } else { node.consoleText += data; } if (consoleNode == node) { Q('p15agentConsoleText').innerHTML += data; diff --git a/webserver.js b/webserver.js index 93634d15..3015037e 100644 --- a/webserver.js +++ b/webserver.js @@ -3751,6 +3751,80 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { return null; } + // Route a command from a agent. domainid, nodeid and meshid are the values of the source agent. + obj.routeAgentCommand = function (command, domainid, nodeid, meshid) { + // Route a message. + // If this command has a sessionid, that is the target. + if (command.sessionid != null) { + if (typeof command.sessionid != 'string') return; + var splitsessionid = command.sessionid.split('/'); + // Check that we are in the same domain and the user has rights over this node. + if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domainid)) { + // Check if this user has rights to get this message + //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! + + // See if the session is connected. If so, go ahead and send this message to the target node + var ws = obj.wssessions2[command.sessionid]; + if (ws != null) { + command.nodeid = nodeid; // Set the nodeid, required for responses. + delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed. + try { ws.send(JSON.stringify(command)); } catch (ex) { } + } else if (parent.multiServer != null) { + // See if we can send this to a peer server + var serverid = obj.wsPeerSessions2[command.sessionid]; + if (serverid != null) { + command.fromNodeid = nodeid; + parent.multiServer.DispatchMessageSingleServer(command, serverid); + } + } + } + } else if (command.userid != null) { // If this command has a userid, that is the target. + if (typeof command.userid != 'string') return; + var splituserid = command.userid.split('/'); + // Check that we are in the same domain and the user has rights over this node. + if ((splituserid[0] == 'user') && (splituserid[1] == domainid)) { + // Check if this user has rights to get this message + //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! + + // See if the session is connected + var sessions = obj.wssessions[command.userid]; + + // Go ahead and send this message to the target node + if (sessions != null) { + command.nodeid = nodeid; // Set the nodeid, required for responses. + delete command.userid; // Remove the userid, since we are sending to that userid, so it's implyed. + for (i in sessions) { sessions[i].send(JSON.stringify(command)); } + } + + if (parent.multiServer != null) { + // TODO: Add multi-server support + } + } + } else { // Route this command to the mesh + command.nodeid = nodeid; + var cmdstr = JSON.stringify(command); + for (var userid in obj.wssessions) { // Find all connected users for this mesh and send the message + var user = obj.users[userid]; + if ((user != null) && (user.links != null)) { + var rights = user.links[meshid]; + if (rights != null) { // TODO: Look at what rights are needed for message routing + 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) { } } + } + } + } + + // Send the message to all users of other servers + if (parent.multiServer != null) { + delete command.nodeid; + command.fromNodeid = nodeid; + command.meshid = meshid; + parent.multiServer.DispatchMessage(command); + } + } + } + // Return true if a mobile browser is detected. // This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/ function isMobileBrowser(req) { From dece20ac93fdcff17ebad050fb29f63942cca561 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 10 Oct 2019 11:13:25 -0700 Subject: [PATCH 08/14] Improvements to plugin support. --- MeshCentralServer.njsproj | 1 + agents/meshcore.js | 350 +- agents/meshcore.min.js | 351 +- meshagent.js | 10 +- meshcentral.js | 16 +- meshrelay.js | 6 +- package.json | 2 +- pluginHandler.js | 262 +- views/default-min.handlebars | 15281 ++++++++++++++++++++++++++++++++- views/default.handlebars | 24 +- webserver.js | 6 +- 11 files changed, 15748 insertions(+), 561 deletions(-) diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index 8639af10..e1c50f24 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -117,6 +117,7 @@ + diff --git a/agents/meshcore.js b/agents/meshcore.js index 6de857a6..8f177891 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -37,12 +37,10 @@ var MESHRIGHT_NOFILES = 1024; var MESHRIGHT_NOAMT = 2048; var MESHRIGHT_LIMITEDINPUT = 4096; -function createMeshCore(agent) -{ +function createMeshCore(agent) { var obj = {}; - if (process.platform == 'darwin' && !process.versions) - { + if (process.platform == 'darwin' && !process.versions) { // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; @@ -51,21 +49,18 @@ function createMeshCore(agent) child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); print kn2[1]; } }"); child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n"); child.waitExit(); - if (child.stdout.str.trim() == 'Crashed') - { + if (child.stdout.str.trim() == 'Crashed') { child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n"); child.waitExit(); - if (parseInt(child.stdout.str.trim()) == process.pid) - { + if (parseInt(child.stdout.str.trim()) == process.pid) { // The currently running MeshAgent is us, so we can continue with the update var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString(); var tokens = plist.split('KeepAlive'); - if (tokens[1].split('>')[0].split('<')[1] == 'dict') - { + if (tokens[1].split('>')[0].split('<')[1] == 'dict') { var tmp = tokens[1].split(''); tmp.shift(); tokens[1] = '\n ' + tmp.join(''); @@ -125,10 +120,8 @@ function createMeshCore(agent) if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ee) { } } obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH }); } catch (e) { } - obj.DAIPC.on('connection', function (c) - { - c._send = function (j) - { + obj.DAIPC.on('connection', function (c) { + c._send = function (j) { var data = JSON.stringify(j); var packet = Buffer.alloc(data.length + 4); packet.writeUInt32LE(data.length + 4, 0); @@ -138,32 +131,26 @@ function createMeshCore(agent) this._daipc = c; c.parent = this; c.on('end', function () { console.log('Connection Closed'); this.parent._daipc = null; }); - c.on('data', function (chunk) - { + c.on('data', function (chunk) { if (chunk.length < 4) { this.unshift(chunk); return; } var len = chunk.readUInt32LE(0); if (len > 8192) { this.parent._daipc = null; this.end(); return; } if (chunk.length < len) { this.unshift(chunk); return; } - + var data = chunk.slice(4, len); - try - { + try { data = JSON.parse(data.toString()); } - catch(de) - { + catch (de) { this.parent._daipc = null; this.end(); return; } - + if (!data.cmd) { this.parent._daipc = null; this.end(); return; } - try - { - switch(data.cmd) - { + try { + switch (data.cmd) { case 'query': - switch(data.value) - { + switch (data.value) { case 'connection': data.result = require('MeshAgent').ConnectedServer; this._send(data); @@ -176,32 +163,26 @@ function createMeshCore(agent) return; } } - catch(xe) - { + catch (xe) { this.parent._daipc = null; this.end(); return; } }); }); - function diagnosticAgent_uninstall() - { + function diagnosticAgent_uninstall() { require('service-manager').manager.uninstallService('meshagentDiagnostic'); require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); }; - function diagnosticAgent_installCheck(install) - { - try - { + function diagnosticAgent_installCheck(install) { + try { var diag = require('service-manager').manager.getService('meshagentDiagnostic'); return (diag); } - catch (e) - { + catch (e) { } if (!install) { return (null); } var svc = null; - try - { + try { require('service-manager').manager.installService( { name: 'meshagentDiagnostic', @@ -213,8 +194,7 @@ function createMeshCore(agent) }); svc = require('service-manager').manager.getService('meshagentDiagnostic'); } - catch (e) - { + catch (e) { return (null); } var proxyConfig = require('global-tunnel').proxyConfig; @@ -227,12 +207,10 @@ function createMeshCore(agent) ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri); if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); } if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); } - if (proxyConfig) - { + if (proxyConfig) { ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port); } - else - { + else { ddb.Put('ignoreProxyFile', '1'); } @@ -242,7 +220,7 @@ function createMeshCore(agent) delete ddb; // Set a recurrent task, to run the Diagnostic Agent every 2 days - require('task-scheduler').create({name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic'}); + require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' }); //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' }); return (svc); @@ -281,7 +259,7 @@ function createMeshCore(agent) } obj.borderManager = new borderController(); */ - + // 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, 32 = Temporary Agent, 64 = Recovery Agent @@ -397,7 +375,7 @@ function createMeshCore(agent) }); } } catch (ex) { sendConsoleText("ex1: " + ex); } - + // Try to load up the WIFI scanner try { var wifiScannerLib = require('wifi-scanner'); @@ -436,7 +414,7 @@ function createMeshCore(agent) } catch (e) { return false; } } - + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. function clearGatewayMac(str) { if (str == null) return null; @@ -444,7 +422,7 @@ function createMeshCore(agent) for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } return JSON.stringify(x); } - + function getIpLocationData(func) { // Get the location information for the cache if possible var publicLocationInfo = db.Get('publicLocationInfo'); @@ -483,7 +461,7 @@ function createMeshCore(agent) } } } - + // Polyfill String.endsWith if (!String.prototype.endsWith) { String.prototype.endsWith = function (searchString, position) { @@ -494,7 +472,7 @@ function createMeshCore(agent) return lastIndex !== -1 && lastIndex === position; }; } - + // Polyfill path.join obj.path = { join: function () { @@ -513,19 +491,19 @@ function createMeshCore(agent) return x.join('/'); } }; - + // Replace a string with a number if the string is an exact number function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } - + // Convert decimal to hex function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } - + // Convert a raw string to a hex string function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } - + // Convert a buffer into a string function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } - + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster function hex2rstr(d) { if (typeof d != "string" || d.length == 0) return ''; @@ -533,7 +511,7 @@ function createMeshCore(agent) while (t = m.shift()) r += String.fromCharCode('0x' + t); return r } - + // Convert an object to string with all functions function objToString(x, p, pad, ret) { if (ret == undefined) ret = ''; @@ -548,17 +526,17 @@ function createMeshCore(agent) 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) + '}'; } - + // Return p number of spaces function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } - + // 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; @@ -574,7 +552,7 @@ function createMeshCore(agent) if (current != null) { results[current] = true; } return results; } - + // Get server target url with a custom path function getServerTargetUrl(path) { var x = mesh.ServerUrl; @@ -585,13 +563,13 @@ function createMeshCore(agent) 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; } - + // Send a wake-on-lan packet function sendWakeOnLan(hexMac) { var count = 0; @@ -600,7 +578,7 @@ function createMeshCore(agent) var magic = 'FFFFFFFFFFFF'; for (var x = 1; x <= 16; ++x) { magic += hexMac; } var magicbin = Buffer.from(magic, 'hex'); - + for (var adapter in interfaces) { if (interfaces.hasOwnProperty(adapter)) { for (var i = 0; i < interfaces[adapter].length; ++i) { @@ -618,7 +596,7 @@ function createMeshCore(agent) } catch (e) { } return count; } - + // Handle a mesh agent command function handleServerCommand(data) { if (typeof data == 'object') { @@ -722,7 +700,7 @@ function createMeshCore(agent) // Open a local web browser and return success/fail MeshServerLog('Opening: ' + data.url, data); sendConsoleText('OpenURL: ' + data.url); - if (data.url) { mesh.SendCommand({ "action": "msg", "type":"openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); } + if (data.url) { mesh.SendCommand({ "action": "msg", "type": "openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); } break; } case 'getclip': { @@ -831,13 +809,25 @@ function createMeshCore(agent) } case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; } case 'pong': { break; } + case 'plugin': { + if (typeof data.pluginaction == 'string') { + try { + MeshServerLog('Plugin called', data); + // Not yet implemented + // require(data.plugin.name).serveraction(data); + } catch (e) { + MeshServerLog('Error calling plugin', data); + } + } + break; + } default: // Unknown action, ignore it. break; } } } - + // Called when a file changed in the file system /* function onFileWatcher(a, b) { @@ -889,8 +879,8 @@ function createMeshCore(agent) pr.then(defragResult, defragResult); } else { */ - results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); - func(results); + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); //} } catch (ex) { func(null, ex); } } @@ -934,7 +924,7 @@ function createMeshCore(agent) } return response; } - + // Tunnel callback operations function onTunnelUpgrade(response, s, head) { this.s = s; @@ -990,7 +980,7 @@ function createMeshCore(agent) peerTunnel.s.first = true; peerTunnel.s.resume(); } - + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) function onTcpRelayServerTunnelData(data) { if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target @@ -1000,7 +990,7 @@ function createMeshCore(agent) if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete tunnels[this.httprequest.index]; - + /* // Close the watcher if required if (this.httprequest.watcher != undefined) { @@ -1032,7 +1022,7 @@ function createMeshCore(agent) function onTunnelData(data) { //console.log("OnTunnelData"); //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + 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. @@ -1069,17 +1059,14 @@ function createMeshCore(agent) return; } - this.end = function () - { - if (process.platform == "win32") - { + this.end = function () { + if (process.platform == "win32") { // Unpipe the web socket this.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this); } // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). - if (this.rtcchannel) - { + if (this.rtcchannel) { this.rtcchannel.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } } @@ -1087,27 +1074,21 @@ function createMeshCore(agent) // Clean up if (this.httprequest._term) { this.httprequest._term.end(); } this.httprequest._term = null; - } else - { + } else { // TODO!! } }; // Remote terminal using native pipes - if (process.platform == "win32") - { - try - { - if ((this.httprequest.protocol == 6) && (require('win-terminal').PowerShellCapable() == true)) - { + if (process.platform == "win32") { + try { + if ((this.httprequest.protocol == 6) && (require('win-terminal').PowerShellCapable() == true)) { this.httprequest._term = require('win-terminal').StartPowerShell(80, 25); - } else - { + } else { this.httprequest._term = require('win-terminal').Start(80, 25); } } - catch(e) - { + catch (e) { MeshServerLog('Failed to start remote terminal session, ' + e.toString() + ' (' + this.httprequest.remoteaddr + ')', this.httprequest); this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); this.end(); @@ -1116,14 +1097,11 @@ function createMeshCore(agent) 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 () { console.log('Terminal was closed'); }); }); - } else - { - if (fs.existsSync("/bin/bash")) - { + } else { + if (fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/bin/bash", ["bash", "-i"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("alias ls='ls --color=auto'\nclear\n"); } - } else - { + } else { this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nPS1='\\u@\\h:\\w\\$ '\nclear\n"); } } @@ -1176,8 +1154,7 @@ function createMeshCore(agent) this.removeAllListeners('data'); this.on('data', onTunnelControlData); //this.write('MeshCore Terminal Hello'); - } else if (this.httprequest.protocol == 2) - { + } else if (this.httprequest.protocol == 2) { // Check user access rights for desktop if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) { // Disengage this tunnel, user does not have the rights to do this!! @@ -1209,7 +1186,7 @@ function createMeshCore(agent) if (this.desktop.kvm.connectionCount == 0) { // Display a toast message. This may not be supported on all platforms. // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { } - + this.httprequest.desktop.kvm.end(); } }; @@ -1225,8 +1202,7 @@ function createMeshCore(agent) } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 8)) - { + if (this.httprequest.consent && (this.httprequest.consent & 8)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); @@ -1235,8 +1211,7 @@ function createMeshCore(agent) this.pause(); pr.then( - function () - { + function () { // Success MeshServerLog('Starting remote desktop after local user accepted (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); @@ -1247,15 +1222,13 @@ function createMeshCore(agent) this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); this.ws.resume(); }, - function (e) - { + function (e) { // User Consent Denied/Failed MeshServerLog('Failed to start remote desktop after local user rejected (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); }); } - else - { + else { // User Consent Prompt is not required if (this.httprequest.consent && (this.httprequest.consent & 1)) { // User Notifications is required @@ -1283,8 +1256,7 @@ function createMeshCore(agent) } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 32)) - { + if (this.httprequest.consent && (this.httprequest.consent & 32)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); @@ -1361,7 +1333,7 @@ function createMeshCore(agent) var response = getDirectoryInfo(cmd.path); if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } this.write(new Buffer(JSON.stringify(response))); - + /* // Start the directory watcher if ((cmd.path != '') && (samepath == false)) { @@ -1479,6 +1451,23 @@ function createMeshCore(agent) // Unknown action, ignore it. break; } + } else if (this.httprequest.protocol == 7) { // plugin data exchange + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) return; + + switch (cmd.action) { + case 'plugin': { + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; } + break; + } + default: { + // probably shouldn't happen, but just in case this feature is expanded + } + } + } //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } @@ -1553,12 +1542,10 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over. if (ws.httprequest.protocol == 1) { // Terminal // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.httprequest._term.unpipe(ws); } - else - { + else { ws.httprequest.process.stdout.unpipe(ws); ws.httprequest.process.stderr.unpipe(ws); } @@ -1575,13 +1562,11 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc1') { if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal // Switch the user input from websocket to webrtc at this point. - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.unpipe(ws.httprequest._term); ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } - else - { + else { ws.unpipe(ws.httprequest.process.stdin); ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } @@ -1596,12 +1581,10 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc2') { // Other side received websocket end of data marker, start sending data on WebRTC channel if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } - else - { + else { ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } @@ -1641,7 +1624,7 @@ function createMeshCore(agent) // Console state var consoleWebSockets = {}; var consoleHttpRequest = null; - + // Console HTTP response function consoleHttpResponse(response) { response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } @@ -1684,7 +1667,7 @@ function createMeshCore(agent) var response = null; switch (cmd) { case 'help': { // Displays available commands - var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt'; + var fin = '', f = '', availcommands = 'help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbcompact,eval,parseuri,httpget,nwslist,plugin,wsconnect,wssend,wsclose,notify,ls,ps,kill,amt,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,sendcaps,openurl,amtreset,amtccm,amtacm,amtdeactivate,amtpolicy,getscript,getclip,setclip,log,av,cpuinfo,sysinfo,apf,scanwifi,scanamt'; availcommands = availcommands.split(',').sort(); while (availcommands.length > 0) { if (f.length > 100) { fin += (f + ',\r\n'); f = ''; } @@ -1802,7 +1785,7 @@ function createMeshCore(agent) case 'ps': { processManager.getProcesses(function (plist) { var x = ''; - for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user):'') + '\r\n'; } + for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user) : '') + '\r\n'; } sendConsoleText(x, sessionid); }); break; @@ -1836,14 +1819,11 @@ function createMeshCore(agent) break; } case 'dump': - if (args['_'].length < 1) - { + if (args['_'].length < 1) { response = 'Proper usage: dump [on/off/status]'; // Display correct command usage } - else - { - switch(args['_'][0].toLowerCase()) - { + else { + switch (args['_'][0].toLowerCase()) { case 'on': process.coreDumpLocation = process.platform == 'win32' ? process.execPath.replace('.exe', '.dmp') : (process.execPath + '.dmp'); response = 'enabled'; @@ -1853,12 +1833,10 @@ function createMeshCore(agent) response = 'disabled'; break; case 'status': - if (process.coreDumpLocation) - { + if (process.coreDumpLocation) { response = 'Core Dump: [ENABLED' + (require('fs').existsSync(process.coreDumpLocation) ? (', (DMP file exists)]') : (']')); } - else - { + else { response = 'Core Dump: [DISABLED]'; } break; @@ -1878,18 +1856,14 @@ function createMeshCore(agent) } case 'uninstallagent': var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - if (!require('service-manager').manager.getService(agentName).isMe()) - { + if (!require('service-manager').manager.getService(agentName).isMe()) { response = 'Uininstall failed, this instance is not the service instance'; } - else - { - try - { + else { + try { diagnosticAgent_uninstall(); } - catch(x) - { + catch (x) { } var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); @@ -2068,7 +2042,7 @@ function createMeshCore(agent) if (httprequest != null) { httprequest.upgrade = onWebSocketUpgrade; httprequest.on('error', function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }); - + var index = 1; while (consoleWebSockets[index]) { index++; } httprequest.sessionid = sessionid; @@ -2256,40 +2230,31 @@ function createMeshCore(agent) } case 'diagnostic': { - if (!mesh.DAIPC.listening) - { + if (!mesh.DAIPC.listening) { response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system'; break; } var diag = diagnosticAgent_installCheck(); - if (diag) - { - if (args['_'].length == 1 && args['_'][0] == 'uninstall') - { + if (diag) { + if (args['_'].length == 1 && args['_'][0] == 'uninstall') { diagnosticAgent_uninstall(); response = 'Diagnostic Agent uninstalled'; } - else - { + else { response = 'Diagnostic Agent installed at: ' + diag.appLocation(); } } - else - { - if (args['_'].length == 1 && args['_'][0] == 'install') - { + else { + if (args['_'].length == 1 && args['_'][0] == 'install') { diag = diagnosticAgent_installCheck(true); - if (diag) - { + if (diag) { response = 'Diagnostic agent was installed at: ' + diag.appLocation(); } - else - { + else { response = 'Diagnostic agent installation failed'; } } - else - { + else { response = 'Diagnostic Agent Not installed. To install: diagnostic install'; } } @@ -2297,27 +2262,27 @@ function createMeshCore(agent) break; } case 'apf': { - if (meshCoreObj.intelamt!==null) { + if (meshCoreObj.intelamt !== null) { if (args['_'].length == 1) { if (args['_'][0] == 'on') { response = 'Starting APF tunnel' var apfarg = { - mpsurl: mesh.ServerUrl.replace('agent.ashx','apf.ashx'), - mpsuser: Buffer.from(mesh.ServerInfo.MeshID,'hex').toString('base64').substring(0,16), - mpspass: Buffer.from(mesh.ServerInfo.MeshID,'hex').toString('base64').substring(0,16), + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), mpskeepalive: 60000, clientname: require('os').hostname(), clientaddress: '127.0.0.1', clientuuid: meshCoreObj.intelamt.uuid }; var tobj = { debug: false }; // - apftunnel= require('apfclient')(tobj,apfarg); + apftunnel = require('apfclient')(tobj, apfarg); try { apftunnel.connect(); response += "..success"; } catch (e) { response += JSON.stringify(e); - } + } } else if (args['_'][0] == 'off') { response = 'Stopping APF tunnel'; try { @@ -2326,19 +2291,34 @@ function createMeshCore(agent) } catch (e) { response += JSON.stringify(e); } - apftunnel=null; + apftunnel = null; } else { response = 'Invalid command.\r\nCmd syntax: apf on|off'; } - } else { - response = 'APF tunnel is '+ (apftunnel == null ? 'off': 'on' ); + } else { + response = 'APF tunnel is ' + (apftunnel == null ? 'off' : 'on'); } - + } else { response = 'APF tunnel requires Intel AMT'; } break; } + case 'plugin': { + if (typeof args['_'][0] == 'string') { + try { + // Pass off the action to the plugin + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js + // to control the output / actions here. + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); + } catch (e) { + response = 'There was an error in the plugin (' + e + ')'; + } + } else { + response = 'Proper usage: plugin [pluginName] [args].'; + } + break; + } default: { // This is an unknown command, return an error message response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.'; break; @@ -2347,7 +2327,7 @@ function createMeshCore(agent) } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); } if (response != null) { sendConsoleText(response, sessionid); } } - + // Send a mesh agent console command function sendConsoleText(text, sessionid) { if (typeof text == 'object') { text = JSON.stringify(text); } @@ -2356,7 +2336,7 @@ function createMeshCore(agent) // Called before the process exits //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } - + // Called when the server connection state changes function handleServerConnection(state) { meshServerConnectionState = state; @@ -2381,13 +2361,13 @@ function createMeshCore(agent) if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes } } - + // Update the server with the latest network interface information var sendNetworkUpdateNagleTimer = null; function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } function sendNetworkUpdate(force) { sendNetworkUpdateNagleTimer = null; - + // Update the network interfaces information data var netInfo = mesh.NetInfo; if (netInfo) { @@ -2396,7 +2376,7 @@ function createMeshCore(agent) if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } } } - + // Called periodically to check if we need to send updates to the server function sendPeriodicServerUpdate(flags) { if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. @@ -2451,8 +2431,10 @@ function createMeshCore(agent) //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (ex) { pr = null; } } // Pending reboot if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } } + + // TODO: add plugin hook here } - + // Starting function obj.start = function () { @@ -2485,16 +2467,16 @@ function createMeshCore(agent) //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); } catch (ex) { } } - + obj.stop = function () { mesh.AddCommandHandler(null); mesh.AddConnectHandler(null); } - + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } - + function onWebSocketUpgrade(response, s, head) { sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); this.s = s; diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index 21b632ca..8f177891 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -37,12 +37,10 @@ var MESHRIGHT_NOFILES = 1024; var MESHRIGHT_NOAMT = 2048; var MESHRIGHT_LIMITEDINPUT = 4096; -function createMeshCore(agent) -{ +function createMeshCore(agent) { var obj = {}; - if (process.platform == 'darwin' && !process.versions) - { + if (process.platform == 'darwin' && !process.versions) { // This is an older MacOS Agent, so we'll need to check the service definition so that Auto-Update will function correctly var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; @@ -51,21 +49,18 @@ function createMeshCore(agent) child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); print kn2[1]; } }"); child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n"); child.waitExit(); - if (child.stdout.str.trim() == 'Crashed') - { + if (child.stdout.str.trim() == 'Crashed') { child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write("launchctl list | grep 'meshagent' | awk '{ if($3==\"meshagent\"){print $1;}}'\nexit\n"); child.waitExit(); - if (parseInt(child.stdout.str.trim()) == process.pid) - { + if (parseInt(child.stdout.str.trim()) == process.pid) { // The currently running MeshAgent is us, so we can continue with the update var plist = require('fs').readFileSync('/Library/LaunchDaemons/meshagent_osx64_LaunchDaemon.plist').toString(); var tokens = plist.split('KeepAlive'); - if (tokens[1].split('>')[0].split('<')[1] == 'dict') - { + if (tokens[1].split('>')[0].split('<')[1] == 'dict') { var tmp = tokens[1].split(''); tmp.shift(); tokens[1] = '\n ' + tmp.join(''); @@ -125,10 +120,8 @@ function createMeshCore(agent) if (process.platform != 'win32') { try { require('fs').unlinkSync(process.cwd() + '/DAIPC'); } catch (ee) { } } obj.DAIPC.IPCPATH = process.platform == 'win32' ? ('\\\\.\\pipe\\' + require('_agentNodeId')() + '-DAIPC') : (process.cwd() + '/DAIPC'); try { obj.DAIPC.listen({ path: obj.DAIPC.IPCPATH }); } catch (e) { } - obj.DAIPC.on('connection', function (c) - { - c._send = function (j) - { + obj.DAIPC.on('connection', function (c) { + c._send = function (j) { var data = JSON.stringify(j); var packet = Buffer.alloc(data.length + 4); packet.writeUInt32LE(data.length + 4, 0); @@ -138,32 +131,26 @@ function createMeshCore(agent) this._daipc = c; c.parent = this; c.on('end', function () { console.log('Connection Closed'); this.parent._daipc = null; }); - c.on('data', function (chunk) - { + c.on('data', function (chunk) { if (chunk.length < 4) { this.unshift(chunk); return; } var len = chunk.readUInt32LE(0); if (len > 8192) { this.parent._daipc = null; this.end(); return; } if (chunk.length < len) { this.unshift(chunk); return; } - + var data = chunk.slice(4, len); - try - { + try { data = JSON.parse(data.toString()); } - catch(de) - { + catch (de) { this.parent._daipc = null; this.end(); return; } - + if (!data.cmd) { this.parent._daipc = null; this.end(); return; } - try - { - switch(data.cmd) - { + try { + switch (data.cmd) { case 'query': - switch(data.value) - { + switch (data.value) { case 'connection': data.result = require('MeshAgent').ConnectedServer; this._send(data); @@ -176,32 +163,26 @@ function createMeshCore(agent) return; } } - catch(xe) - { + catch (xe) { this.parent._daipc = null; this.end(); return; } }); }); - function diagnosticAgent_uninstall() - { + function diagnosticAgent_uninstall() { require('service-manager').manager.uninstallService('meshagentDiagnostic'); require('task-scheduler').delete('meshagentDiagnostic/periodicStart'); }; - function diagnosticAgent_installCheck(install) - { - try - { + function diagnosticAgent_installCheck(install) { + try { var diag = require('service-manager').manager.getService('meshagentDiagnostic'); return (diag); } - catch (e) - { + catch (e) { } if (!install) { return (null); } var svc = null; - try - { + try { require('service-manager').manager.installService( { name: 'meshagentDiagnostic', @@ -213,8 +194,7 @@ function createMeshCore(agent) }); svc = require('service-manager').manager.getService('meshagentDiagnostic'); } - catch (e) - { + catch (e) { return (null); } var proxyConfig = require('global-tunnel').proxyConfig; @@ -227,12 +207,10 @@ function createMeshCore(agent) ddb.Put('MeshServer', require('MeshAgent').ServerInfo.ServerUri); if (cert.root.pfx) { ddb.Put('SelfNodeCert', cert.root.pfx); } if (cert.tls) { ddb.Put('SelfNodeTlsCert', cert.tls.pfx); } - if (proxyConfig) - { + if (proxyConfig) { ddb.Put('WebProxy', proxyConfig.host + ':' + proxyConfig.port); } - else - { + else { ddb.Put('ignoreProxyFile', '1'); } @@ -242,7 +220,7 @@ function createMeshCore(agent) delete ddb; // Set a recurrent task, to run the Diagnostic Agent every 2 days - require('task-scheduler').create({name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic'}); + require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: 2, time: require('tls').generateRandomInteger('0', '23') + ':' + require('tls').generateRandomInteger('0', '59').padStart(2, '0'), service: 'meshagentDiagnostic' }); //require('task-scheduler').create({ name: 'meshagentDiagnostic/periodicStart', daily: '1', time: '17:16', service: 'meshagentDiagnostic' }); return (svc); @@ -281,7 +259,7 @@ function createMeshCore(agent) } obj.borderManager = new borderController(); */ - + // 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, 32 = Temporary Agent, 64 = Recovery Agent @@ -397,7 +375,7 @@ function createMeshCore(agent) }); } } catch (ex) { sendConsoleText("ex1: " + ex); } - + // Try to load up the WIFI scanner try { var wifiScannerLib = require('wifi-scanner'); @@ -436,7 +414,7 @@ function createMeshCore(agent) } catch (e) { return false; } } - + // Remove all Gateway MAC addresses for interface list. This is useful because the gateway MAC is not always populated reliably. function clearGatewayMac(str) { if (str == null) return null; @@ -444,7 +422,7 @@ function createMeshCore(agent) for (var i in x.netif) { if (x.netif[i].gatewaymac) { delete x.netif[i].gatewaymac } } return JSON.stringify(x); } - + function getIpLocationData(func) { // Get the location information for the cache if possible var publicLocationInfo = db.Get('publicLocationInfo'); @@ -483,7 +461,7 @@ function createMeshCore(agent) } } } - + // Polyfill String.endsWith if (!String.prototype.endsWith) { String.prototype.endsWith = function (searchString, position) { @@ -494,7 +472,7 @@ function createMeshCore(agent) return lastIndex !== -1 && lastIndex === position; }; } - + // Polyfill path.join obj.path = { join: function () { @@ -513,19 +491,19 @@ function createMeshCore(agent) return x.join('/'); } }; - + // Replace a string with a number if the string is an exact number function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } - + // Convert decimal to hex function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); } - + // Convert a raw string to a hex string function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; } - + // Convert a buffer into a string function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; } - + // Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster function hex2rstr(d) { if (typeof d != "string" || d.length == 0) return ''; @@ -533,7 +511,7 @@ function createMeshCore(agent) while (t = m.shift()) r += String.fromCharCode('0x' + t); return r } - + // Convert an object to string with all functions function objToString(x, p, pad, ret) { if (ret == undefined) ret = ''; @@ -548,17 +526,17 @@ function createMeshCore(agent) 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) + '}'; } - + // Return p number of spaces function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; } - + // 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; @@ -574,7 +552,7 @@ function createMeshCore(agent) if (current != null) { results[current] = true; } return results; } - + // Get server target url with a custom path function getServerTargetUrl(path) { var x = mesh.ServerUrl; @@ -585,13 +563,13 @@ function createMeshCore(agent) 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; } - + // Send a wake-on-lan packet function sendWakeOnLan(hexMac) { var count = 0; @@ -600,7 +578,7 @@ function createMeshCore(agent) var magic = 'FFFFFFFFFFFF'; for (var x = 1; x <= 16; ++x) { magic += hexMac; } var magicbin = Buffer.from(magic, 'hex'); - + for (var adapter in interfaces) { if (interfaces.hasOwnProperty(adapter)) { for (var i = 0; i < interfaces[adapter].length; ++i) { @@ -618,7 +596,7 @@ function createMeshCore(agent) } catch (e) { } return count; } - + // Handle a mesh agent command function handleServerCommand(data) { if (typeof data == 'object') { @@ -722,7 +700,7 @@ function createMeshCore(agent) // Open a local web browser and return success/fail MeshServerLog('Opening: ' + data.url, data); sendConsoleText('OpenURL: ' + data.url); - if (data.url) { mesh.SendCommand({ "action": "msg", "type":"openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); } + if (data.url) { mesh.SendCommand({ "action": "msg", "type": "openUrl", "url": data.url, "sessionid": data.sessionid, "success": (openUserDesktopUrl(data.url) != null) }); } break; } case 'getclip': { @@ -834,11 +812,10 @@ function createMeshCore(agent) case 'plugin': { if (typeof data.pluginaction == 'string') { try { - MeshServerLog('Plugin called', data); - /* Not yet implmented - require(data.plugin.name).serveraction(data);*/ - } catch(e) { + // Not yet implemented + // require(data.plugin.name).serveraction(data); + } catch (e) { MeshServerLog('Error calling plugin', data); } } @@ -850,7 +827,7 @@ function createMeshCore(agent) } } } - + // Called when a file changed in the file system /* function onFileWatcher(a, b) { @@ -902,8 +879,8 @@ function createMeshCore(agent) pr.then(defragResult, defragResult); } else { */ - results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); - func(results); + results.hash = require('SHA384Stream').create().syncHash(JSON.stringify(results)).toString('hex'); + func(results); //} } catch (ex) { func(null, ex); } } @@ -947,7 +924,7 @@ function createMeshCore(agent) } return response; } - + // Tunnel callback operations function onTunnelUpgrade(response, s, head) { this.s = s; @@ -1003,7 +980,7 @@ function createMeshCore(agent) peerTunnel.s.first = true; peerTunnel.s.resume(); } - + // Called when we get data from the server for a TCP relay (We have to skip the first received 'c' and pipe the rest) function onTcpRelayServerTunnelData(data) { if (this.first == true) { this.first = false; this.pipe(this.tcprelay); } // Pipe Server --> Target @@ -1013,7 +990,7 @@ function createMeshCore(agent) if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls. //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete tunnels[this.httprequest.index]; - + /* // Close the watcher if required if (this.httprequest.watcher != undefined) { @@ -1045,7 +1022,7 @@ function createMeshCore(agent) function onTunnelData(data) { //console.log("OnTunnelData"); //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + 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. @@ -1082,17 +1059,14 @@ function createMeshCore(agent) return; } - this.end = function () - { - if (process.platform == "win32") - { + this.end = function () { + if (process.platform == "win32") { // Unpipe the web socket this.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this); } // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). - if (this.rtcchannel) - { + if (this.rtcchannel) { this.rtcchannel.unpipe(this.httprequest._term); if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } } @@ -1100,27 +1074,21 @@ function createMeshCore(agent) // Clean up if (this.httprequest._term) { this.httprequest._term.end(); } this.httprequest._term = null; - } else - { + } else { // TODO!! } }; // Remote terminal using native pipes - if (process.platform == "win32") - { - try - { - if ((this.httprequest.protocol == 6) && (require('win-terminal').PowerShellCapable() == true)) - { + if (process.platform == "win32") { + try { + if ((this.httprequest.protocol == 6) && (require('win-terminal').PowerShellCapable() == true)) { this.httprequest._term = require('win-terminal').StartPowerShell(80, 25); - } else - { + } else { this.httprequest._term = require('win-terminal').Start(80, 25); } } - catch(e) - { + catch (e) { MeshServerLog('Failed to start remote terminal session, ' + e.toString() + ' (' + this.httprequest.remoteaddr + ')', this.httprequest); this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); this.end(); @@ -1129,14 +1097,11 @@ function createMeshCore(agent) 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 () { console.log('Terminal was closed'); }); }); - } else - { - if (fs.existsSync("/bin/bash")) - { + } else { + if (fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/bin/bash", ["bash", "-i"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("alias ls='ls --color=auto'\nclear\n"); } - } else - { + } else { this.httprequest.process = childProcess.execFile("/bin/sh", ["sh"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("stty erase ^H\nalias ls='ls --color=auto'\nPS1='\\u@\\h:\\w\\$ '\nclear\n"); } } @@ -1189,8 +1154,7 @@ function createMeshCore(agent) this.removeAllListeners('data'); this.on('data', onTunnelControlData); //this.write('MeshCore Terminal Hello'); - } else if (this.httprequest.protocol == 2) - { + } else if (this.httprequest.protocol == 2) { // Check user access rights for desktop if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)) { // Disengage this tunnel, user does not have the rights to do this!! @@ -1222,7 +1186,7 @@ function createMeshCore(agent) if (this.desktop.kvm.connectionCount == 0) { // Display a toast message. This may not be supported on all platforms. // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { } - + this.httprequest.desktop.kvm.end(); } }; @@ -1238,8 +1202,7 @@ function createMeshCore(agent) } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 8)) - { + if (this.httprequest.consent && (this.httprequest.consent & 8)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); @@ -1248,8 +1211,7 @@ function createMeshCore(agent) this.pause(); pr.then( - function () - { + function () { // Success MeshServerLog('Starting remote desktop after local user accepted (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); @@ -1260,15 +1222,13 @@ function createMeshCore(agent) this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); this.ws.resume(); }, - function (e) - { + function (e) { // User Consent Denied/Failed MeshServerLog('Failed to start remote desktop after local user rejected (' + this.ws.httprequest.remoteaddr + ')', this.ws.httprequest); this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() })); }); } - else - { + else { // User Consent Prompt is not required if (this.httprequest.consent && (this.httprequest.consent & 1)) { // User Notifications is required @@ -1296,8 +1256,7 @@ function createMeshCore(agent) } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 32)) - { + if (this.httprequest.consent && (this.httprequest.consent & 32)) { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'Waiting for user to grant access...' })); @@ -1374,7 +1333,7 @@ function createMeshCore(agent) var response = getDirectoryInfo(cmd.path); if (cmd.reqid != undefined) { response.reqid = cmd.reqid; } this.write(new Buffer(JSON.stringify(response))); - + /* // Start the directory watcher if ((cmd.path != '') && (samepath == false)) { @@ -1493,25 +1452,22 @@ function createMeshCore(agent) break; } } else if (this.httprequest.protocol == 7) { // plugin data exchange - var cmd = null; - try { cmd = JSON.parse(data); } catch (e) { }; - if (cmd == null) { return; } - if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. - if (cmd.action == undefined) { return; } + var cmd = null; + try { cmd = JSON.parse(data); } catch (e) { }; + if (cmd == null) { return; } + if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { onTunnelControlData(cmd, this); return; } // If this is control data, handle it now. + if (cmd.action == undefined) return; + + switch (cmd.action) { + case 'plugin': { + try { require(cmd.plugin).consoleaction(cmd, null, null, this); } catch (e) { throw e; } + break; + } + default: { + // probably shouldn't happen, but just in case this feature is expanded + } + } - switch (cmd.action) { - case 'plugin': { - try { - require(cmd.plugin).consoleaction(cmd, null, null, this); - } catch (e) { throw e; } - - break; - } - default: { - // probably shouldn't happen, but just in case this feature is expanded - } - } - } //sendConsoleText("Got tunnel #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } @@ -1586,12 +1542,10 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc0') { // Browser indicates we can start WebRTC switch-over. if (ws.httprequest.protocol == 1) { // Terminal // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.httprequest._term.unpipe(ws); } - else - { + else { ws.httprequest.process.stdout.unpipe(ws); ws.httprequest.process.stderr.unpipe(ws); } @@ -1608,13 +1562,11 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc1') { if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal // Switch the user input from websocket to webrtc at this point. - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.unpipe(ws.httprequest._term); ws.rtcchannel.pipe(ws.httprequest._term, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } - else - { + else { ws.unpipe(ws.httprequest.process.stdin); ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } @@ -1629,12 +1581,10 @@ function createMeshCore(agent) } else if (obj.type == 'webrtc2') { // Other side received websocket end of data marker, start sending data on WebRTC channel if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal - if (process.platform == 'win32') - { + if (process.platform == 'win32') { ws.httprequest._term.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } - else - { + else { ws.httprequest.process.stdout.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. ws.httprequest.process.stderr.pipe(ws.webrtc.rtcchannel, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. } @@ -1674,7 +1624,7 @@ function createMeshCore(agent) // Console state var consoleWebSockets = {}; var consoleHttpRequest = null; - + // Console HTTP response function consoleHttpResponse(response) { response.data = function (data) { sendConsoleText(rstr2hex(buf2rstr(data)), this.sessionid); consoleHttpRequest = null; } @@ -1835,7 +1785,7 @@ function createMeshCore(agent) case 'ps': { processManager.getProcesses(function (plist) { var x = ''; - for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user):'') + '\r\n'; } + for (var i in plist) { x += i + ', ' + plist[i].cmd + ((plist[i].user) ? (', ' + plist[i].user) : '') + '\r\n'; } sendConsoleText(x, sessionid); }); break; @@ -1869,14 +1819,11 @@ function createMeshCore(agent) break; } case 'dump': - if (args['_'].length < 1) - { + if (args['_'].length < 1) { response = 'Proper usage: dump [on/off/status]'; // Display correct command usage } - else - { - switch(args['_'][0].toLowerCase()) - { + else { + switch (args['_'][0].toLowerCase()) { case 'on': process.coreDumpLocation = process.platform == 'win32' ? process.execPath.replace('.exe', '.dmp') : (process.execPath + '.dmp'); response = 'enabled'; @@ -1886,12 +1833,10 @@ function createMeshCore(agent) response = 'disabled'; break; case 'status': - if (process.coreDumpLocation) - { + if (process.coreDumpLocation) { response = 'Core Dump: [ENABLED' + (require('fs').existsSync(process.coreDumpLocation) ? (', (DMP file exists)]') : (']')); } - else - { + else { response = 'Core Dump: [DISABLED]'; } break; @@ -1911,18 +1856,14 @@ function createMeshCore(agent) } case 'uninstallagent': var agentName = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; - if (!require('service-manager').manager.getService(agentName).isMe()) - { + if (!require('service-manager').manager.getService(agentName).isMe()) { response = 'Uininstall failed, this instance is not the service instance'; } - else - { - try - { + else { + try { diagnosticAgent_uninstall(); } - catch(x) - { + catch (x) { } var js = "require('service-manager').manager.getService('" + agentName + "').stop(); require('service-manager').manager.uninstallService('" + agentName + "'); process.exit();"; this.child = require('child_process').execFile(process.execPath, [process.platform == 'win32' ? (process.execPath.split('\\').pop()) : (process.execPath.split('/').pop()), '-b64exec', Buffer.from(js).toString('base64')], { type: 4, detached: true }); @@ -2101,7 +2042,7 @@ function createMeshCore(agent) if (httprequest != null) { httprequest.upgrade = onWebSocketUpgrade; httprequest.on('error', function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }); - + var index = 1; while (consoleWebSockets[index]) { index++; } httprequest.sessionid = sessionid; @@ -2289,40 +2230,31 @@ function createMeshCore(agent) } case 'diagnostic': { - if (!mesh.DAIPC.listening) - { + if (!mesh.DAIPC.listening) { response = 'Unable to bind to Diagnostic IPC, most likely because the path (' + process.cwd() + ') is not on a local file system'; break; } var diag = diagnosticAgent_installCheck(); - if (diag) - { - if (args['_'].length == 1 && args['_'][0] == 'uninstall') - { + if (diag) { + if (args['_'].length == 1 && args['_'][0] == 'uninstall') { diagnosticAgent_uninstall(); response = 'Diagnostic Agent uninstalled'; } - else - { + else { response = 'Diagnostic Agent installed at: ' + diag.appLocation(); } } - else - { - if (args['_'].length == 1 && args['_'][0] == 'install') - { + else { + if (args['_'].length == 1 && args['_'][0] == 'install') { diag = diagnosticAgent_installCheck(true); - if (diag) - { + if (diag) { response = 'Diagnostic agent was installed at: ' + diag.appLocation(); } - else - { + else { response = 'Diagnostic agent installation failed'; } } - else - { + else { response = 'Diagnostic Agent Not installed. To install: diagnostic install'; } } @@ -2330,27 +2262,27 @@ function createMeshCore(agent) break; } case 'apf': { - if (meshCoreObj.intelamt!==null) { + if (meshCoreObj.intelamt !== null) { if (args['_'].length == 1) { if (args['_'][0] == 'on') { response = 'Starting APF tunnel' var apfarg = { - mpsurl: mesh.ServerUrl.replace('agent.ashx','apf.ashx'), - mpsuser: Buffer.from(mesh.ServerInfo.MeshID,'hex').toString('base64').substring(0,16), - mpspass: Buffer.from(mesh.ServerInfo.MeshID,'hex').toString('base64').substring(0,16), + mpsurl: mesh.ServerUrl.replace('agent.ashx', 'apf.ashx'), + mpsuser: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), + mpspass: Buffer.from(mesh.ServerInfo.MeshID, 'hex').toString('base64').substring(0, 16), mpskeepalive: 60000, clientname: require('os').hostname(), clientaddress: '127.0.0.1', clientuuid: meshCoreObj.intelamt.uuid }; var tobj = { debug: false }; // - apftunnel= require('apfclient')(tobj,apfarg); + apftunnel = require('apfclient')(tobj, apfarg); try { apftunnel.connect(); response += "..success"; } catch (e) { response += JSON.stringify(e); - } + } } else if (args['_'][0] == 'off') { response = 'Stopping APF tunnel'; try { @@ -2359,14 +2291,14 @@ function createMeshCore(agent) } catch (e) { response += JSON.stringify(e); } - apftunnel=null; + apftunnel = null; } else { response = 'Invalid command.\r\nCmd syntax: apf on|off'; } - } else { - response = 'APF tunnel is '+ (apftunnel == null ? 'off': 'on' ); + } else { + response = 'APF tunnel is ' + (apftunnel == null ? 'off' : 'on'); } - + } else { response = 'APF tunnel requires Intel AMT'; } @@ -2375,17 +2307,16 @@ function createMeshCore(agent) case 'plugin': { if (typeof args['_'][0] == 'string') { try { - // pass off the action to the plugin + // Pass off the action to the plugin // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js // to control the output / actions here. response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh); - } catch(e) { + } catch (e) { response = 'There was an error in the plugin (' + e + ')'; } } else { - response = 'Proper usage: plugin [pluginName] [args].'; + response = 'Proper usage: plugin [pluginName] [args].'; } - break; } default: { // This is an unknown command, return an error message @@ -2396,7 +2327,7 @@ function createMeshCore(agent) } catch (e) { response = 'Command returned an exception error: ' + e; console.log(e); } if (response != null) { sendConsoleText(response, sessionid); } } - + // Send a mesh agent console command function sendConsoleText(text, sessionid) { if (typeof text == 'object') { text = JSON.stringify(text); } @@ -2405,7 +2336,7 @@ function createMeshCore(agent) // Called before the process exits //process.exit = function (code) { console.log("Exit with code: " + code.toString()); } - + // Called when the server connection state changes function handleServerConnection(state) { meshServerConnectionState = state; @@ -2430,13 +2361,13 @@ function createMeshCore(agent) if (selfInfoUpdateTimer == null) { selfInfoUpdateTimer = setInterval(sendPeriodicServerUpdate, 1200000); } // 20 minutes } } - + // Update the server with the latest network interface information var sendNetworkUpdateNagleTimer = null; function sendNetworkUpdateNagle() { if (sendNetworkUpdateNagleTimer != null) { clearTimeout(sendNetworkUpdateNagleTimer); sendNetworkUpdateNagleTimer = null; } sendNetworkUpdateNagleTimer = setTimeout(sendNetworkUpdate, 5000); } function sendNetworkUpdate(force) { sendNetworkUpdateNagleTimer = null; - + // Update the network interfaces information data var netInfo = mesh.NetInfo; if (netInfo) { @@ -2445,7 +2376,7 @@ function createMeshCore(agent) if ((force == true) || (clearGatewayMac(netInfoStr) != clearGatewayMac(lastNetworkInfo))) { mesh.SendCommand(netInfo); lastNetworkInfo = netInfoStr; } } } - + // Called periodically to check if we need to send updates to the server function sendPeriodicServerUpdate(flags) { if (meshServerConnectionState == 0) return; // Not connected to server, do nothing. @@ -2500,10 +2431,10 @@ function createMeshCore(agent) //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (ex) { pr = null; } } // Pending reboot if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); } } - + // TODO: add plugin hook here } - + // Starting function obj.start = function () { @@ -2536,16 +2467,16 @@ function createMeshCore(agent) //userSession.on('unlocked', function (user) { sendConsoleText('[' + (user.Domain ? user.Domain + '\\' : '') + user.Username + '] has UNLOCKED the desktop'); }); } catch (ex) { } } - + obj.stop = function () { mesh.AddCommandHandler(null); mesh.AddConnectHandler(null); } - + function onWebSocketClosed() { sendConsoleText("WebSocket #" + this.httprequest.index + " closed.", this.httprequest.sessionid); delete consoleWebSockets[this.httprequest.index]; } function onWebSocketData(data) { sendConsoleText("Got WebSocket #" + this.httprequest.index + " data: " + data, this.httprequest.sessionid); } function onWebSocketSendOk() { sendConsoleText("WebSocket #" + this.index + " SendOK.", this.sessionid); } - + function onWebSocketUpgrade(response, s, head) { sendConsoleText("WebSocket #" + this.index + " connected.", this.sessionid); this.s = s; diff --git a/meshagent.js b/meshagent.js index 9224db2d..4593df2d 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1278,14 +1278,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { break; } case 'plugin': { - if (typeof command.plugin == 'string') { - try { + if (typeof command.plugin != 'string') break; + try { var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent); pluginHandler.plugins[command.plugin].serveraction(command, obj, parent); - } catch (e) { - - console.log('Error loading plugin handler ('+ e + ')'); - } + } catch (e) { + console.log('Error loading plugin handler (' + e + ')'); } break; } diff --git a/meshcentral.js b/meshcentral.js index a1583df1..b93939fe 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -31,6 +31,7 @@ function CreateMeshCentralServer(config, args) { obj.swarmserver = null; obj.mailserver = null; obj.amtEventHandler = null; + obj.pluginHandler = null; obj.amtScanner = null; obj.meshScanner = null; obj.letsencrypt = null; @@ -702,6 +703,11 @@ function CreateMeshCentralServer(config, args) { return; } + // Start plugin manager if configuration allows this. + if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null)) { + obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj); + } + // Load the default meshcore and meshcmd obj.updateMeshCore(); obj.updateMeshCmd(); @@ -865,10 +871,8 @@ function CreateMeshCentralServer(config, args) { // Dispatch an event that the server is now running obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' }); - obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj); - // Plugin hook. Need to run something at server startup? This is the place. - obj.pluginHandler.callHook("server_startup"); + if (obj.pluginHandler) { obj.pluginHandler.callHook("server_startup"); } // Load the login cookie encryption key from the database if allowed if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) { @@ -1352,8 +1356,10 @@ function CreateMeshCentralServer(config, args) { } } } - obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj); - obj.pluginHandler.addMeshCoreModules(modulesAdd); + + // Add plugins to cores + if (obj.pluginHandler) { obj.pluginHandler.addMeshCoreModules(modulesAdd); } + // Merge the cores and compute the hashes for (var i in modulesAdd) { if ((i == 'windows-recovery') || (i == 'linux-recovery')) { diff --git a/meshrelay.js b/meshrelay.js index 9d5676cd..7798807b 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -198,7 +198,11 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (xdevicename2 != null) { metadata.devicename = xdevicename2; } var firstBlock = JSON.stringify(metadata); recordingEntry(fd, 1, ((req.query.browser) ? 2 : 0), firstBlock, function () { - relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false }; + try { relayinfo.peer1.ws.logfile = ws.logfile = { fd: fd, lock: false }; } catch (ex) { + try { ws.send('c'); } catch (ex) { } // Send connect to both peers, 'cr' indicates the session is being recorded. + try { relayinfo.peer1.ws.send('c'); } catch (ex) { } + return; + } try { ws.send('cr'); } catch (ex) { } // Send connect to both peers, 'cr' indicates the session is being recorded. try { relayinfo.peer1.ws.send('cr'); } catch (ex) { } }); diff --git a/package.json b/package.json index 05dcb330..fe9aed58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.1-y", + "version": "0.4.1-z", "keywords": [ "Remote Management", "Intel AMT", diff --git a/pluginHandler.js b/pluginHandler.js index 2eb93294..cb96ec42 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -23,144 +23,136 @@ module.exports.pluginHandler = function (parent) { obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins'); obj.plugins = {}; obj.exports = {}; - - try { - obj.enabled = obj.parent.config.settings.plugins.enabled; - obj.loadList = obj.parent.config.settings.plugins.list; - } catch (e) { // Config file options not present, disable self - obj.enabled = false; - obj.loadList = {}; - console.log('Plugin options not added to the config file. Plugins disabled. Please see the documentation.'); - } - - if (obj.enabled) { - obj.loadList.forEach(function(plugin, index) { - if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) { - try { - obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj); - obj.exports[plugin] = obj.plugins[plugin].exports; - } catch (e) { - console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled.", e.stack); - } - } - }); - } - - obj.prepExports = function() { - var str = 'function() {\r\n'; - str += ' var obj = {};\r\n'; - - for (const p of Object.keys(obj.plugins)) { - str += ' obj.'+ p +' = {};\r\n'; - for (const l of Object.values(obj.exports[p])) { - str += ' obj.'+ p +'.'+ l + ' = '+ obj.plugins[p][l].toString()+'\r\n'; - } - } - - str += 'obj.enabled = '+ obj.enabled +';\r\n'; - str += `obj.onDeviceRefeshEnd = function(nodeid, panel, refresh, event) { - for (const p of Object.keys(obj)) { - if (typeof obj[p].onDeviceRefreshEnd == 'function') { - obj[p].onDeviceRefreshEnd(nodeid, panel, refresh, event); - } - } - }; - obj.registerPluginTab = function(pluginRegInfo) { - var d = pluginRegInfo(); - if (!Q(d.tabId)) { - QA('p19headers', ''+d.tabTitle+''); - } - }; - obj.callPluginPage = function(id) { - var pages = Q('p19pages').querySelectorAll("#p19pages>div"); - for (const i of pages) { - i.style.display = 'none'; - } - QV(id, true); - }; - return obj; };`; - return str; - } - - obj.callHook = function(hookName, ...args) { - for (var p in obj.plugins) { - if (typeof obj.plugins[p][hookName] == 'function') { - try { - obj.plugins[p][hookName](args); - } catch (e) { - console.log('Error ocurred while running plugin hook' + p + ':' + hookName + ' (' + e + ')'); - } - } - } - }; - - obj.addMeshCoreModules = function(modulesAdd) { - if (obj.enabled !== true) return; - for (var plugin in obj.plugins) { - var moduleDirPath = null; - var modulesDir = null; - //if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(obj.pluginPath, 'modules_meshcore_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Favor minified modules if present. - if (modulesDir == null) { try { moduleDirPath = obj.path.join(obj.pluginPath, plugin + '/modules_meshcore'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Use non-minified mofules. - if (modulesDir != null) { - for (var i in modulesDir) { - if (modulesDir[i].toLowerCase().endsWith('.js')) { - var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3); - if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files. - var moduleData = [ 'try { addModule("', moduleName, '", "', obj.parent.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (e) { }\r\n' ]; + obj.loadList = obj.parent.config.settings.plugins.list; - // Merge this module - // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms. - if (moduleName.startsWith('amt-') || (moduleName == 'smbios')) { - // Add to IA / Intel AMT cores only - modulesAdd['windows-amt'].push(...moduleData); - modulesAdd['linux-amt'].push(...moduleData); - } else if (moduleName.startsWith('win-')) { - // Add to Windows cores only - modulesAdd['windows-amt'].push(...moduleData); - } else if (moduleName.startsWith('linux-')) { - // Add to Linux cores only - modulesAdd['linux-amt'].push(...moduleData); - modulesAdd['linux-noamt'].push(...moduleData); - } else { - // Add to all cores - modulesAdd['windows-amt'].push(...moduleData); - modulesAdd['linux-amt'].push(...moduleData); - modulesAdd['linux-noamt'].push(...moduleData); - } - - // Merge this module to recovery modules if needed - if (modulesAdd['windows-recovery'] != null) { - if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) { - modulesAdd['windows-recovery'].push(...moduleData); - } - } + if (typeof obj.loadList != 'object') { + obj.loadList = {}; + console.log('Plugin list not specified, please fix configuration file.'); + return null; + } - // 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'].push(...moduleData); - } - } - } - } + obj.loadList.forEach(function (plugin, index) { + if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) { + try { + obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj); + obj.exports[plugin] = obj.plugins[plugin].exports; + } catch (e) { + console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled.", e.stack); } - } - }; - - obj.deviceViewPanel = function() { - var panel = {}; - for (var p in obj.plugins) { - if (typeof obj.plugins[p][hookName] == 'function') { - try { - panel[p].header = obj.plugins[p].on_device_header(); - panel[p].content = obj.plugins[p].on_device_page(); - } catch (e) { - console.log('Error ocurred while getting plugin views ' + p + ':' + ' (' + e + ')'); - } - } - } - return panel; + } + }); + + obj.prepExports = function () { + var str = 'function() {\r\n'; + str += ' var obj = {};\r\n'; + + for (const p of Object.keys(obj.plugins)) { + str += ' obj.' + p + ' = {};\r\n'; + for (const l of Object.values(obj.exports[p])) { + str += ' obj.' + p + '.' + l + ' = ' + obj.plugins[p][l].toString() + '\r\n'; + } + } + + str += `obj.onDeviceRefeshEnd = function(nodeid, panel, refresh, event) { + for (const p of Object.keys(obj)) { + if (typeof obj[p].onDeviceRefreshEnd == 'function') { + obj[p].onDeviceRefreshEnd(nodeid, panel, refresh, event); + } + } + }; + obj.registerPluginTab = function(pluginRegInfo) { + var d = pluginRegInfo(); + if (!Q(d.tabId)) { + QA('p19headers', ''+d.tabTitle+''); + } + }; + obj.callPluginPage = function(id) { + var pages = Q('p19pages').querySelectorAll("#p19pages>div"); + for (const i of pages) { i.style.display = 'none'; } + QV(id, true); + }; + return obj; };`; + return str; } - + + obj.callHook = function (hookName, ...args) { + for (var p in obj.plugins) { + if (typeof obj.plugins[p][hookName] == 'function') { + try { + obj.plugins[p][hookName](args); + } catch (e) { + console.log('Error ocurred while running plugin hook' + p + ':' + hookName + ' (' + e + ')'); + } + } + } + }; + + obj.addMeshCoreModules = function (modulesAdd) { + for (var plugin in obj.plugins) { + var moduleDirPath = null; + var modulesDir = null; + //if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(obj.pluginPath, 'modules_meshcore_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Favor minified modules if present. + if (modulesDir == null) { try { moduleDirPath = obj.path.join(obj.pluginPath, plugin + '/modules_meshcore'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Use non-minified mofules. + if (modulesDir != null) { + for (var i in modulesDir) { + if (modulesDir[i].toLowerCase().endsWith('.js')) { + var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3); + if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files. + var moduleData = ['try { addModule("', moduleName, '", "', obj.parent.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (e) { }\r\n']; + + // Merge this module + // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms. + if (moduleName.startsWith('amt-') || (moduleName == 'smbios')) { + // Add to IA / Intel AMT cores only + modulesAdd['windows-amt'].push(...moduleData); + modulesAdd['linux-amt'].push(...moduleData); + } else if (moduleName.startsWith('win-')) { + // Add to Windows cores only + modulesAdd['windows-amt'].push(...moduleData); + } else if (moduleName.startsWith('linux-')) { + // Add to Linux cores only + modulesAdd['linux-amt'].push(...moduleData); + modulesAdd['linux-noamt'].push(...moduleData); + } else { + // Add to all cores + modulesAdd['windows-amt'].push(...moduleData); + modulesAdd['linux-amt'].push(...moduleData); + modulesAdd['linux-noamt'].push(...moduleData); + } + + // Merge this module to recovery modules if needed + if (modulesAdd['windows-recovery'] != null) { + if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) { + modulesAdd['windows-recovery'].push(...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'].push(...moduleData); + } + } + } + } + } + } + }; + + obj.deviceViewPanel = function () { + var panel = {}; + for (var p in obj.plugins) { + if (typeof obj.plugins[p][hookName] == 'function') { + try { + panel[p].header = obj.plugins[p].on_device_header(); + panel[p].content = obj.plugins[p].on_device_page(); + } catch (e) { + console.log('Error ocurred while getting plugin views ' + p + ':' + ' (' + e + ')'); + } + } + } + return panel; + } + return obj; }; \ No newline at end of file diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 28619be2..66e162b5 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1,15280 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

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

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 23878299..b30d7fd7 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1036,7 +1036,8 @@ var sessionActivity = Date.now(); var updateSessionTimer = null; var pluginHandlerBuilder = {{{pluginHandler}}}; - var pluginHandler = new pluginHandlerBuilder(); + var pluginHandler = null; + if (pluginHandlerBuilder != null) { pluginHandler = new pluginHandlerBuilder(); } // Console Message Display Timers var p11DeskConsoleMsgTimer = null; @@ -2329,14 +2330,9 @@ break; } case 'plugin': { - if (typeof message.plugin == 'string') { - try { - pluginHandler[message.plugin][message.method](server, message); - } catch (e) { - console.log('Error loading plugin handler ('+ e + ')'); - } - } - break; + if ((pluginHandler == null) || (typeof message.plugin != 'string')) break; + try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); } + break; } default: //console.log('Unknown message.action', message.action); @@ -4390,6 +4386,7 @@ QV('MainDevFiles', ((mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 4) != 0))) && (meshrights & 8) && fileAccess); QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess); QV('MainDevConsole', (consoleRights && (mesh.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 8) != 0))) && (meshrights & 8)); + QV('MainDevPlugins', pluginHandler != null); QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0)); QH('p15coreName', ((node.agent != null) && (node.agent.core != null))?node.agent.core:''); @@ -4435,7 +4432,8 @@ p12clearConsoleMsg(); p13clearConsoleMsg(); - pluginHandler.onDeviceRefeshEnd(nodeid, panel, refresh, event); + // Device refresh plugin handler + if (pluginHandler != null) { pluginHandler.onDeviceRefeshEnd(nodeid, panel, refresh, event); } } setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching. if (!panel) panel = 10; @@ -9168,16 +9166,14 @@ // If we are going to panel 0 in "full screen mode", hide the left bar. QV('topbar', x != 0); - if ((x == 0) && (webPageFullScreen)) { - QC('body').add("arg_hide"); - } + if ((x == 0) && (webPageFullScreen)) { QC('body').add("arg_hide"); } QV('MainSubMenuSpan', x >= 10 && x < 20); QV('UserDummyMenuSpan', (x < 10) && (x != 6) && webPageFullScreen); QV('MeshSubMenuSpan', x >= 20 && x < 30); QV('UserSubMenuSpan', x >= 30 && x < 40); QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41); - var panels = { 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 20: 'MeshGeneral', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 19: 'Plugins', 115: 'ServerConsole' }; + var panels = { 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 115: 'ServerConsole' }; for (var i in panels) { QC(panels[i]).remove('style3x'); QC(panels[i]).remove('style3sel'); diff --git a/webserver.js b/webserver.js index ea227799..a281f2b9 100644 --- a/webserver.js +++ b/webserver.js @@ -1517,14 +1517,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Clean up the U2F challenge if needed if (req.session.u2fchallenge) { delete req.session.u2fchallenge; }; - - var pluginHandler = require('./pluginHandler.js').pluginHandler(parent); - + // Fetch the web state parent.debug('web', 'handleRootRequestEx: success.'); obj.db.Get('ws' + user._id, function (err, states) { var webstate = (states.length == 1) ? states[0].state : ''; - res.render(getRenderPage('default', req), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate), pluginHandler: pluginHandler.prepExports() }); + res.render(getRenderPage('default', req), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate), pluginHandler: (parent.pluginHandler == null)?'null':parent.pluginHandler.prepExports() }); }); } else { // Send back the login application From c3f084aeeece221ff8e356f305b953259586b9ba Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 10 Oct 2019 11:43:21 -0700 Subject: [PATCH 09/14] Fixed chat not working when UserAllowedIP is set. --- package.json | 2 +- webserver.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fe9aed58..f10ad438 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.1-z", + "version": "0.4.2-a", "keywords": [ "Remote Management", "Intel AMT", diff --git a/webserver.js b/webserver.js index a281f2b9..19977b52 100644 --- a/webserver.js +++ b/webserver.js @@ -1697,7 +1697,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Render the messenger application. function handleMessengerRequest(req, res) { - const domain = checkUserIpAddress(req, res); + const domain = getDomain(req); if (domain == null) { parent.debug('web', 'handleMessengerRequest: no domain'); res.sendStatus(404); return; } parent.debug('web', 'handleMessengerRequest()'); From 6beea9ee91ecea60e937627822db0549d477ac44 Mon Sep 17 00:00:00 2001 From: "Christian.Ante" Date: Thu, 10 Oct 2019 16:46:29 -0400 Subject: [PATCH 10/14] Changed readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f435460e..63995635 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -MeshCentral +Hacktober-MeshCentral =========== For more information, [visit MeshCommander.com/MeshCentral2](https://www.meshcommander.com/meshcentral2). From 483f270a23c96cb44ab85185e38b2ddc37539dbe Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 10 Oct 2019 13:46:50 -0700 Subject: [PATCH 11/14] Added checks to catch MongoDB . in key exception. --- db.js | 90 ++++++++++++++++++++++++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/db.js b/db.js index ff530c3d..c15627b4 100644 --- a/db.js +++ b/db.js @@ -607,10 +607,20 @@ module.exports.CreateDB = function (parent, func) { setupFunctions(func); // Completed setup of NeDB } + // Check the object names for a "." + function checkObjectNames(r) { + if (typeof r != 'object') return; + for (var i in r) { + if (i.indexOf('.') >= 0) { throw('BadDbName: ' + JSON.stringify(r)); } + checkObjectNames(r[i]); + } + } + function setupFunctions(func) { if (obj.databaseType == 3) { // Database actions on the main collection (MongoDB) obj.Set = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING obj.file.replaceOne({ _id: data._id }, performTypedRecordEncrypt(data), { upsert: true }, func); }; obj.Get = function (id, func) { @@ -640,11 +650,25 @@ module.exports.CreateDB = function (parent, func) { obj.Remove = function (id) { obj.file.deleteOne({ _id: id }); }; obj.RemoveAll = function (func) { obj.file.deleteMany({}, { multi: true }, func); }; obj.RemoveAllOfType = function (type, func) { obj.file.deleteMany({ type: type }, { multi: true }, func); }; - obj.InsertMany = function (data, func) { obj.file.insertMany(data, func); }; + obj.InsertMany = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING + obj.file.insertMany(data, func); + }; obj.RemoveMeshDocuments = function (id) { obj.file.deleteMany({ meshid: id }, { multi: true }); obj.file.deleteOne({ _id: 'nt' + id }); }; - obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); }; + obj.MakeSiteAdmin = function (username, domain) { + obj.Get('user/' + domain + '/' + username, function (err, docs) { + if (docs.length == 1) { + checkObjectNames(docs[0]); // DEBUG CHECKING + docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); + } + }); + }; obj.DeleteDomain = function (domain, func) { obj.file.deleteMany({ domain: domain }, { multi: true }, func); }; - obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); }; + obj.SetUser = function (user) { + checkObjectNames(user); // DEBUG CHECKING + var u = Clone(user); + if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); + }; obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); }; @@ -656,7 +680,10 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the events collection obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); }; - obj.StoreEvent = function (event) { obj.eventsfile.insertOne(event); }; + obj.StoreEvent = function (event) { + checkObjectNames(event); // DEBUG CHECKING + obj.eventsfile.insertOne(event); + }; obj.GetEvents = function (ids, domain, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); }; obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); }; obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); }; @@ -668,18 +695,27 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); }; - obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); }; + obj.storePowerEvent = function (event, multiServer, func) { + checkObjectNames(event); // DEBUG CHECKING + if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); + }; obj.getPowerTimeline = function (nodeid, func) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }).project({ _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).toArray(func); }; obj.removeAllPowerEvents = function () { obj.powerfile.deleteMany({}, { multi: true }); }; obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.deleteMany({ nodeid: nodeid }, { multi: true }); }; // Database actions on the SMBIOS collection - obj.SetSMBIOS = function (smbios, func) { obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); }; + obj.SetSMBIOS = function (smbios, func) { + checkObjectNames(smbios); // DEBUG CHECKING + obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); + }; obj.RemoveSMBIOS = function (id) { obj.smbiosfile.deleteOne({ _id: id }); }; obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }).toArray(func); }; // Database actions on the Server Stats collection - obj.SetServerStats = function (data, func) { obj.serverstatsfile.insertOne(data, func); }; + obj.SetServerStats = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING + obj.serverstatsfile.insertOne(data, func); + }; obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }).toArray(func); }; // Read a configuration file from the database @@ -706,7 +742,11 @@ module.exports.CreateDB = function (parent, func) { } } else { // Database actions on the main collection (NeDB and MongoJS) - obj.Set = function (data, func) { var xdata = performTypedRecordEncrypt(data); obj.file.update({ _id: xdata._id }, xdata, { upsert: true }, func); }; + obj.Set = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING + var xdata = performTypedRecordEncrypt(data); + obj.file.update({ _id: xdata._id }, xdata, { upsert: true }, func); + }; obj.Get = function (id, func) { if (arguments.length > 2) { var parms = [func]; @@ -734,11 +774,24 @@ module.exports.CreateDB = function (parent, func) { obj.Remove = function (id) { obj.file.remove({ _id: id }); }; obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); }; obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); }; - obj.InsertMany = function (data, func) { obj.file.insert(data, func); }; + obj.InsertMany = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING + obj.file.insert(data, func); + }; obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); }; - obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); }; + obj.MakeSiteAdmin = function (username, domain) { + obj.Get('user/' + domain + '/' + username, function (err, docs) { + if (docs.length == 1) { + checkObjectNames(docs[0]); // DEBUG CHECKING + docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); + } + }); + }; obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); }; - obj.SetUser = function (user) { var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); }; + obj.SetUser = function (user) { + checkObjectNames(user); // DEBUG CHECKING + var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); + }; obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; @@ -746,7 +799,10 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the events collection obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); }; - obj.StoreEvent = function (event) { obj.eventsfile.insert(event); }; + obj.StoreEvent = function (event) { + checkObjectNames(event); // DEBUG CHECKING + obj.eventsfile.insert(event); + }; obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } }; obj.GetEventsWithLimit = function (ids, domain, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func); } }; obj.GetUserEvents = function (ids, domain, username, func) { @@ -770,7 +826,10 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}, func); }; - obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); }; + obj.storePowerEvent = function (event, multiServer, func) { + checkObjectNames(event); // DEBUG CHECKING + if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); + }; obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } }; obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); }; obj.removeAllPowerEventsForNode = function (nodeid) { obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); }; @@ -781,7 +840,10 @@ module.exports.CreateDB = function (parent, func) { obj.GetSMBIOS = function (id, func) { obj.smbiosfile.find({ _id: id }, func); }; // Database actions on the Server Stats collection - obj.SetServerStats = function (data, func) { obj.serverstatsfile.insert(data, func); }; + obj.SetServerStats = function (data, func) { + checkObjectNames(data); // DEBUG CHECKING + obj.serverstatsfile.insert(data, func); + }; obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); }; // Read a configuration file from the database diff --git a/package.json b/package.json index f10ad438..1731a211 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.2-a", + "version": "0.4.2-b", "keywords": [ "Remote Management", "Intel AMT", From f0fa3e52376107e0147d718a7a0f0acbf094716b Mon Sep 17 00:00:00 2001 From: "Christian.Ante" Date: Thu, 10 Oct 2019 16:53:57 -0400 Subject: [PATCH 12/14] Changed readme --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 63995635..79156628 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -Hacktober-MeshCentral +MeshCentral =========== For more information, [visit MeshCommander.com/MeshCentral2](https://www.meshcommander.com/meshcentral2). @@ -75,8 +75,8 @@ Command line arguments on any platform: | ------------------------------------- | ----------- | --notls | Use HTTP instead of HTTPS for the main web server. | --user [username] | Always login as [username] if the account exists. -| --port [number] | Web server port number (default to 443). -| --mpsport [number] | Intel AMT server port number (default to 4433). +| --port [number] | Web server port number (default is 443). +| --mpsport [number] | Intel AMT server port number (default is 4433). | --redirport [number] | Redirection web server, redirects users to the HTTPS server (default to 80). | --exactports | Server must run with correct ports or exit. | --cert [name], (country), (org) | Create a web server certificate with a server name. Country and organization can optionaly be set. From fcccb18cc8c02f0e7694c9bf046693821d5e7599 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 10 Oct 2019 14:06:14 -0700 Subject: [PATCH 13/14] Probably fixed the MongoDB dot exception. --- db.js | 36 ++++++++++++++++++------------------ meshagent.js | 6 +++--- meshuser.js | 14 +++++++------- package.json | 2 +- webserver.js | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/db.js b/db.js index c15627b4..033df508 100644 --- a/db.js +++ b/db.js @@ -608,11 +608,11 @@ module.exports.CreateDB = function (parent, func) { } // Check the object names for a "." - function checkObjectNames(r) { + function checkObjectNames(r, tag) { if (typeof r != 'object') return; for (var i in r) { - if (i.indexOf('.') >= 0) { throw('BadDbName: ' + JSON.stringify(r)); } - checkObjectNames(r[i]); + if (i.indexOf('.') >= 0) { throw('BadDbName (' + tag + '): ' + JSON.stringify(r)); } + checkObjectNames(r[i], tag); } } @@ -620,7 +620,7 @@ module.exports.CreateDB = function (parent, func) { if (obj.databaseType == 3) { // Database actions on the main collection (MongoDB) obj.Set = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x1'); // DEBUG CHECKING obj.file.replaceOne({ _id: data._id }, performTypedRecordEncrypt(data), { upsert: true }, func); }; obj.Get = function (id, func) { @@ -651,21 +651,21 @@ module.exports.CreateDB = function (parent, func) { obj.RemoveAll = function (func) { obj.file.deleteMany({}, { multi: true }, func); }; obj.RemoveAllOfType = function (type, func) { obj.file.deleteMany({ type: type }, { multi: true }, func); }; obj.InsertMany = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x2'); // DEBUG CHECKING obj.file.insertMany(data, func); }; obj.RemoveMeshDocuments = function (id) { obj.file.deleteMany({ meshid: id }, { multi: true }); obj.file.deleteOne({ _id: 'nt' + id }); }; obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { - checkObjectNames(docs[0]); // DEBUG CHECKING + checkObjectNames(docs[0], 'x3'); // DEBUG CHECKING docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); }; obj.DeleteDomain = function (domain, func) { obj.file.deleteMany({ domain: domain }, { multi: true }, func); }; obj.SetUser = function (user) { - checkObjectNames(user); // DEBUG CHECKING + checkObjectNames(user, 'x4'); // DEBUG CHECKING var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); }; @@ -681,7 +681,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the events collection obj.GetAllEvents = function (func) { obj.eventsfile.find({}).toArray(func); }; obj.StoreEvent = function (event) { - checkObjectNames(event); // DEBUG CHECKING + checkObjectNames(event, 'x5'); // DEBUG CHECKING obj.eventsfile.insertOne(event); }; obj.GetEvents = function (ids, domain, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); }; @@ -696,7 +696,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); }; obj.storePowerEvent = function (event, multiServer, func) { - checkObjectNames(event); // DEBUG CHECKING + checkObjectNames(event, 'x6'); // DEBUG CHECKING if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insertOne(event, func); }; obj.getPowerTimeline = function (nodeid, func) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }).project({ _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).toArray(func); }; @@ -705,7 +705,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the SMBIOS collection obj.SetSMBIOS = function (smbios, func) { - checkObjectNames(smbios); // DEBUG CHECKING + checkObjectNames(smbios, 'x7'); // DEBUG CHECKING obj.smbiosfile.updateOne({ _id: smbios._id }, { $set: smbios }, { upsert: true }, func); }; obj.RemoveSMBIOS = function (id) { obj.smbiosfile.deleteOne({ _id: id }); }; @@ -713,7 +713,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the Server Stats collection obj.SetServerStats = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x8'); // DEBUG CHECKING obj.serverstatsfile.insertOne(data, func); }; obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }).toArray(func); }; @@ -743,7 +743,7 @@ module.exports.CreateDB = function (parent, func) { } else { // Database actions on the main collection (NeDB and MongoJS) obj.Set = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x9'); // DEBUG CHECKING var xdata = performTypedRecordEncrypt(data); obj.file.update({ _id: xdata._id }, xdata, { upsert: true }, func); }; @@ -775,21 +775,21 @@ module.exports.CreateDB = function (parent, func) { obj.RemoveAll = function (func) { obj.file.remove({}, { multi: true }, func); }; obj.RemoveAllOfType = function (type, func) { obj.file.remove({ type: type }, { multi: true }, func); }; obj.InsertMany = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x10'); // DEBUG CHECKING obj.file.insert(data, func); }; obj.RemoveMeshDocuments = function (id) { obj.file.remove({ meshid: id }, { multi: true }); obj.file.remove({ _id: 'nt' + id }); }; obj.MakeSiteAdmin = function (username, domain) { obj.Get('user/' + domain + '/' + username, function (err, docs) { if (docs.length == 1) { - checkObjectNames(docs[0]); // DEBUG CHECKING + checkObjectNames(docs[0], 'x11'); // DEBUG CHECKING docs[0].siteadmin = 0xFFFFFFFF; obj.Set(docs[0]); } }); }; obj.DeleteDomain = function (domain, func) { obj.file.remove({ domain: domain }, { multi: true }, func); }; obj.SetUser = function (user) { - checkObjectNames(user); // DEBUG CHECKING + checkObjectNames(user, 'x12'); // DEBUG CHECKING var u = Clone(user); if (u.subscriptions) { delete u.subscriptions; } obj.Set(u); }; obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; @@ -800,7 +800,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the events collection obj.GetAllEvents = function (func) { obj.eventsfile.find({}, func); }; obj.StoreEvent = function (event) { - checkObjectNames(event); // DEBUG CHECKING + checkObjectNames(event, 'x13'); // DEBUG CHECKING obj.eventsfile.insert(event); }; obj.GetEvents = function (ids, domain, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func); } else { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func); } }; @@ -827,7 +827,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}, func); }; obj.storePowerEvent = function (event, multiServer, func) { - checkObjectNames(event); // DEBUG CHECKING + checkObjectNames(event, 'x14'); // DEBUG CHECKING if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); }; obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } }; @@ -841,7 +841,7 @@ module.exports.CreateDB = function (parent, func) { // Database actions on the Server Stats collection obj.SetServerStats = function (data, func) { - checkObjectNames(data); // DEBUG CHECKING + checkObjectNames(data, 'x15'); // DEBUG CHECKING obj.serverstatsfile.insert(data, func); }; obj.GetServerStats = function (hours, func) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); obj.serverstatsfile.find({ time: { $gt: t } }, { _id: 0, cpu: 0 }, func); }; diff --git a/meshagent.js b/meshagent.js index 4593df2d..a2917e94 100644 --- a/meshagent.js +++ b/meshagent.js @@ -526,7 +526,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (adminUser.links == null) adminUser.links = {}; adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF }; db.SetUser(adminUser); - parent.parent.DispatchEvent(['*', obj.dbMeshKey, adminUser._id], obj, { etype: 'mesh', username: adminUser.name, meshid: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id }); + parent.parent.DispatchEvent(['*', obj.dbMeshKey, adminUser._id], obj, { etype: 'mesh', username: adminUser.name, meshid: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', action: 'createmesh', msg: 'Mesh created: ' + obj.meshid, domain: domain.id }); } } else { if ((mesh != null) && (mesh.deleted != null) && (mesh.links)) { @@ -547,7 +547,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { } // Send out an event indicating this mesh was "created" - parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', links: mesh.links, msg: 'Mesh undeleted: ' + mesh._id, domain: domain.id }); + parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', msg: 'Mesh undeleted: ' + mesh._id, domain: domain.id }); // Mark the mesh as active delete mesh.deleted; @@ -617,7 +617,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { adminUser.links[obj.meshid] = { rights: 0xFFFFFFFF }; //adminUser.subscriptions = parent.subscribe(adminUser._id, ws); db.SetUser(user); - parent.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: obj.meshid, name: obj.meshid, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id }); + parent.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: obj.meshid, name: obj.meshid, mtype: 2, desc: '', action: 'createmesh', msg: 'Mesh created: ' + obj.meshid, domain: domain.id }); } } diff --git a/meshuser.js b/meshuser.js index df015534..4089f0f3 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1110,7 +1110,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (mesh.links[deluser._id] != null) { delete mesh.links[deluser._id]; parent.db.Set(common.escapeLinksFieldName(mesh)); } // Notify mesh change change = 'Removed user ' + deluser.name + ' from group ' + mesh.name; - var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }; + var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', msg: change, domain: domain.id }; if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. parent.parent.DispatchEvent(['*', mesh._id, deluser._id, user._id], obj, event); } @@ -1682,7 +1682,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use parent.parent.DispatchEvent(targets, obj, event); // Event the device group creation - var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', links: links, msg: 'Device group created: ' + command.meshname, domain: domain.id }; + var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, action: 'createmesh', msg: 'Device group created: ' + command.meshname, domain: domain.id }; parent.parent.DispatchEvent(['*', meshid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon. try { ws.send(JSON.stringify({ action: 'createmesh', responseid: command.responseid, result: 'ok', meshid: meshid })); } catch (ex) { } @@ -1763,7 +1763,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if ((common.validateInt(command.consent) == true) && (command.consent != mesh.consent)) { if (change != '') change += ' and consent changed'; else change += 'Group "' + mesh.name + '" consent changed'; mesh.consent = command.consent; } if (change != '') { db.Set(common.escapeLinksFieldName(mesh)); - var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, consent: mesh.consent, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }; + var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, consent: mesh.consent, action: 'meshchange', msg: change, domain: domain.id }; if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, event); } @@ -1808,7 +1808,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use db.Set(common.escapeLinksFieldName(mesh)); // Notify mesh change - var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id }; + var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id }; if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, event); removedCount++; @@ -1870,9 +1870,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Notify mesh change var event; if (deluser != null) { - event = { etype: 'mesh', username: user.name, userid: deluser.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + deluser.name + ' from group ' + mesh.name, domain: domain.id }; + event = { etype: 'mesh', username: user.name, userid: deluser.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', msg: 'Removed user ' + deluser.name + ' from group ' + mesh.name, domain: domain.id }; } else { - event = { etype: 'mesh', username: user.name, userid: (deluserid.split('/')[2]), meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + (deluserid.split('/')[2]) + ' from group ' + mesh.name, domain: domain.id }; + event = { etype: 'mesh', username: user.name, userid: (deluserid.split('/')[2]), meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', msg: 'Removed user ' + (deluserid.split('/')[2]) + ' from group ' + mesh.name, domain: domain.id }; } parent.parent.DispatchEvent(['*', mesh._id, user._id, command.userid], obj, event); if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } @@ -1913,7 +1913,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use db.Set(common.escapeLinksFieldName(mesh)); var amtpolicy2 = common.Clone(amtpolicy); delete amtpolicy2.password; - var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, amt: amtpolicy2, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }; + var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, amt: amtpolicy2, action: 'meshchange', msg: change, domain: domain.id }; if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, event); diff --git a/package.json b/package.json index 1731a211..44dd02dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.2-b", + "version": "0.4.2-d", "keywords": [ "Remote Management", "Intel AMT", diff --git a/webserver.js b/webserver.js index 19977b52..3369d8f8 100644 --- a/webserver.js +++ b/webserver.js @@ -1229,7 +1229,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (mesh.links[userid] != null) { delete mesh.links[userid]; obj.db.Set(obj.common.escapeLinksFieldName(mesh)); } // Notify mesh change var change = 'Removed user ' + user.name + ' from group ' + mesh.name; - obj.parent.DispatchEvent(['*', mesh._id, user._id, userid], obj, { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); + obj.parent.DispatchEvent(['*', mesh._id, user._id, userid], obj, { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', msg: change, domain: domain.id }); } } } From 6165723ef124c1952002c5b128c6c8885211dd52 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 10 Oct 2019 16:07:32 -0700 Subject: [PATCH 14/14] Fixed CIRA connections when a device is moved to a different group. --- db.js | 6 ++- meshuser.js | 3 ++ mpsserver.js | 142 +++++++++++++++++++++++++++++---------------------- package.json | 2 +- 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/db.js b/db.js index 033df508..2e882e74 100644 --- a/db.js +++ b/db.js @@ -671,7 +671,8 @@ module.exports.CreateDB = function (parent, func) { }; obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); }; - obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); }; + obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); }; + obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }).toArray(func); }; // TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch. // https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/ @@ -794,7 +795,8 @@ module.exports.CreateDB = function (parent, func) { }; obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; - obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; + obj.getAmtUuidMeshNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; + obj.getAmtUuidNode = function (uuid, func) { obj.file.find({ type: 'node', 'intelamt.uuid': uuid }, func); }; obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max), count); }); } } // Database actions on the events collection diff --git a/meshuser.js b/meshuser.js index 4089f0f3..bef61832 100644 --- a/meshuser.js +++ b/meshuser.js @@ -2017,6 +2017,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // If any MQTT sessions are connected on this server, switch it now. if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.changeDeviceMesh(node._id, command.meshid); } + // If any CIRA sessions are connected on this server, switch it now. + if (parent.parent.mpsserver != null) { parent.parent.mpsserver.changeDeviceMesh(node._id, command.meshid); } + // Add the connection state const state = parent.parent.GetConnectivityState(node._id); if (state) { diff --git a/mpsserver.js b/mpsserver.js index 7135ecfd..dfdeab49 100644 --- a/mpsserver.js +++ b/mpsserver.js @@ -20,7 +20,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { obj.db = db; obj.args = args; obj.certificates = certificates; - obj.ciraConnections = {}; + obj.ciraConnections = {}; // NodeID --> Socket var tlsSessionStore = {}; // Store TLS session information for quick resume. var tlsSessionStoreCount = 0; // Number of cached TLS session information in store. const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead. @@ -272,41 +272,26 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { socket.tag.connectTime = Date.now(); socket.tag.host = ''; - // Fetch the mesh - obj.db.Get(socket.tag.meshid, function (err, meshes) { - if ((meshes != null) && (meshes.length === 1)) { - var mesh = meshes[0]; - obj.db.Get(socket.tag.nodeid, function (err, nodes) { - if ((nodes == null) || (nodes.length !== 1)) { - if (mesh.mtype == 1) { - // Check if we already have too many devices for this domain - if (domain.limits && (typeof domain.limits.maxdevices == 'number')) { - db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) { - if (ismax == true) { - // Too many devices in this domain. - maxDomainDevicesReached++; - console.log('Too many devices on this domain to accept the CIRA connection. meshid: ' + socket.tag.meshid); - socket.end(); - } else { - // We are under the limit, create the new device. - // Node is not in the database, add it. Credentials will be empty until added by the user. - var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0, state: 2 } }; - obj.db.Set(device); - - // Event the new node - addedTlsDeviceCount++; - var device2 = common.Clone(device); - if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this. - var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name; - obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: domainid }); - - // Add the connection to the MPS connection list - obj.ciraConnections[socket.tag.nodeid] = socket; - obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 2, 7); // TODO: Right now report power state as "present" (7) until we can poll. - } - }); - return; + // Fetch the node + obj.db.Get(socket.tag.nodeid, function (err, nodes) { + if ((nodes == null) || (nodes.length !== 1)) { + var mesh = obj.parent.webserver.meshes[socket.tag.meshid]; + if (mesh == null) { + unknownTlsMeshIdCount++; + console.log('ERROR: Intel AMT CIRA connected with unknown groupid: ' + socket.tag.meshid); + socket.end(); + return; + } else if (mesh.mtype == 1) { + // Check if we already have too many devices for this domain + if (domain.limits && (typeof domain.limits.maxdevices == 'number')) { + db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) { + if (ismax == true) { + // Too many devices in this domain. + maxDomainDevicesReached++; + console.log('Too many devices on this domain to accept the CIRA connection. meshid: ' + socket.tag.meshid); + socket.end(); } else { + // We are under the limit, create the new device. // Node is not in the database, add it. Credentials will be empty until added by the user. var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0, state: 2 } }; obj.db.Set(device); @@ -317,30 +302,42 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this. var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name; obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: domainid }); - } - } else { - // New CIRA connection for unknown node, disconnect. - unknownTlsNodeCount++; - console.log('CIRA connection for unknown node with incorrect group type. meshid: ' + socket.tag.meshid); - socket.end(); - return; - } - } else { - // Node is already present - var node = nodes[0]; - if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; } - } - // Add the connection to the MPS connection list - obj.ciraConnections[socket.tag.nodeid] = socket; - obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 2, 7); // TODO: Right now report power state as "present" (7) until we can poll. - }); + // Add the connection to the MPS connection list + obj.ciraConnections[socket.tag.nodeid] = socket; + obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 2, 7); // TODO: Right now report power state as "present" (7) until we can poll. + } + }); + return; + } else { + // Node is not in the database, add it. Credentials will be empty until added by the user. + var device = { type: 'node', mtype: 1, _id: socket.tag.nodeid, meshid: socket.tag.meshid, name: socket.tag.name, host: null, domain: domainid, intelamt: { user: '', pass: '', tls: 0, state: 2 } }; + obj.db.Set(device); + + // Event the new node + addedTlsDeviceCount++; + var device2 = common.Clone(device); + if (device2.intelamt.pass != null) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this. + var change = 'CIRA added device ' + socket.tag.name + ' to mesh ' + mesh.name; + obj.parent.DispatchEvent(['*', socket.tag.meshid], obj, { etype: 'node', action: 'addnode', node: device2, msg: change, domain: domainid }); + } + } else { + // New CIRA connection for unknown node, disconnect. + unknownTlsNodeCount++; + console.log('CIRA connection for unknown node with incorrect group type. meshid: ' + socket.tag.meshid); + socket.end(); + return; + } } else { - unknownTlsMeshIdCount++; - console.log('ERROR: Intel AMT CIRA connected with unknown groupid: ' + socket.tag.meshid); - socket.end(); - return; + // Node is already present + var node = nodes[0]; + socket.tag.meshid = node.meshid; // Correct the MeshID if the node has moved. + if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; } } + + // Add the connection to the MPS connection list + obj.ciraConnections[socket.tag.nodeid] = socket; + obj.parent.SetConnectivityState(socket.tag.meshid, socket.tag.nodeid, socket.tag.connectTime, 2, 7); // TODO: Right now report power state as "present" (7) until we can poll. }); } else { // This node connected without certificate authentication, use password auth @@ -410,7 +407,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (usernameLen != 16) { badUserNameLengthCount++; parent.debug('mps', 'Username length not 16', username, password); SendUserAuthFail(socket); return -1; } var meshIdStart = '/' + username, mesh = null; if (obj.parent.webserver.meshes) { for (var i in obj.parent.webserver.meshes) { if (obj.parent.webserver.meshes[i]._id.replace(/\@/g, 'X').replace(/\$/g, 'X').indexOf(meshIdStart) > 0) { mesh = obj.parent.webserver.meshes[i]; break; } } } - if (mesh == null) { meshNotFoundCount++; parent.debug('mps', 'Mesh not found', username, password); SendUserAuthFail(socket); return -1; } + if (mesh == null) { meshNotFoundCount++; parent.debug('mps', 'Device group not found', username, password); SendUserAuthFail(socket); return -1; } // If this is a agent-less mesh, use the device guid 3 times as ID. if (mesh.mtype == 1) { @@ -470,6 +467,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { } else { // Node is already present var node = nodes[0]; + socket.tag.meshid = node.meshid; // Correct the MeshID if the node has moved. if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; } } @@ -480,8 +478,8 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { }); } else if (mesh.mtype == 2) { // If this is a agent mesh, search the mesh for this device UUID // Intel AMT GUID (socket.tag.SystemId) will be used to search the node - obj.db.getAmtUuidNode(mesh._id, socket.tag.SystemId, function (err, nodes) { // TODO: May need to optimize this request with indexes - if ((nodes == null) || (nodes.length !== 1)) { + obj.db.getAmtUuidNode(socket.tag.SystemId, function (err, nodes) { // TODO: May need to optimize this request with indexes + if ((nodes == null) || (nodes.length === 0) || (obj.parent.webserver.meshes == null)) { // New CIRA connection for unknown node, disconnect. unknownNodeCount++; console.log('CIRA connection for unknown node. groupid: ' + mesh._id + ', uuid: ' + socket.tag.SystemId); @@ -489,11 +487,27 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { return; } + // Looking at nodes that match this UUID, select one in the same domain and mesh type. + var node = null; + for (var i in nodes) { + if (mesh.domain == nodes[i].domain) { + var nodemesh = obj.parent.webserver.meshes[nodes[i].meshid]; + if ((nodemesh != null) && (nodemesh.mtype == 2)) { node = nodes[i]; } + } + } + + if (node == null) { + // New CIRA connection for unknown node, disconnect. + unknownNodeCount++; + console.log('CIRA connection for unknown node. candidate(s): ' + nodes.length + ', groupid: ' + mesh._id + ', uuid: ' + socket.tag.SystemId); + socket.end(); + return; + } + // Node is present - var node = nodes[0]; if ((node.intelamt != null) && (node.intelamt.state == 2)) { socket.tag.host = node.intelamt.host; } socket.tag.nodeid = node._id; - socket.tag.meshid = mesh._id; + socket.tag.meshid = node.meshid; // Correct the MeshID if the node has moved. socket.tag.connectTime = Date.now(); // Add the connection to the MPS connection list @@ -920,6 +934,12 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { }); } + // Change a node to a new meshid, this is called when a node changes groups. + obj.changeDeviceMesh = function (nodeid, newMeshId) { + var socket = obj.ciraConnections[nodeid]; + if ((socket != null) && (socket.tag != null)) { socket.tag.meshid = newMeshId; } + } + function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); } return obj; diff --git a/package.json b/package.json index 44dd02dd..f044e8a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.2-d", + "version": "0.4.2-e", "keywords": [ "Remote Management", "Intel AMT",