Started work on MeshCtrl, a MeshCentral command line tool.
Ylian Saint-Hilaire committed
Jun 28, 2019 at 18:31 UTC
83327ee3f944ea741fbf44a8048966c441233b4b
3 files changed
+149
-11
meshctrl.js
new
+117
@@ -0,0 +1,117 @@
1
+#!/usr/bin/env node
2
+
3
+var settings = {};
4
+const args = require('minimist')(process.argv.slice(2));
5
+const possibleCommands = ['listusers','listgroups','serverinfo','userinfo'];
6
+//console.log(args);
7
+
8
+if (args['_'].length != 1) {
9
+ console.log("MeshCtrl is a tool used to perform command line actions on a MeshCentral server.");
10
+ console.log("No action specified, use MeshCtrl like this:\r\n\r\n meshctrl [action] [arguments]\r\n");
11
+ console.log("Supported actions:");
12
+ console.log(" ServerInfo - Show server information");
13
+ console.log(" UserInfo - Show user information");
14
+ console.log(" ListUsers - List user accounts");
15
+ console.log(" ListGroups - List device groups");
16
+ console.log("\r\nSupported arguments:");
17
+ console.log(" --json - Show result as JSON");
18
+ return;
19
+} else {
20
+ settings.cmd = args['_'][0].toLowerCase();
21
+ if (possibleCommands.indexOf(settings.cmd) == -1) { console.log("Invalid command. Possible commands are: " + possibleCommands.join(', ') + '.'); return; }
22
+ //console.log(settings.cmd);
23
+
24
+ var ok = false;
25
+ switch (settings.cmd) {
26
+ case 'serverinfo': { ok = true; break; }
27
+ case 'userinfo': { ok = true; break; }
28
+ case 'listusers': { ok = true; break; }
29
+ case 'listgroups': { ok = true; break; }
30
+ }
31
+
32
+ if (ok) serverConnect();
33
+}
34
+
35
+function serverConnect() {
36
+ const WebSocket = require('ws');
37
+
38
+ function onVerifyServer(clientName, certs) { console.log('onVerifyServer', clientName); }
39
+ const ws = new WebSocket('wss://localhost/control.ashx', { rejectUnauthorized: false, checkServerIdentity: onVerifyServer });
40
+ //console.log('Connecting...');
41
+
42
+ ws.on('open', function open() {
43
+ switch (settings.cmd) {
44
+ case 'serverinfo': { break; }
45
+ case 'userinfo': { break; }
46
+ case 'listusers': { ws.send(JSON.stringify({ action: 'users' })); break; }
47
+ case 'listgroups': { ws.send(JSON.stringify({ action: 'meshes' })); break; }
48
+ }
49
+ });
50
+
51
+ ws.on('close', function close() { process.exit(); });
52
+
53
+ ws.on('message', function incoming(rawdata) {
54
+ var data = null;
55
+ try { data = JSON.parse(rawdata); } catch (ex) { }
56
+ if (data == null) { console.log('Unable to parse data: ' + rawdata); }
57
+ switch (data.action) {
58
+ case 'serverinfo': { // SERVERINFO
59
+ if (settings.cmd == 'serverinfo') {
60
+ if (args.json) {
61
+ console.log(JSON.stringify(data.serverinfo, ' ', 2));
62
+ } else {
63
+ for (var i in data.serverinfo) { console.log(i + ':', data.serverinfo[i]); }
64
+ }
65
+ process.exit();
66
+ }
67
+ break;
68
+ }
69
+ case 'userinfo': { // USERINFO
70
+ if (settings.cmd == 'userinfo') {
71
+ if (args.json) {
72
+ console.log(JSON.stringify(data.userinfo, ' ', 2));
73
+ } else {
74
+ for (var i in data.userinfo) { console.log(i + ':', data.userinfo[i]); }
75
+ }
76
+ process.exit();
77
+ }
78
+ break;
79
+ }
80
+ case 'users': { // LISTUSERS
81
+ console.log('id, name, email\r\n---------------');
82
+ if (args.json) {
83
+ console.log(JSON.stringify(data.users, ' ', 2));
84
+ } else {
85
+ for (var i in data.users) {
86
+ const u = data.users[i];
87
+ var t = "\"" + u._id.split('/')[2] + "\", \"" + u.name + "\"";
88
+ if (u.email != null) { t += ", \"" + u.email + "\""; }
89
+ console.log(t);
90
+ }
91
+ }
92
+ process.exit();
93
+ break;
94
+ }
95
+ case 'meshes': { // LISTGROUPS
96
+ console.log('id, name\r\n---------------');
97
+ if (args.json) {
98
+ console.log(JSON.stringify(data.meshes, ' ', 2));
99
+ } else {
100
+ for (var i in data.meshes) {
101
+ const m = data.meshes[i];
102
+ var t = "\"" + m._id.split('/')[2] + "\", \"" + m.name + "\"";
103
+ console.log(t);
104
+ }
105
+ }
106
+ process.exit();
107
+ break;
108
+ }
109
+ default: {
110
+ console.log('Unknown action: ' + data.action);
111
+ break;
112
+ }
113
+ }
114
+ //console.log('Data', data);
115
+ //setTimeout(function timeout() { ws.send(Date.now()); }, 500);
116
+ });
117
+}
\ No newline at end of file
views/default.handlebars
-1
@@ -1860,7 +1860,6 @@
1860
node.namel = node.name.toLowerCase();
1861
if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
1862
if (message.event.node.icon) { node.icon = message.event.node.icon; }
1863
- console.log(node);
1863
1864
// Web page update
1865
masterUpdate(2 | 4 | 8 | 16);
webserver.js
+32
-10
@@ -2190,12 +2190,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2190
2191
// Process the command
2192
switch (cmd.action) {
2193
- case 'amtdiscover': {
2194
- console.log(cmd);
2195
- ws.send(JSON.stringify({ action: 'amtdiscover' }));
2196
- ws.close();
2197
- return;
2198
- }
2193
case 'ccmactivate':
2194
case 'acmactivate': {
2195
// Check the command
@@ -2206,6 +2200,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2200
if (typeof cmd.fqdn != 'string') { ws.send(JSON.stringify({ errorText: 'Invalid FQDN' })); ws.close(); return; }
2201
if ((obj.common.validateString(cmd.ver, 5, 16) == false) || (cmd.ver.split('.').length != 3)) { ws.send(JSON.stringify({ errorText: 'Invalid Intel AMT version' })); ws.close(); return; }
2202
if (obj.common.validateArray(cmd.modes, 1, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid activation modes' })); ws.close(); return; }
2203
+ if (obj.common.validateInt(cmd.currentMode, 0, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid current mode' })); ws.close(); return; }
2204
2205
// Get the current Intel AMT policy
2206
var mesh = obj.meshes[ws.meshid], activationMode = 4; // activationMode: 2 = CCM, 4 = ACM
@@ -2266,8 +2261,26 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2261
parent.certificateOperations.logAmtActivation(domain, { time: new Date(), action: cmd.action, domain: domain.id, amtUuid: cmd.uuid, ipport: ws.remoteaddrport, meshid: ws.meshid });
2262
break;
2263
}
2264
+ case 'amtdiscover':
2265
case 'ccmactivate-success':
2266
case 'acmactivate-success': {
2267
+ // If this is a discovery command, set the state.
2268
+ if (cmd.action == 'amtdiscover') {
2269
+ if (cmd.version != 1) { ws.send(JSON.stringify({ errorText: 'Unsupported version' })); ws.close(); return; }
2270
+ if (obj.common.validateString(cmd.realm, 16, 256) == false) { ws.send(JSON.stringify({ errorText: 'Invalid realm argument' })); ws.close(); return; }
2271
+ if (obj.common.validateString(cmd.uuid, 36, 36) == false) { ws.send(JSON.stringify({ errorText: 'Invalid UUID argument' })); ws.close(); return; }
2272
+ if (typeof cmd.hashes != 'object') { ws.send(JSON.stringify({ errorText: 'Invalid hashes' })); ws.close(); return; }
2273
+ if (typeof cmd.fqdn != 'string') { ws.send(JSON.stringify({ errorText: 'Invalid FQDN' })); ws.close(); return; }
2274
+ if ((obj.common.validateString(cmd.ver, 5, 16) == false) || (cmd.ver.split('.').length != 3)) { ws.send(JSON.stringify({ errorText: 'Invalid Intel AMT version' })); ws.close(); return; }
2275
+ if (obj.common.validateArray(cmd.modes, 1, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid activation modes' })); ws.close(); return; }
2276
+ if (obj.common.validateInt(cmd.currentMode, 0, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid current mode' })); ws.close(); return; }
2277
+ var activationMode = 0; if (cmd.currentMode == 1) { activationMode = 2; } else if (cmd.currentMode == 2) { activationMode = 4; }
2278
+ ws.xxstate = { uuid: cmd.uuid, realm: cmd.realm, tag: cmd.tag, name: cmd.name, flags: activationMode, ver: cmd.ver }; // Flags: 2 = CCM, 4 = ACM
2279
+ } else {
2280
+ // If this is an activation success, check that state was set already.
2281
+ if (ws.xxstate == null) { ws.send(JSON.stringify({ errorText: 'Invalid command' })); ws.close(); return; }
2282
+ }
2283
+
2284
// Log the activation response
2285
parent.certificateOperations.logAmtActivation(domain, { time: new Date(), action: cmd.action, domain: domain.id, amtUuid: cmd.uuid, ipport: ws.remoteaddrport, meshid: ws.meshid });
2286
@@ -2284,7 +2297,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2297
parent.crypto.randomBytes(48, function (err, buf) {
2298
// Create the new node
2299
var xxnodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
2287
- var device = { type: 'node', _id: xxnodeid, meshid: ws.meshid, name: ws.xxstate.name, host: ws.remoteaddr, domain: domain.id, intelamt: { state: 2, flags: ws.xxstate.flags, user: 'admin', pass: ws.xxstate.pass, tls: 0, uuid: ws.xxstate.uuid, realm: ws.xxstate.realm, tag: ws.xxstate.tag, ver: ws.xxstate.ver } };
2300
+ var device = { type: 'node', _id: xxnodeid, meshid: ws.meshid, name: ws.xxstate.name, rname: ws.xxstate.name, host: ws.remoteaddr, domain: domain.id, intelamt: { state: 2, flags: ws.xxstate.flags, tls: 0, uuid: ws.xxstate.uuid, realm: ws.xxstate.realm, tag: ws.xxstate.tag, ver: ws.xxstate.ver } };
2301
+ if (ws.xxstate.pass != null) { device.intelamt.user = 'admin'; device.intelamt.pass = ws.xxstate.pass; }
2302
+ if (device.intelamt.flags != 0) { device.intelamt.state = 2; } else { device.intelamt.state = 0; }
2303
db.Set(device);
2304
2305
// Event the new node
@@ -2296,10 +2311,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2311
// Change an existing device
2312
var device = nodes[0];
2313
if (device.host != ws.remoteaddr) { device.host = ws.remoteaddr; }
2299
- if (device.intelamt.state != 2) { device.intelamt.state = 2; }
2314
+ if ((ws.xxstate.name != null) && (device.rname != ws.xxstate.name)) { device.rname = ws.xxstate.name; }
2315
+ if (device.intelamt.flags != 0) {
2316
+ if (device.intelamt.state != 2) { device.intelamt.state = 2; }
2317
+ }
2318
if (device.intelamt.flags != ws.xxstate.flags) { device.intelamt.state = ws.xxstate.flags; }
2301
- if (device.intelamt.user != 'admin') { device.intelamt.user = 'admin'; }
2302
- if (device.intelamt.pass != ws.xxstate.pass) { device.intelamt.pass = ws.xxstate.pass; }
2319
+ if (ws.xxstate.pass != null) {
2320
+ if (device.intelamt.user != 'admin') { device.intelamt.user = 'admin'; }
2321
+ if (device.intelamt.pass != ws.xxstate.pass) { device.intelamt.pass = ws.xxstate.pass; }
2322
+ }
2323
if (device.intelamt.realm != ws.xxstate.realm) { device.intelamt.realm = ws.xxstate.realm; }
2324
if (ws.xxstate.realm == null) { delete device.intelamt.tag; }
2325
else if (device.intelamt.tag != ws.xxstate.tag) { device.intelamt.tag = ws.xxstate.tag; }
@@ -2313,6 +2333,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2333
parent.DispatchEvent(['*', ws.meshid], obj, { etype: 'node', action: 'changenode', nodeid: device2._id, node: device2, msg: 'Changed device ' + device.name + ' in mesh ' + mesh.name, domain: domain.id });
2334
}
2335
});
2336
+
2337
+ if (cmd.action == 'amtdiscover') { ws.send(JSON.stringify({ action: 'amtdiscover' })); }
2338
break;
2339
}
2340
default: {