More plugin hooks / development of base

Ryan Blenis committed Oct 8, 2019 at 04:18 UTC be87880dc5d4d82ffe6f1e75a1f8d7386a19f12d
6 files changed +200 -17
agents/meshcore.min.js
+20 -13
@@ -757,17 +757,6 @@ function createMeshCore(agent)
757 }
758 break;
759 }
760 - case 'plugin': {
761 - if (typeof data.pluginaction == 'string') {
762 - try {
763 - MeshServerLog('Plugin called', data);
764 - require(data.plugin.name).serveraction(data);
765 - } catch(e) {
766 - MeshServerLog('Error calling plugin', data);
767 - }
768 - }
769 - break;
770 - }
760 default:
761 // Unknown action, ignore it.
762 break;
@@ -841,6 +830,19 @@ function createMeshCore(agent)
830 }
831 case 'ping': { mesh.SendCommand('{"action":"pong"}'); break; }
832 case 'pong': { break; }
833 + case 'plugin': {
834 + if (typeof data.pluginaction == 'string') {
835 + try {
836 +
837 + MeshServerLog('Plugin called', data);
838 + /* Not yet implmented
839 + require(data.plugin.name).serveraction(data);*/
840 + } catch(e) {
841 + MeshServerLog('Error calling plugin', data);
842 + }
843 + }
844 + break;
845 + }
846 default:
847 // Unknown action, ignore it.
848 break;
@@ -2353,9 +2355,12 @@ function createMeshCore(agent)
2355 case 'plugin': {
2356 if (typeof args['_'][0] == 'string') {
2357 try {
2356 - response = require(args['_'][0]).consoleaction(args, rights, sessionid);
2358 + // pass off the action to the plugin
2359 + // for plugin creators, you'll want to have a plugindir/modules_meshcore/plugin.js
2360 + // to control the output / actions here.
2361 + response = require(args['_'][0]).consoleaction(args, rights, sessionid, mesh);
2362 } catch(e) {
2358 - response = 'There was an error in the plugin';
2363 + response = 'There was an error in the plugin (' + e + ')';
2364 }
2365 } else {
2366 response = 'Proper usage: plugin [pluginName] [args].';
@@ -2475,6 +2480,8 @@ function createMeshCore(agent)
2480 //if (process.platform == 'win32') { try { pr = require('win-info').pendingReboot(); } catch (ex) { pr = null; } } // Pending reboot
2481 if ((meshCoreObj.av == null) || (JSON.stringify(meshCoreObj.av) != JSON.stringify(av))) { meshCoreObj.av = av; mesh.SendCommand(meshCoreObj); }
2482 }
2483 +
2484 + // TODO: add plugin hook here
2485 }
2486
2487
meshagent.js
+12
@@ -1345,6 +1345,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1345 });
1346 break;
1347 }
1348 + case 'plugin': {
1349 + if (typeof command.plugin == 'string') {
1350 + try {
1351 + var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent);
1352 + pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
1353 + } catch (e) {
1354 +
1355 + console.log('Error loading plugin handler ('+ e + ')');
1356 + }
1357 + }
1358 + break;
1359 + }
1360 default: {
1361 parent.agentStats.unknownAgentActionCount++;
1362 console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + command.action + '.');
meshcentral.js
+7 -1
@@ -865,6 +865,11 @@ function CreateMeshCentralServer(config, args) {
865 // Dispatch an event that the server is now running
866 obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
867
868 + obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj);
869 +
870 + // Plugin hook. Need to run something at server startup? This is the place.
871 + obj.pluginHandler.callHook("server_startup");
872 +
873 // Load the login cookie encryption key from the database if allowed
874 if ((obj.config) && (obj.config.settings) && (obj.config.settings.allowlogintoken == true)) {
875 obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
@@ -1347,7 +1352,8 @@ function CreateMeshCentralServer(config, args) {
1352 }
1353 }
1354 }
1350 -
1355 + obj.pluginHandler = require("./pluginHandler.js").pluginHandler(obj);
1356 + obj.pluginHandler.addMeshCoreModules(modulesAdd);
1357 // Merge the cores and compute the hashes
1358 for (var i in modulesAdd) {
1359 if ((i == 'windows-recovery') || (i == 'linux-recovery')) {
pluginHandler.js new
+137
@@ -0,0 +1,137 @@
1 +/**
2 +* @description MeshCentral plugin module
3 +* @author Ryan Blenis
4 +* @copyright
5 +* @license Apache-2.0
6 +* @version v0.0.1
7 +*/
8 +
9 +/*xjslint node: true */
10 +/*xjslint plusplus: true */
11 +/*xjslint maxlen: 256 */
12 +/*jshint node: true */
13 +/*jshint strict: false */
14 +/*jshint esversion: 6 */
15 +"use strict";
16 +
17 +module.exports.pluginHandler = function (parent) {
18 + var obj = {};
19 +
20 + obj.fs = require('fs');
21 + obj.path = require('path');
22 + obj.parent = parent;
23 + obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins');
24 + obj.enabled = obj.parent.config.settings.plugins.enabled;
25 + obj.loadList = obj.parent.config.settings.plugins.list;
26 + obj.plugins = {};
27 + obj.exports = {};
28 +
29 + if (obj.enabled) {
30 + obj.loadList.forEach(function(plugin, index) {
31 + if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) {
32 + try {
33 + obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj);
34 + obj.exports[plugin] = obj.plugins[plugin].exports;
35 + } catch (e) {
36 + console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled");
37 + }
38 + }
39 + });
40 + }
41 +
42 + obj.prepExports = function() {
43 + var str = 'function() {\r\n';
44 + str += ' var obj = {};\r\n';
45 +
46 + for (const p of Object.keys(obj.plugins)) {
47 + str += ' obj.'+ p +' = {};\r\n';
48 + for (const l of Object.values(obj.exports[p])) {
49 + str += ' obj.'+ p +'.'+ l + ' = '+ obj.plugins[p][l].toString()+'\r\n';
50 + }
51 + }
52 + str += 'return obj; };\r\n';
53 + return str;
54 + }
55 +
56 + obj.callHook = function(hookName, ...args) {
57 + for (var p in obj.plugins) {
58 + if (typeof obj.plugins[p][hookName] == 'function') {
59 + try {
60 + obj.plugins[p][hookName](args);
61 + } catch (e) {
62 + console.log('Error ocurred while running plugin hook' + p + ':' + hookName + ' (' + e + ')');
63 + }
64 + }
65 + }
66 + };
67 +
68 + obj.addMeshCoreModules = function(modulesAdd) {
69 + if (obj.enabled !== true) return;
70 + for (var plugin in obj.plugins) {
71 + var moduleDirPath = null;
72 + var modulesDir = null;
73 + //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.
74 + if (modulesDir == null) { try { moduleDirPath = obj.path.join(obj.pluginPath, plugin + '/modules_meshcore'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Use non-minified mofules.
75 + if (modulesDir != null) {
76 + for (var i in modulesDir) {
77 + if (modulesDir[i].toLowerCase().endsWith('.js')) {
78 + var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3);
79 + if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files.
80 + 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' ];
81 +
82 + // Merge this module
83 + // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms.
84 + if (moduleName.startsWith('amt-') || (moduleName == 'smbios')) {
85 + // Add to IA / Intel AMT cores only
86 + modulesAdd['windows-amt'].push(...moduleData);
87 + modulesAdd['linux-amt'].push(...moduleData);
88 + } else if (moduleName.startsWith('win-')) {
89 + // Add to Windows cores only
90 + modulesAdd['windows-amt'].push(...moduleData);
91 + } else if (moduleName.startsWith('linux-')) {
92 + // Add to Linux cores only
93 + modulesAdd['linux-amt'].push(...moduleData);
94 + modulesAdd['linux-noamt'].push(...moduleData);
95 + } else {
96 + // Add to all cores
97 + modulesAdd['windows-amt'].push(...moduleData);
98 + modulesAdd['linux-amt'].push(...moduleData);
99 + modulesAdd['linux-noamt'].push(...moduleData);
100 + }
101 +
102 + // Merge this module to recovery modules if needed
103 + if (modulesAdd['windows-recovery'] != null) {
104 + if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) {
105 + modulesAdd['windows-recovery'].push(...moduleData);
106 + }
107 + }
108 +
109 + // Merge this module to agent recovery modules if needed
110 + if (modulesAdd['windows-agentrecovery'] != null) {
111 + if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal')) {
112 + modulesAdd['windows-agentrecovery'].push(...moduleData);
113 + }
114 + }
115 + }
116 + }
117 + }
118 + }
119 + };
120 +
121 + obj.deviceViewPanel = function() {
122 + var panel = {};
123 + for (var p in obj.plugins) {
124 + if (typeof obj.plugins[p][hookName] == 'function') {
125 + try {
126 + panel[p].header = obj.plugins[p].on_device_header();
127 + panel[p].content = obj.plugins[p].on_device_page();
128 + } catch (e) {
129 + console.log('Error ocurred while getting plugin views ' + p + ':' + ' (' + e + ')');
130 + }
131 + }
132 + }
133 + return panel;
134 + }
135 +
136 + return obj;
137 +};
\ No newline at end of file
views/default.handlebars
+20 -1
@@ -122,6 +122,7 @@
122 <td tabindex=0 id=MainDevInfo class="topbar_td style3x" onclick=go(17,event) onkeypress="if (event.key == 'Enter') go(17)">Details</td>
123 <td tabindex=0 id=MainDevAmt class="topbar_td style3x" onclick=go(14,event) onkeypress="if (event.key == 'Enter') go(14)">Intel&reg; AMT</td>
124 <td tabindex=0 id=MainDevConsole class="topbar_td style3x" onclick=go(15,event) onkeypress="if (event.key == 'Enter') go(15)">Console</td>
125 + <td tabindex=0 id=MainDevPlugins class="topbar_td style3x" onclick=go(19,event) onkeypress="if (event.key == 'Enter') go(19)">Plugins</td>
126 <td class="topbar_td_end style3">&nbsp;</td>
127 </tr>
128 </table>
@@ -841,6 +842,12 @@
842 </div>
843 <div id=p41events style=""></div>
844 </div>
845 + <div id=p19 style="display:none">
846 + <h1>Plugins - <span id=p19deviceName></span></h1>
847 + <div class="p19headers">
848 + </div>
849 + <div id=p19pages style=""></div>
850 + </div>
851 <br id="column_l_bottomgap" />
852 </div>
853 <div id="footer">
@@ -1008,6 +1015,7 @@
1015 var nightMode = (getstore('_nightMode', '0') == '1');
1016 var sessionActivity = Date.now();
1017 var updateSessionTimer = null;
1018 + var pluginHandler = {{{pluginHandler}}};
1019
1020 // Console Message Display Timers
1021 var p11DeskConsoleMsgTimer = null;
@@ -2298,6 +2306,17 @@
2306 QH('p0span', message.msg);
2307 break;
2308 }
2309 + case 'plugin': {
2310 + if (typeof message.plugin == 'string') {
2311 + try {
2312 + var ph = pluginHandler();
2313 + ph[message.plugin][message.method](server, message);
2314 + } catch (e) {
2315 + console.log('Error loading plugin handler ('+ e + ')');
2316 + }
2317 + }
2318 + break;
2319 + }
2320 default:
2321 //console.log('Unknown message.action', message.action);
2322 break;
@@ -9090,7 +9109,7 @@
9109 QV('MeshSubMenuSpan', x >= 20 && x < 30);
9110 QV('UserSubMenuSpan', x >= 30 && x < 40);
9111 QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41);
9093 - 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', 115: 'ServerConsole' };
9112 + 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' };
9113 for (var i in panels) {
9114 QC(panels[i]).remove('style3x');
9115 QC(panels[i]).remove('style3sel');
webserver.js
+4 -2
@@ -1516,12 +1516,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1516
1517 // Clean up the U2F challenge if needed
1518 if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
1519 -
1519 +
1520 + var pluginHandler = require('./pluginHandler.js').pluginHandler(parent);
1521 +
1522 // Fetch the web state
1523 parent.debug('web', 'handleRootRequestEx: success.');
1524 obj.db.Get('ws' + user._id, function (err, states) {
1525 var webstate = (states.length == 1) ? states[0].state : '';
1524 - 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) });
1526 + 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() });
1527 });
1528 } else {
1529 // Send back the login application