Merge branch 'master' into plugin

This commit is contained in:
Ryan Blenis 2019-10-08 04:22:55 -04:00
commit 39f316b463
7 changed files with 65 additions and 35 deletions

View File

@ -2107,6 +2107,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Check if this user has rights to do this // Check if this user has rights to do this
if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 64) != 0)) { if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 64) != 0)) {
// If this device is connected on MQTT, send a wake action.
if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.publish(node._id, 'powerAction', 'wake'); }
// Get the device interface information // Get the device interface information
db.Get('if' + node._id, function (err, nodeifs) { db.Get('if' + node._id, function (err, nodeifs) {
if ((nodeifs != null) && (nodeifs.length == 1)) { if ((nodeifs != null) && (nodeifs.length == 1)) {
@ -2146,6 +2149,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'poweraction': case 'poweraction':
{ {
if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
if (common.validateInt(command.actiontype, 2, 4) == false) break; // Check actiontype
for (i in command.nodeids) { for (i in command.nodeids) {
nodeid = command.nodeids[i]; nodeid = command.nodeids[i];
var powerActions = 0; var powerActions = 0;
@ -2159,6 +2163,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Get the mesh for this device // Get the mesh for this device
mesh = parent.meshes[node.meshid]; mesh = parent.meshes[node.meshid];
if (mesh) { if (mesh) {
// If this device is connected on MQTT, send a power action.
if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.publish(nodeid, 'powerAction', ['', '', 'poweroff', 'reset', 'sleep'][command.actiontype]); }
// Check if this user has rights to do this // Check if this user has rights to do this
if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission" if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission"

View File

@ -12,9 +12,13 @@ module.exports.CreateMQTTBroker = function (parent, db, args) {
obj.parent = parent; obj.parent = parent;
obj.db = db; obj.db = db;
obj.args = args; obj.args = args;
obj.aedes = require("aedes")();
obj.handle = obj.aedes.handle;
obj.connections = {}; // NodesID --> client array obj.connections = {}; // NodesID --> client array
const aedes = require("aedes")();
obj.handle = aedes.handle;
const allowedSubscriptionTopics = [ 'presence' ];
const denyError = new Error('denied');
var authError = new Error('Auth error')
authError.returnCode = 1
// Generate a username and password for MQTT login // Generate a username and password for MQTT login
obj.generateLogin = function (meshid, nodeid) { obj.generateLogin = function (meshid, nodeid) {
@ -26,18 +30,18 @@ module.exports.CreateMQTTBroker = function (parent, db, args) {
} }
// Connection Authentication // Connection Authentication
obj.aedes.authenticate = function (client, username, password, callback) { aedes.authenticate = function (client, username, password, callback) {
obj.parent.debug("mqtt", "Authentication User:" + username + ", Pass:" + password.toString() + ", ClientID:" + client.id + ", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); obj.parent.debug("mqtt", "Authentication User:" + username + ", Pass:" + password.toString() + ", ClientID:" + client.id + ", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip));
// Parse the username and password // Parse the username and password
var usersplit = username.split(':'); var usersplit = username.split(':');
var passsplit = password.toString().split(':'); var passsplit = password.toString().split(':');
if ((usersplit.length !== 4) || (passsplit.length !== 3)) { obj.parent.debug("mqtt", "Invalid user/pass format, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(null, false); return; } if ((usersplit.length !== 4) || (passsplit.length !== 3)) { obj.parent.debug("mqtt", "Invalid user/pass format, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(authError, null); return; }
if (usersplit[0] !== 'MCAuth1') { obj.parent.debug("mqtt", "Invalid auth method, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(null, false); return; } if (usersplit[0] !== 'MCAuth1') { obj.parent.debug("mqtt", "Invalid auth method, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(authError, null); return; }
// Check authentication // Check authentication
if (passsplit[0] !== parent.config.settings.mqtt.auth.keyid) { obj.parent.debug("mqtt", "Invalid auth keyid, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(null, false); return; } if (passsplit[0] !== parent.config.settings.mqtt.auth.keyid) { obj.parent.debug("mqtt", "Invalid auth keyid, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(authError, null); return; }
if (parent.crypto.createHash('sha384').update(username + ':' + passsplit[1] + ':' + parent.config.settings.mqtt.auth.key).digest("base64") !== passsplit[2]) { obj.parent.debug("mqtt", "Invalid password, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(null, false); return; } if (parent.crypto.createHash('sha384').update(username + ':' + passsplit[1] + ':' + parent.config.settings.mqtt.auth.key).digest("base64") !== passsplit[2]) { obj.parent.debug("mqtt", "Invalid password, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); callback(authError, null); return; }
// Setup the identifiers // Setup the identifiers
const xnodeid = usersplit[1]; const xnodeid = usersplit[1];
@ -49,16 +53,15 @@ module.exports.CreateMQTTBroker = function (parent, db, args) {
// Convert meshid from HEX to Base64 if needed // Convert meshid from HEX to Base64 if needed
if (xmeshid.length === 96) { xmeshid = Buffer.from(xmeshid, 'hex').toString('base64'); } if (xmeshid.length === 96) { xmeshid = Buffer.from(xmeshid, 'hex').toString('base64'); }
if ((xmeshid.length !== 64) || (xnodeid.length != 64)) { callback(null, false); return; } if ((xmeshid.length !== 64) || (xnodeid.length != 64)) { callback(authError, null); return; }
// Set the client nodeid and meshid
client.xdbNodeKey = 'node/' + xdomainid + '/' + xnodeid; client.xdbNodeKey = 'node/' + xdomainid + '/' + xnodeid;
client.xdbMeshKey = 'mesh/' + xdomainid + '/' + xmeshid; client.xdbMeshKey = 'mesh/' + xdomainid + '/' + xmeshid;
//console.log(obj.generateLogin(client.xdbMeshKey, client.xdbNodeKey));
// Check if this node exists in the database // Check if this node exists in the database
db.Get(client.xdbNodeKey, function (err, nodes) { db.Get(client.xdbNodeKey, function (err, nodes) {
if ((nodes == null) || (nodes.length != 1)) { callback(null, false); return; } // Node does not exist if ((nodes == null) || (nodes.length != 1)) { callback(authError, null); return; } // Node does not exist
// If this device now has a different meshid, fix it here. // If this device now has a different meshid, fix it here.
client.xdbMeshKey = nodes[0].meshid; client.xdbMeshKey = nodes[0].meshid;
@ -93,27 +96,42 @@ module.exports.CreateMQTTBroker = function (parent, db, args) {
} }
// Check if a client can publish a packet // Check if a client can publish a packet
obj.aedes.authorizePublish = function (client, packet, callback) { aedes.authorizeSubscribe = function (client, sub, callback) {
// TODO: add authorized publish control // Subscription control
//console.log(packet); obj.parent.debug("mqtt", "AuthorizeSubscribe \"" + sub.topic + "\", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip));
obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); if (allowedSubscriptionTopics.indexOf(sub.topic) === -1) { sub = null; } // If not a supported subscription, deny it.
callback(null); callback(null, sub); // We authorize supported topics, but will not allow agents to publish anything to other agents.
} }
// Check if a client can publish a packet // Check if a client can publish a packet
obj.aedes.authorizeSubscribe = function (client, sub, callback) { aedes.authorizePublish = function (client, packet, callback) {
// TODO: add subscription control here // Handle a published message
obj.parent.debug("mqtt", "AuthorizeSubscribe \"" + sub.topic + "\", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip));
callback(null, sub); handleMessage(client.xdbNodeKey, client.xdbNodeKey, packet.topic, packet.payload);
// We don't accept that any client message be published, so don't call the callback.
} }
// Check if a client can forward a packet // Publish a message to a specific nodeid & topic, also send this to peer servers.
obj.aedes.authorizeForward = function (client, packet) { obj.publish = function (nodeid, topic, message) {
// TODO: add forwarding control // Publish this message on peer servers.
//console.log(packet); if (parent.multiServer != null) { parent.multiServer.DispatchMessage(JSON.stringify({ action: 'mqtt', nodeid: nodeid, topic: topic, message: message })); }
obj.parent.debug("mqtt", "AuthorizeForward, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); obj.publishNoPeers(nodeid, topic, message);
//return packet; }
return packet;
// Publish a message to a specific nodeid & topic, don't send to peer servers.
obj.publishNoPeers = function (nodeid, topic, message) {
// Look for any MQTT connections to send this to
var clients = obj.connections[nodeid];
if (clients == null) return;
if (typeof message == 'string') { message = new Buffer(message); }
for (var i in clients) { clients[i].publish({ cmd: 'publish', qos: 0, topic: topic, payload: message, retain: false }); }
}
// Handle messages coming from clients
function handleMessage(nodeid, meshid, topic, message) {
// TODO: Handle messages here.
//console.log('handleMessage', nodeid, topic, message.toString());
//obj.publish(nodeid, 'echoTopic', "Echo: " + message.toString());
} }
// Clean a IPv6 address that encodes a IPv4 address // Clean a IPv6 address that encodes a IPv4 address

View File

@ -456,6 +456,10 @@ module.exports.CreateMultiServer = function (parent, args) {
var userid, i; var userid, i;
//console.log('ProcessPeerServerMessage', peerServerId, msg); //console.log('ProcessPeerServerMessage', peerServerId, msg);
switch (msg.action) { switch (msg.action) {
case 'mqtt': {
if ((obj.parent.mqttbroker != null) && (msg.nodeid != null)) { obj.parent.mqttbroker.publishNoPeers(msg.nodeid, msg.topic, msg.message); } // Dispatch in the MQTT broker
break;
}
case 'bus': { case 'bus': {
obj.parent.DispatchEvent(msg.ids, null, msg.event, true); // Dispatch the peer event obj.parent.DispatchEvent(msg.ids, null, msg.event, true); // Dispatch the peer event
break; break;

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.4.1-v", "version": "0.4.1-w",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

File diff suppressed because one or more lines are too long

View File

@ -3329,7 +3329,7 @@
p10showChangeGroupDialog(getCheckedDevices()); p10showChangeGroupDialog(getCheckedDevices());
} else { } else {
// Power operation // Power operation
meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: op }); meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: parseInt(op) });
} }
} }
@ -4465,7 +4465,7 @@
meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] }); meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] });
} else { } else {
// Power operation // Power operation
meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: op }); meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) });
} }
} }
@ -4875,9 +4875,9 @@
QV('DeskClip', (currentNode.agent) && (currentNode.agent.id != 11) && (currentNode.agent.id != 16) && ((desktop == null) || (desktop.contype != 2))); // Clipboard not supported on MacOS QV('DeskClip', (currentNode.agent) && (currentNode.agent.id != 11) && (currentNode.agent.id != 16) && ((desktop == null) || (desktop.contype != 2))); // Clipboard not supported on MacOS
QE('DeskClip', deskState == 3); QE('DeskClip', deskState == 3);
QE('DeskType', deskState == 3); QE('DeskType', deskState == 3);
QV('DeskWD', (currentNode.agent) && (currentNode.agent.id < 5) && inputAllowed); QV('DeskWD', inputAllowed);
QE('DeskWD', deskState == 3); QE('DeskWD', deskState == 3);
QV('deskkeys', (currentNode.agent) && (currentNode.agent.id < 5) && inputAllowed); QV('deskkeys', inputAllowed);
QE('deskkeys', deskState == 3); QE('deskkeys', deskState == 3);
QV('DeskToolsButton', (inputAllowed) && (mesh.mtype == 2) && online); QV('DeskToolsButton', (inputAllowed) && (mesh.mtype == 2) && online);

View File

@ -447,7 +447,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// If a trusted reverse-proxy is sending us the remote IP address, use it. // If a trusted reverse-proxy is sending us the remote IP address, use it.
// This is not done automatically for web socket like it's done for HTTP requests. // This is not done automatically for web socket like it's done for HTTP requests.
if ((obj.args.tlsoffload) && (res.headers['x-forwarded-for']) && ((obj.args.tlsoffload === true) || (obj.args.tlsoffload === ip) || (('::ffff:') + obj.args.tlsoffload === ip))) { ip = res.headers['x-forwarded-for']; } if ((obj.args.trustedproxy) && (res.headers['x-forwarded-for']) && ((obj.args.trustedproxy === true) || (obj.args.trustedproxy === ip) || (('::ffff:') + obj.args.trustedproxy === ip))) { ip = res.headers['x-forwarded-for']; }
else if ((obj.args.tlsoffload) && (res.headers['x-forwarded-for']) && ((obj.args.tlsoffload === true) || (obj.args.tlsoffload === ip) || (('::ffff:') + obj.args.tlsoffload === ip))) { ip = res.headers['x-forwarded-for']; }
if (ip) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(ip, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } } if (ip) { for (var i = 0; i < ipList.length; i++) { if (require('ipcheck').match(ip, ipList[i])) { if (closeIfThis === true) { try { req.close(); } catch (e) { } } return true; } } }
if (closeIfThis === false) { try { req.close(); } catch (e) { } } if (closeIfThis === false) { try { req.close(); } catch (e) { } }
@ -3193,7 +3194,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Setup middleware // Setup middleware
obj.app.engine('handlebars', obj.exphbs({ defaultLayout: null })); // defaultLayout: 'main' obj.app.engine('handlebars', obj.exphbs({ defaultLayout: null })); // defaultLayout: 'main'
obj.app.set('view engine', 'handlebars'); obj.app.set('view engine', 'handlebars');
if (obj.args.tlsoffload) { obj.app.set('trust proxy', obj.args.tlsoffload); } // Reverse proxy should add the "X-Forwarded-*" headers if (obj.args.trustedproxy) { obj.app.set('trust proxy', obj.args.trustedproxy); } // Reverse proxy should add the "X-Forwarded-*" headers
else if (obj.args.tlsoffload) { obj.app.set('trust proxy', obj.args.tlsoffload); } // Reverse proxy should add the "X-Forwarded-*" headers
obj.app.use(obj.bodyParser.urlencoded({ extended: false })); obj.app.use(obj.bodyParser.urlencoded({ extended: false }));
var sessionOptions = { var sessionOptions = {
name: 'xid', // Recommended security practice to not use the default cookie name name: 'xid', // Recommended security practice to not use the default cookie name