From 00e6ff0e91405501883d00417fdd11dd6dbc1012 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 8 Oct 2019 11:08:41 -0700 Subject: [PATCH] More MQTT improvements. --- meshuser.js | 40 +++++++++++++++++- mqttbroker.js | 80 +++++++++++++++++++++++++++++++++++- package.json | 2 +- views/default-min.handlebars | 2 +- views/default.handlebars | 78 ++++++++++++++++++++++++++++------- 5 files changed, 183 insertions(+), 19 deletions(-) diff --git a/meshuser.js b/meshuser.js index 47f225f2..f7e35860 100644 --- a/meshuser.js +++ b/meshuser.js @@ -2882,6 +2882,45 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } break; } + case 'sendmqttmsg': { + if (common.validateArray(command.nodeids, 1) == false) { err = 'Invalid nodeids'; }; // Check nodeid's + if (common.validateString(command.topic, 1, 64) == false) { err = 'Invalid topic'; } // Check the topic + if (common.validateString(command.msg, 1, 4096) == false) { err = 'Invalid msg'; } // Check the message + + // Handle any errors + if (err != null) { + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'sendmqttmsg', responseid: command.responseid, result: err })); } catch (ex) { } } + break; + } + + // TODO: We can optimize this a lot. + // - We should get a full list of all MAC's to wake first. + // - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan. + for (i in command.nodeids) { + nodeid = command.nodeids[i]; + var wakeActions = 0; + if (common.validateString(nodeid, 1, 1024) == false) break; // Check nodeid + if ((nodeid.split('/').length == 3) && (nodeid.split('/')[1] == domain.id)) { // Validate the domain, operation only valid for current domain + // Get the device + db.Get(nodeid, function (err, nodes) { + if ((nodes == null) || (nodes.length != 1)) return; + var node = nodes[0]; + + // Get the mesh for this device + mesh = parent.meshes[node.meshid]; + if (mesh) { + // 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, command.topic, command.msg); } + } + } + }); + } + } + + break; + } case 'getmqttlogin': { var err = null; if (parent.parent.mqttbroker == null) { err = 'MQTT not supported on this server'; } @@ -2927,7 +2966,6 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } }); } - break; } case 'amt': { diff --git a/mqttbroker.js b/mqttbroker.js index 2edd6ce8..2009dec2 100644 --- a/mqttbroker.js +++ b/mqttbroker.js @@ -107,7 +107,7 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { 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); + handleMessage(client.xdbNodeKey, client.xdbMeshKey, packet.topic, packet.payload); // We don't accept that any client message be published, so don't call the callback. } @@ -129,7 +129,9 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Handle messages coming from clients function handleMessage(nodeid, meshid, topic, message) { - // TODO: Handle messages here. + // Handle messages here + if (topic == 'console') { routeMessage({ action: 'msg', type: 'console', value: message.toString(), source: 'MQTT' }, nodeid, meshid); return; } // Handle console messages + //console.log('handleMessage', nodeid, topic, message.toString()); //obj.publish(nodeid, 'echoTopic', "Echo: " + message.toString()); } @@ -137,5 +139,79 @@ module.exports.CreateMQTTBroker = function (parent, db, args) { // Clean a IPv6 address that encodes a IPv4 address function cleanRemoteAddr(addr) { if (typeof addr != 'string') { return null; } if (addr.indexOf('::ffff:') == 0) { return addr.substring(7); } else { return addr; } } + // Route a message + function routeMessage(command, dbNodeKey, dbMeshKey) { + // Route a message. + // If this command has a sessionid, that is the target. + if (command.sessionid != null) { + if (typeof command.sessionid != 'string') return; + var splitsessionid = command.sessionid.split('/'); + // Check that we are in the same domain and the user has rights over this node. + if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) { + // Check if this user has rights to get this message + //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! + + // See if the session is connected. If so, go ahead and send this message to the target node + var ws = parent.webserver.wssessions2[command.sessionid]; + if (ws != null) { + command.nodeid = dbNodeKey; // Set the nodeid, required for responses. + delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed. + try { ws.send(JSON.stringify(command)); } catch (ex) { } + } else if (parent.multiServer != null) { + // See if we can send this to a peer server + var serverid = parent.webserver.wsPeerSessions2[command.sessionid]; + if (serverid != null) { + command.fromNodeid = dbNodeKey; + parent.multiServer.DispatchMessageSingleServer(command, serverid); + } + } + } + } else if (command.userid != null) { // If this command has a userid, that is the target. + if (typeof command.userid != 'string') return; + var splituserid = command.userid.split('/'); + // Check that we are in the same domain and the user has rights over this node. + if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) { + // Check if this user has rights to get this message + //if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!! + + // See if the session is connected + var sessions = parent.webserver.wssessions[command.userid]; + + // Go ahead and send this message to the target node + if (sessions != null) { + command.nodeid = dbNodeKey; // Set the nodeid, required for responses. + delete command.userid; // Remove the userid, since we are sending to that userid, so it's implyed. + for (i in sessions) { sessions[i].send(JSON.stringify(command)); } + } + + if (parent.multiServer != null) { + // TODO: Add multi-server support + } + } + } else { // Route this command to the mesh + command.nodeid = dbNodeKey; + var cmdstr = JSON.stringify(command); + for (var userid in parent.webserver.wssessions) { // Find all connected users for this mesh and send the message + var user = parent.webserver.users[userid]; + if ((user != null) && (user.links != null)) { + var rights = user.links[dbMeshKey]; + if (rights != null) { // TODO: Look at what rights are needed for message routing + var xsessions = parent.webserver.wssessions[userid]; + // Send the message to all users on this server + for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } } + } + } + } + + // Send the message to all users of other servers + if (parent.multiServer != null) { + delete command.nodeid; + command.fromNodeid = dbNodeKey; + command.meshid = dbMeshKey; + parent.multiServer.DispatchMessage(command); + } + } + } + return obj; } diff --git a/package.json b/package.json index 73b0fbf3..c0d92ea3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.1-w", + "version": "0.4.1-x", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 5f923d2a..fe7c9452 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 f564d488..3282ae44 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -702,6 +702,12 @@   + + + @@ -1591,7 +1597,7 @@ if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } } if (index != -1) { // Node was found, dispatch the message - if (message.type == 'console') { p15consoleReceive(nodes[index], message.value); } // This is a console message. + if (message.type == 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message. else if (message.type == 'notify') { // This is a notification message. var n = getstore('notifications', 0); if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored. @@ -3282,8 +3288,13 @@ } function groupActionFunction() { + var mqttx = ''; + if (features & 0x00400000) { // Check if any of the selected devices have a MQTT connection active + var nodeids = getCheckedDevices(); + for (var i in nodeids) { if ((getNodeFromId(nodeids[i]).conn & 16) != 0) { mqttx = ''; } } + } var x = "Select an operation to perform on all selected devices. Actions will be performed only with proper rights.

"; - x += addHtmlValue('Operation', ''); + x += addHtmlValue('Operation', ''); setDialogMode(2, "Group Action", 3, groupActionFunctionEx, x); } @@ -3308,6 +3319,9 @@ } else if (op == 102) { // Move computers to a different group p10showChangeGroupDialog(getCheckedDevices()); + } else if (op == 103) { + // Send MQTT Message + p10showSendMqttMsgDialog(getCheckedDevices()); } else { // Power operation meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: parseInt(op) }); @@ -4434,6 +4448,7 @@ var y = ''; x += addHtmlValue('Operation', y); setDialogMode(2, "Device Action", 3, deviceActionFunctionEx, x); @@ -4443,7 +4458,10 @@ var op = Q('d2deviceop').value; if (op == 100) { // Device wake - meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] }); + meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] }); + } else if (op == 103) { + // Send MQTT Message + p10showSendMqttMsgDialog([currentNode._id]); } else { // Power operation meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) }); @@ -4572,6 +4590,24 @@ } } + function p10showSendMqttMsgDialog(nodeids) { + if (xxdialogMode) return false; + var x = addHtmlValue('Topic', ''); + x += addHtmlValue('Message', '
'); + setDialogMode(2, "Send MQTT message", 3, p10showSendMqttMsgDialogEx, x, nodeids); + p10validateSendMqttMsgDialog(); + Q('dp2topic').focus(); + return false; + } + + function p10validateSendMqttMsgDialog() { + QE('idx_dlgOkButton', (Q('dp2topic').value.length > 0) && (Q('dp2msg').value.length > 0)); + } + + function p10showSendMqttMsgDialogEx(b, nodeids) { + meshserver.send({ action: 'sendmqttmsg', nodeids: nodeids, topic: Q('dp2topic').value, msg: Q('dp2msg').value }); + } + function p10showChangeGroupDialog(nodeids) { if (xxdialogMode) return false; var targetMeshId = null; @@ -6416,6 +6452,7 @@ QE('p15consoleText', true); QH('p15statetext', ''); QH('p15coreName', ''); + QV('p15outputselecttd', false); if (samenode == false) { QH('p15agentConsoleText', consoleServerText); @@ -6434,14 +6471,18 @@ QH('p15agentConsoleText', consoleNode.consoleText); Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight; } - var online = ((consoleNode.conn & 1) != 0) ? true : false; - QH('p15statetext', online ? "Agent is online" : "Agent is offline"); + var online = (((consoleNode.conn & 1) != 0) || ((consoleNode.conn & 16) != 0)) ? true : false; + var onlineText = ((consoleNode.conn & 1) != 0) ? "Agent is online" : "Agent is offline" + if ((consoleNode.conn & 16) != 0) { onlineText += ', MQTT is online' } + QH('p15statetext', onlineText); QE('p15consoleText', online); QE('p15uploadCore', online); + QV('p15outputselecttd', (consoleNode.conn & 17) == 17); } else { QH('p15statetext', 'Access Denied'); QE('p15consoleText', false); QE('p15uploadCore', false); + QV('p15outputselecttd', false); } } } @@ -6461,21 +6502,29 @@ var consoleHistory = []; function p15consoleSend(e) { if (e && e.keyCode != 13) return; - var v = Q('p15consoleText').value, t = '
> ' + EscapeHtml(Q('p15consoleText').value) + '
'; - Q('p15agentConsoleText').innerHTML += t; - Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight; - Q('p15consoleText').value = ''; + var v = Q('p15consoleText').value, t = '
> ' + v + '
'; if (xxcurrentView == 115) { // Send the command to the server - TODO: In the future, we may support multiple servers. consoleServerText += t; meshserver.send({ action: 'serverconsole', value: v }); } else { - // Send the command to the mesh agent - consoleNode.consoleText += t; - meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value: v }); + if (((consoleNode.conn & 16) != 0) && ((Q('p15outputselect').value == 2) || ((consoleNode.conn & 1) == 0))) { + // Send the command to MQTT + t = '
MQTT> ' + EscapeHtml(v) + '
'; + consoleNode.consoleText += t; + meshserver.send({ action: 'sendmqttmsg', topic: 'console', nodeids: [ consoleNode._id ], msg: v }); + } else { + // Send the command to the mesh agent + consoleNode.consoleText += t; + meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value: v }); + } } + Q('p15agentConsoleText').innerHTML += t; + Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight; + Q('p15consoleText').value = ''; + // Add command to history list if (v.length > 0) { // Move this command to the top if it already exists @@ -6487,10 +6536,10 @@ } // Handle Mesh Agent console data - function p15consoleReceive(node, data) { - data = '
' + data + '
' + function p15consoleReceive(node, data, source) { if (node === 'serverconsole') { // Server console data + data = '
' + EscapeHtml(data) + '
' consoleServerText += data; if (consoleNode == 'server') { Q('p15agentConsoleText').innerHTML += data; @@ -6498,6 +6547,7 @@ } } else { // Agent console data + if (source == 'MQTT') { data = '
MQTT> ' + EscapeHtml(data) + '
'; } else { data = '
' + EscapeHtml(data) + '
' } if (node.consoleText == null) { node.consoleText = data; } else { node.consoleText += data; } if (consoleNode == node) { Q('p15agentConsoleText').innerHTML += data;