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® 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"> </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