From 08bf681afac52067fe35bd6d05b937f614c3d15c Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 7 Oct 2019 11:38:01 -0700 Subject: [PATCH 1/3] Added trustedproxy setting, fixed send keys in remote desktop. --- package.json | 2 +- views/default-min.handlebars | 2 +- views/default.handlebars | 4 ++-- webserver.js | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 62c4ac79..73b0fbf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.1-v", + "version": "0.4.1-w", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 59fc8798..5f923d2a 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 096cafee..7aa8e071 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -4856,9 +4856,9 @@ 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('DeskType', deskState == 3); - QV('DeskWD', (currentNode.agent) && (currentNode.agent.id < 5) && inputAllowed); + QV('DeskWD', inputAllowed); QE('DeskWD', deskState == 3); - QV('deskkeys', (currentNode.agent) && (currentNode.agent.id < 5) && inputAllowed); + QV('deskkeys', inputAllowed); QE('deskkeys', deskState == 3); QV('DeskToolsButton', (inputAllowed) && (mesh.mtype == 2) && online); diff --git a/webserver.js b/webserver.js index 8de985a5..93634d15 100644 --- a/webserver.js +++ b/webserver.js @@ -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. // 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 (closeIfThis === false) { try { req.close(); } catch (e) { } } @@ -3191,7 +3192,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Setup middleware obj.app.engine('handlebars', obj.exphbs({ defaultLayout: null })); // defaultLayout: 'main' 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 })); var sessionOptions = { name: 'xid', // Recommended security practice to not use the default cookie name From 2f70837ae6a70d246e2bab12bc7e86d65258652a Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 7 Oct 2019 15:00:36 -0700 Subject: [PATCH 2/3] More MQTT improvements. --- mqttbroker.js | 66 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/mqttbroker.js b/mqttbroker.js index 238a6a74..16650d0d 100644 --- a/mqttbroker.js +++ b/mqttbroker.js @@ -12,9 +12,13 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { obj.parent = parent; obj.db = db; obj.args = args; - obj.aedes = require("aedes")(); - obj.handle = obj.aedes.handle; 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 obj.generateLogin = function (meshid, nodeid) { @@ -25,19 +29,28 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { return { meshid: meshid, nodeid: nodeid, user: username, pass: parent.config.settings.mqtt.auth.keyid + ':' + nonce + ':' + parent.crypto.createHash('sha384').update(username + ':' + nonce + ':' + parent.config.settings.mqtt.auth.key).digest("base64") }; } + // Publish a message to a specific nodeid & topic + obj.publish = function (nodeid, topic, message) { + 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 }); } + } + // 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)); + console.log('MQTT Connect'); // Parse the username and password var usersplit = username.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[0] !== 'MCAuth1') { obj.parent.debug("mqtt", "Invalid auth method, " + 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(authError, null); return; } // 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 (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 (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(authError, null); return; } // Setup the identifiers const xnodeid = usersplit[1]; @@ -49,16 +62,15 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Convert meshid from HEX to Base64 if needed 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.xdbMeshKey = 'mesh/' + xdomainid + '/' + xmeshid; - //console.log(obj.generateLogin(client.xdbMeshKey, client.xdbNodeKey)); - // Check if this node exists in the database 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. client.xdbMeshKey = nodes[0].meshid; @@ -93,27 +105,33 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { } // Check if a client can publish a packet - obj.aedes.authorizePublish = function (client, packet, callback) { - // TODO: add authorized publish control - //console.log(packet); - obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); - callback(null); + aedes.authorizeSubscribe = function (client, sub, callback) { + // Subscription control + obj.parent.debug("mqtt", "AuthorizeSubscribe \"" + sub.topic + "\", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); + if (allowedSubscriptionTopics.indexOf(sub.topic) === -1) { sub = null; } // If not a supported subscription, deny it. + 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 - obj.aedes.authorizeSubscribe = function (client, sub, callback) { - // TODO: add subscription control here - obj.parent.debug("mqtt", "AuthorizeSubscribe \"" + sub.topic + "\", " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); - callback(null, sub); + aedes.authorizePublish = function (client, packet, callback) { + // Handle a published message + obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); + handleMessage(client.xdbNodeKey, client.xdbNodeKey, packet.topic, packet.payload); + //callback(denyError); // Deny all, clients can't publish anything to other agents. + //callback(null); // Deny all, clients can't publish anything to other agents. } // Check if a client can forward a packet - obj.aedes.authorizeForward = function (client, packet) { + //aedes.authorizeForward = function (client, packet) { // TODO: add forwarding control - //console.log(packet); - obj.parent.debug("mqtt", "AuthorizeForward, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); + //obj.parent.debug("mqtt", "AuthorizeForward, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); //return packet; - return packet; + //} + + // Handle messages coming from clients + function handleMessage(nodeid, meshid, topic, message) { + console.log('handleMessage', nodeid, topic, message.toString()); + obj.publish(nodeid, 'abc', "This is a server reply"); } // Clean a IPv6 address that encodes a IPv4 address From d5f7943684584cd26ad52f6fc1ec98f3f7c226e7 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 7 Oct 2019 18:11:15 -0700 Subject: [PATCH 3/3] More MQTT improvements. --- meshuser.js | 6 ++++++ mqttbroker.js | 38 +++++++++++++++++++------------------- multiserver.js | 4 ++++ views/default.handlebars | 4 ++-- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/meshuser.js b/meshuser.js index bbe1de43..47f225f2 100644 --- a/meshuser.js +++ b/meshuser.js @@ -2107,6 +2107,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Check if this user has rights to do this 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 db.Get('if' + node._id, function (err, nodeifs) { if ((nodeifs != null) && (nodeifs.length == 1)) { @@ -2146,6 +2149,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'poweraction': { 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) { nodeid = command.nodeids[i]; var powerActions = 0; @@ -2159,6 +2163,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Get the mesh for this device mesh = parent.meshes[node.meshid]; 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 if (mesh.links[user._id] != null && ((mesh.links[user._id].rights & 8) != 0)) { // "Remote Control permission" diff --git a/mqttbroker.js b/mqttbroker.js index 16650d0d..2edd6ce8 100644 --- a/mqttbroker.js +++ b/mqttbroker.js @@ -29,18 +29,9 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { return { meshid: meshid, nodeid: nodeid, user: username, pass: parent.config.settings.mqtt.auth.keyid + ':' + nonce + ':' + parent.crypto.createHash('sha384').update(username + ':' + nonce + ':' + parent.config.settings.mqtt.auth.key).digest("base64") }; } - // Publish a message to a specific nodeid & topic - obj.publish = function (nodeid, topic, message) { - 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 }); } - } - // Connection Authentication 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)); - console.log('MQTT Connect'); // Parse the username and password var usersplit = username.split(':'); @@ -117,21 +108,30 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Handle a published message obj.parent.debug("mqtt", "AuthorizePublish, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); handleMessage(client.xdbNodeKey, client.xdbNodeKey, packet.topic, packet.payload); - //callback(denyError); // Deny all, clients can't publish anything to other agents. - //callback(null); // Deny all, clients can't publish anything to other agents. + // We don't accept that any client message be published, so don't call the callback. } - // Check if a client can forward a packet - //aedes.authorizeForward = function (client, packet) { - // TODO: add forwarding control - //obj.parent.debug("mqtt", "AuthorizeForward, " + client.conn.xtransport + "://" + cleanRemoteAddr(client.conn.xip)); - //return packet; - //} + // Publish a message to a specific nodeid & topic, also send this to peer servers. + obj.publish = function (nodeid, topic, message) { + // Publish this message on peer servers. + if (parent.multiServer != null) { parent.multiServer.DispatchMessage(JSON.stringify({ action: 'mqtt', nodeid: nodeid, topic: topic, message: message })); } + obj.publishNoPeers(nodeid, topic, message); + } + + // 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) { - console.log('handleMessage', nodeid, topic, message.toString()); - obj.publish(nodeid, 'abc', "This is a server reply"); + // 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 diff --git a/multiserver.js b/multiserver.js index 9d398bd9..9a939984 100644 --- a/multiserver.js +++ b/multiserver.js @@ -456,6 +456,10 @@ module.exports.CreateMultiServer = function (parent, args) { var userid, i; //console.log('ProcessPeerServerMessage', peerServerId, msg); 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': { obj.parent.DispatchEvent(msg.ids, null, msg.event, true); // Dispatch the peer event break; diff --git a/views/default.handlebars b/views/default.handlebars index 7aa8e071..f564d488 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -3310,7 +3310,7 @@ p10showChangeGroupDialog(getCheckedDevices()); } else { // Power operation - meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: op }); + meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: parseInt(op) }); } } @@ -4446,7 +4446,7 @@ meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] }); } else { // Power operation - meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: op }); + meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) }); } }