From 83327ee3f944ea741fbf44a8048966c441233b4b Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Fri, 28 Jun 2019 18:31:18 -0700 Subject: [PATCH] Started work on MeshCtrl, a MeshCentral command line tool. --- meshctrl.js | 117 +++++++++++++++++++++++++++++++++++++++ views/default.handlebars | 1 - webserver.js | 42 ++++++++++---- 3 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 meshctrl.js diff --git a/meshctrl.js b/meshctrl.js new file mode 100644 index 00000000..aafb539d --- /dev/null +++ b/meshctrl.js @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +var settings = {}; +const args = require('minimist')(process.argv.slice(2)); +const possibleCommands = ['listusers','listgroups','serverinfo','userinfo']; +//console.log(args); + +if (args['_'].length != 1) { + console.log("MeshCtrl is a tool used to perform command line actions on a MeshCentral server."); + console.log("No action specified, use MeshCtrl like this:\r\n\r\n meshctrl [action] [arguments]\r\n"); + console.log("Supported actions:"); + console.log(" ServerInfo - Show server information"); + console.log(" UserInfo - Show user information"); + console.log(" ListUsers - List user accounts"); + console.log(" ListGroups - List device groups"); + console.log("\r\nSupported arguments:"); + console.log(" --json - Show result as JSON"); + return; +} else { + settings.cmd = args['_'][0].toLowerCase(); + if (possibleCommands.indexOf(settings.cmd) == -1) { console.log("Invalid command. Possible commands are: " + possibleCommands.join(', ') + '.'); return; } + //console.log(settings.cmd); + + var ok = false; + switch (settings.cmd) { + case 'serverinfo': { ok = true; break; } + case 'userinfo': { ok = true; break; } + case 'listusers': { ok = true; break; } + case 'listgroups': { ok = true; break; } + } + + if (ok) serverConnect(); +} + +function serverConnect() { + const WebSocket = require('ws'); + + function onVerifyServer(clientName, certs) { console.log('onVerifyServer', clientName); } + const ws = new WebSocket('wss://localhost/control.ashx', { rejectUnauthorized: false, checkServerIdentity: onVerifyServer }); + //console.log('Connecting...'); + + ws.on('open', function open() { + switch (settings.cmd) { + case 'serverinfo': { break; } + case 'userinfo': { break; } + case 'listusers': { ws.send(JSON.stringify({ action: 'users' })); break; } + case 'listgroups': { ws.send(JSON.stringify({ action: 'meshes' })); break; } + } + }); + + ws.on('close', function close() { process.exit(); }); + + ws.on('message', function incoming(rawdata) { + var data = null; + try { data = JSON.parse(rawdata); } catch (ex) { } + if (data == null) { console.log('Unable to parse data: ' + rawdata); } + switch (data.action) { + case 'serverinfo': { // SERVERINFO + if (settings.cmd == 'serverinfo') { + if (args.json) { + console.log(JSON.stringify(data.serverinfo, ' ', 2)); + } else { + for (var i in data.serverinfo) { console.log(i + ':', data.serverinfo[i]); } + } + process.exit(); + } + break; + } + case 'userinfo': { // USERINFO + if (settings.cmd == 'userinfo') { + if (args.json) { + console.log(JSON.stringify(data.userinfo, ' ', 2)); + } else { + for (var i in data.userinfo) { console.log(i + ':', data.userinfo[i]); } + } + process.exit(); + } + break; + } + case 'users': { // LISTUSERS + console.log('id, name, email\r\n---------------'); + if (args.json) { + console.log(JSON.stringify(data.users, ' ', 2)); + } else { + for (var i in data.users) { + const u = data.users[i]; + var t = "\"" + u._id.split('/')[2] + "\", \"" + u.name + "\""; + if (u.email != null) { t += ", \"" + u.email + "\""; } + console.log(t); + } + } + process.exit(); + break; + } + case 'meshes': { // LISTGROUPS + console.log('id, name\r\n---------------'); + if (args.json) { + console.log(JSON.stringify(data.meshes, ' ', 2)); + } else { + for (var i in data.meshes) { + const m = data.meshes[i]; + var t = "\"" + m._id.split('/')[2] + "\", \"" + m.name + "\""; + console.log(t); + } + } + process.exit(); + break; + } + default: { + console.log('Unknown action: ' + data.action); + break; + } + } + //console.log('Data', data); + //setTimeout(function timeout() { ws.send(Date.now()); }, 500); + }); +} \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 56e7cf69..d1801135 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1860,7 +1860,6 @@ node.namel = node.name.toLowerCase(); if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; } if (message.event.node.icon) { node.icon = message.event.node.icon; } - console.log(node); // Web page update masterUpdate(2 | 4 | 8 | 16); diff --git a/webserver.js b/webserver.js index 1f2ff8be..01370bf1 100644 --- a/webserver.js +++ b/webserver.js @@ -2190,12 +2190,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Process the command switch (cmd.action) { - case 'amtdiscover': { - console.log(cmd); - ws.send(JSON.stringify({ action: 'amtdiscover' })); - ws.close(); - return; - } case 'ccmactivate': case 'acmactivate': { // Check the command @@ -2206,6 +2200,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (typeof cmd.fqdn != 'string') { ws.send(JSON.stringify({ errorText: 'Invalid FQDN' })); ws.close(); return; } 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; } if (obj.common.validateArray(cmd.modes, 1, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid activation modes' })); ws.close(); return; } + if (obj.common.validateInt(cmd.currentMode, 0, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid current mode' })); ws.close(); return; } // Get the current Intel AMT policy 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) { parent.certificateOperations.logAmtActivation(domain, { time: new Date(), action: cmd.action, domain: domain.id, amtUuid: cmd.uuid, ipport: ws.remoteaddrport, meshid: ws.meshid }); break; } + case 'amtdiscover': case 'ccmactivate-success': case 'acmactivate-success': { + // If this is a discovery command, set the state. + if (cmd.action == 'amtdiscover') { + if (cmd.version != 1) { ws.send(JSON.stringify({ errorText: 'Unsupported version' })); ws.close(); return; } + if (obj.common.validateString(cmd.realm, 16, 256) == false) { ws.send(JSON.stringify({ errorText: 'Invalid realm argument' })); ws.close(); return; } + if (obj.common.validateString(cmd.uuid, 36, 36) == false) { ws.send(JSON.stringify({ errorText: 'Invalid UUID argument' })); ws.close(); return; } + if (typeof cmd.hashes != 'object') { ws.send(JSON.stringify({ errorText: 'Invalid hashes' })); ws.close(); return; } + if (typeof cmd.fqdn != 'string') { ws.send(JSON.stringify({ errorText: 'Invalid FQDN' })); ws.close(); return; } + 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; } + if (obj.common.validateArray(cmd.modes, 1, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid activation modes' })); ws.close(); return; } + if (obj.common.validateInt(cmd.currentMode, 0, 2) == false) { ws.send(JSON.stringify({ errorText: 'Invalid current mode' })); ws.close(); return; } + var activationMode = 0; if (cmd.currentMode == 1) { activationMode = 2; } else if (cmd.currentMode == 2) { activationMode = 4; } + ws.xxstate = { uuid: cmd.uuid, realm: cmd.realm, tag: cmd.tag, name: cmd.name, flags: activationMode, ver: cmd.ver }; // Flags: 2 = CCM, 4 = ACM + } else { + // If this is an activation success, check that state was set already. + if (ws.xxstate == null) { ws.send(JSON.stringify({ errorText: 'Invalid command' })); ws.close(); return; } + } + // Log the activation response parent.certificateOperations.logAmtActivation(domain, { time: new Date(), action: cmd.action, domain: domain.id, amtUuid: cmd.uuid, ipport: ws.remoteaddrport, meshid: ws.meshid }); @@ -2284,7 +2297,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.crypto.randomBytes(48, function (err, buf) { // Create the new node var xxnodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); - 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 } }; + 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 } }; + if (ws.xxstate.pass != null) { device.intelamt.user = 'admin'; device.intelamt.pass = ws.xxstate.pass; } + if (device.intelamt.flags != 0) { device.intelamt.state = 2; } else { device.intelamt.state = 0; } db.Set(device); // Event the new node @@ -2296,10 +2311,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Change an existing device var device = nodes[0]; if (device.host != ws.remoteaddr) { device.host = ws.remoteaddr; } - if (device.intelamt.state != 2) { device.intelamt.state = 2; } + if ((ws.xxstate.name != null) && (device.rname != ws.xxstate.name)) { device.rname = ws.xxstate.name; } + if (device.intelamt.flags != 0) { + if (device.intelamt.state != 2) { device.intelamt.state = 2; } + } if (device.intelamt.flags != ws.xxstate.flags) { device.intelamt.state = ws.xxstate.flags; } - if (device.intelamt.user != 'admin') { device.intelamt.user = 'admin'; } - if (device.intelamt.pass != ws.xxstate.pass) { device.intelamt.pass = ws.xxstate.pass; } + if (ws.xxstate.pass != null) { + if (device.intelamt.user != 'admin') { device.intelamt.user = 'admin'; } + if (device.intelamt.pass != ws.xxstate.pass) { device.intelamt.pass = ws.xxstate.pass; } + } if (device.intelamt.realm != ws.xxstate.realm) { device.intelamt.realm = ws.xxstate.realm; } if (ws.xxstate.realm == null) { delete device.intelamt.tag; } 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) { 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 }); } }); + + if (cmd.action == 'amtdiscover') { ws.send(JSON.stringify({ action: 'amtdiscover' })); } break; } default: {