From f6bedce5bf14ffeaf5d4f0363bff218ca3592aa7 Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Sat, 5 Oct 2019 02:50:32 -0400 Subject: [PATCH 1/6] 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 d143990f313453eb78ba1d985ed494cfb86c39ea Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Tue, 8 Oct 2019 04:18:40 -0400 Subject: [PATCH 2/6] 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 9e9bbfad62310f993023aff0d875b803bdc54f39 Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 03:06:37 -0400 Subject: [PATCH 4/6] 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 4f05936d75da698ac7e1d27ce1f77d829021fde6 Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 03:22:35 -0400 Subject: [PATCH 5/6] 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 a7728f3c2fc24dd3e69831441a685989d0fd9fa7 Mon Sep 17 00:00:00 2001 From: Ryan Blenis Date: Wed, 9 Oct 2019 04:54:37 -0400 Subject: [PATCH 6/6] 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; }