Added XMPP support (#4679)

Ylian Saint-Hilaire committed Oct 28, 2022 at 12:34 UTC cbc26b3a4381519d05cdaf250145c6f7548fbeaa
7 files changed +90 -4
amtmanager.js
+1
@@ -948,6 +948,7 @@ module.exports.CreateAmtManager = function (parent) {
948
949 // Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL
950 function performPowerAction(nodeid, action) {
951 + console.log('performPowerAction', nodeid, action);
952 var devices = obj.amtDevices[nodeid];
953 if (devices == null) return;
954 for (var i in devices) {
docs/docs/messaging/index.md
+20
@@ -80,6 +80,26 @@ Discord integration requires that MeshCentral be run on NodeJS v17 or higher. On
80
81 Once users will need to join the same Discord server as the bot, the optional "serverurl" can be used to give the users a URL link to join the server, this can be a server invitation link or some other URL with instructions.
82
83 +## XMPP Setup
84 +
85 +For XMPP integration, you need to provide MeshCentral with a XMPP server, username and password so that MeshCentral can login and send notifications to users. You can get a XMPP account to any number of servers or start up your own XMPP server.
86 +
87 +```json
88 +{
89 + "messaging": {
90 + "xmpp": {
91 + service: "xmppserver.com",
92 + credentials: {
93 + username: 'username',
94 + password: 'password'
95 + }
96 + }
97 + }
98 +}
99 +```
100 +
101 +An easy way to get setup with XMPP is to create a free account with [chatterboxtown.us](https://chatterboxtown.us/) and then, setup MeshCentral with the service value set to "chatterboxtown.us" along with the username and password of you account. This can be done in minutes. Once setup, users will be able to setup and verify XMLL accounts and use this for notifications and 2FA verification.
102 +
103 ## User Setup
104
105 Once a messaging system is setup with MeshCentral, users will be able to register their handle and verify that they own that account by typing in a 6 digit code.
meshcentral.js
+1
@@ -4030,6 +4030,7 @@ function mainStart() {
4030 if (config.messaging != null) {
4031 if (config.messaging.telegram != null) { modules.push('telegram'); modules.push('input'); }
4032 if (config.messaging.discord != null) { if (nodeVersion >= 17) { modules.push('discord.js@14.6.0'); } else { delete config.messaging.discord; addServerWarning('This NodeJS version does not support Discord.js.', 26); } }
4033 + if (config.messaging.xmpp != null) { modules.push('@xmpp/client'); }
4034 }
4035
4036 // Setup web based push notifications
meshmessaging.js
+54 -2
@@ -40,16 +40,31 @@
40 "token": "xxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxx"
41 }
42 }
43 +
44 +// For XMPP login, add this in config.json
45 +"messaging": {
46 + "xmpp": {
47 + service: "xmppserver.com",
48 + //domain: "xmppserver.com",
49 + //resource: "example",
50 + credentials: {
51 + username: 'username',
52 + password: 'password'
53 + }
54 + }
55 +}
56 */
57
58 // Construct a messaging server object
59 module.exports.CreateServer = function (parent) {
60 var obj = {};
61 obj.parent = parent;
49 - obj.providers = 0; // 1 = Telegram, 2 = Signal, 4 = Discord
62 + obj.providers = 0; // 1 = Telegram, 2 = Signal, 4 = Discord, 8 = XMPP
63 obj.telegramClient = null;
64 obj.discordClient = null;
65 obj.discordUrl = null;
66 + obj.xmppClient = null;
67 + var xmppXml = null;
68
69 // Telegram client setup
70 if (parent.config.messaging.telegram) {
@@ -137,6 +152,30 @@ module.exports.CreateServer = function (parent) {
152 }
153 }
154
155 + // XMPP client setup
156 + if (parent.config.messaging.xmpp) {
157 + // Validate Discord configuration values
158 + var xmppOK = true;
159 + if (typeof parent.config.messaging.xmpp.service != 'string') { console.log('Invalid or missing XMPP service.'); xmppOK = false; }
160 +
161 + if (xmppOK) {
162 + // Setup XMPP
163 + const { client, xml } = require('@xmpp/client');
164 + const xmpp = client(parent.config.messaging.xmpp);
165 + xmpp.on('error', function (err) { parent.debug('email', 'XMPP error: ' + err); console.error('XMPP error', err); });
166 + xmpp.on('offline', function () { parent.debug('email', 'XMPP client is offline.'); console.log('XMPP offline'); });
167 + //xmpp.on('stanza', async function (stanza) { if (stanza.is("message")) { await xmpp.send(xml('presence', { type: 'unavailable' })); await xmpp.stop(); } });
168 + xmpp.on('online', async function (address) {
169 + // await xmpp.send(xml("presence")); const message = xml("message", { type: "chat", to: "username@server.com" }, xml("body", {}, "hello world")); await xmpp.send(message);
170 + xmppXml = xml;
171 + obj.xmppClient = xmpp;
172 + obj.providers += 8; // Enable XMPP messaging
173 + console.log("MeshCentral XMPP client is connected.");
174 + });
175 + xmpp.start().catch(console.error);
176 + }
177 + }
178 +
179 // Send a direct message to a specific userid
180 async function discordSendMsg(userId, message) {
181 const user = await obj.discordClient.users.fetch(userId).catch(function () { return null; });
@@ -158,6 +197,13 @@ module.exports.CreateServer = function (parent) {
197 });
198 }
199
200 + // Send an XMPP message
201 + async function sendXmppMessage(to, msg, func) {
202 + const message = xmppXml('message', { type: 'chat', to: to.substring(5) }, xmppXml('body', {}, msg));
203 + await obj.xmppClient.send(message);
204 + if (func != null) { func(true); }
205 + }
206 +
207 // Send an user message
208 obj.sendMessage = function(to, msg, func) {
209 if ((to.startsWith('telegram:')) && (obj.telegramClient != null)) { // Telegram
@@ -168,7 +214,13 @@ module.exports.CreateServer = function (parent) {
214 }
215 sendTelegramMessage(to, msg, func);
216 } else if ((to.startsWith('discord:')) && (obj.discordClient != null)) { // Discord
171 - discordFindUserByTag(to.substring(8), function (userid) { discordSendMsg(userid, msg); if (func != null) { func(true); } });
217 + discordFindUserByTag(to.substring(8), function (userid) {
218 + parent.debug('email', 'Sending Discord message to: ' + to.substring(9) + ', ' + userid + ': ' + msg);
219 + discordSendMsg(userid, msg); if (func != null) { func(true); }
220 + });
221 + } else if ((to.startsWith('xmpp:')) && (obj.xmppClient != null)) { // XMPP
222 + parent.debug('email', 'Sending XMPP message to: ' + to.substring(5) + ': ' + msg);
223 + sendXmppMessage(to, msg, func);
224 } else {
225 // No providers found
226 func(false, "No messaging providers found for this message.");
meshuser.js
+2
@@ -6695,6 +6695,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6695 var handle = null;
6696 if ((command.service == 1) && ((parent.parent.msgserver.providers & 1) != 0)) { handle = 'telegram:@' + command.handle; }
6697 if ((command.service == 4) && ((parent.parent.msgserver.providers & 4) != 0)) { handle = 'discord:' + command.handle; }
6698 + if ((command.service == 8) && ((parent.parent.msgserver.providers & 8) != 0)) { handle = 'xmpp:' + command.handle; }
6699 if (handle == null) return;
6700
6701 // Send a verification message
@@ -6837,6 +6838,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6838 if ((parent.parent.msgserver.providers & 1) != 0) { r.push("Usage: MSG \"telegram:@UserHandle\" \"Message\"."); }
6839 if ((parent.parent.msgserver.providers & 2) != 0) { r.push("Usage: MSG \"signal:UserHandle\" \"Message\"."); }
6840 if ((parent.parent.msgserver.providers & 4) != 0) { r.push("Usage: MSG \"discord:Username#0000\" \"Message\"."); }
6841 + if ((parent.parent.msgserver.providers & 8) != 0) { r.push("Usage: MSG \"xmpp:username@server.com\" \"Message\"."); }
6842 cmdData.result = r.join('\r\n');
6843 } else {
6844 parent.parent.msgserver.sendMessage(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], function (status, msg) {
mpsserver.js
+1 -1
@@ -30,7 +30,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
30 const net = require('net');
31 const tls = require('tls');
32 const MAX_IDLE = 90000; // 90 seconds max idle time, higher than the typical KEEP-ALIVE periode of 60 seconds
33 - const KEEPALIVE_INTERVAL = 30; // 30 seconds is typical keepalive interval for AMT CIRA connection
33 + const KEEPALIVE_INTERVAL = 30; // 30 seconds is typical keepalive interval for AMT CIRA connection
34
35 // This MPS server is also a tiny HTTPS server. HTTP responses are here.
36 obj.httpResponses = {
views/default.handlebars
+11 -1
@@ -11985,6 +11985,7 @@
11985 if ((serverinfo.userMsgProviders & 1) != 0) { y += '<option value=1>' + "Telegram" + '</option>'; }
11986 if ((serverinfo.userMsgProviders & 2) != 0) { y += '<option value=2>' + "Signal Messenger" + '</option>'; }
11987 if ((serverinfo.userMsgProviders & 4) != 0) { y += '<option value=4>' + "Discord" + '</option>'; }
11988 + if ((serverinfo.userMsgProviders & 8) != 0) { y += '<option value=8>' + "XMPP" + '</option>'; }
11989 y += '</select>';
11990 x += '<table><tr><td>' + "Service" + '<td>' + y;
11991 x += '<tr><td>' + "Handle" + '<td><input maxlength=64 style=width:160px;margin-left:8px id=d2handleinput onKeyUp=account_manageMessagingValidate() onkeypress="if (event.key==\'Enter\') account_manageMessagingValidate(1)">';
@@ -11998,7 +11999,9 @@
11999
12000 function account_manageMessagingValidate(x) {
12001 if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); }
12001 - if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; } else { Q('d2handleinput')['placeholder'] = "Username"; }
12002 + if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; }
12003 + else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "username@server.com"; }
12004 + else { Q('d2handleinput')['placeholder'] = "Username"; }
12005 var ok = (Q('d2handleinput').value.length > 0); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); }
12006 }
12007 function account_manageMessagingAdd() { if (Q('d2handleinput').value.length == 0) return; QE('d2handleinput', false); meshserver.send({ action: 'verifyMessaging', service: Q('d2serviceselect').value, handle: Q('d2handleinput').value }); }
@@ -15860,6 +15863,7 @@
15863 if ((serverinfo.userMsgProviders & 1) != 0) { y += '<option value=1>' + "Telegram" + '</option>'; }
15864 if ((serverinfo.userMsgProviders & 2) != 0) { y += '<option value=2>' + "Signal Messenger" + '</option>'; }
15865 if ((serverinfo.userMsgProviders & 4) != 0) { y += '<option value=4>' + "Discord" + '</option>'; }
15866 + if ((serverinfo.userMsgProviders & 8) != 0) { y += '<option value=8>' + "XMPP" + '</option>'; }
15867 y += '</select>';
15868 x += '<table style=margin-top:12px><tr><td>' + "Service" + '<td>' + y;
15869 x += '<tr><td>' + "Handle" + '<td><input maxlength=64 style=width:160px;margin-left:8px id=d2handleinput onKeyUp=p30editMessagingValidate() onkeypress="if (event.key==\'Enter\') p30editMessagingValidate(1)">';
@@ -15870,6 +15874,7 @@
15874 if (userinfo.msghandle) {
15875 if (userinfo.msghandle.startsWith('telegram:') && ((serverinfo.userMsgProviders & 1) != 0)) { Q('d2serviceselect').value = 1; Q('d2handleinput').value = userinfo.msghandle.substring(10); }
15876 if (userinfo.msghandle.startsWith('discord:') && ((serverinfo.userMsgProviders & 4) != 0)) { Q('d2serviceselect').value = 4; Q('d2handleinput').value = userinfo.msghandle.substring(8); }
15877 + if (userinfo.msghandle.startsWith('xmpp:') && ((serverinfo.userMsgProviders & 8) != 0)) { Q('d2serviceselect').value = 4; Q('d2handleinput').value = userinfo.msghandle.substring(5); }
15878 }
15879 p30editMessagingValidate();
15880 }
@@ -15877,6 +15882,10 @@
15882 function p30editMessagingValidate(x) {
15883 QE('d2handleinput', Q('d2serviceselect').value != 0);
15884 if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); }
15885 + if (Q('d2serviceselect').value == 0) { Q('d2handleinput')['placeholder'] = ''; }
15886 + else if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; }
15887 + else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "username@server.com"; }
15888 + else { Q('d2handleinput')['placeholder'] = "Username"; }
15889 if (x == 1) { dialogclose(1); }
15890 }
15891
@@ -15886,6 +15895,7 @@
15895 if ((Q('d2handleinput').value == '') || (Q('d2serviceselect').value == 0)) { handle = ''; }
15896 else if (Q('d2serviceselect').value == 1) { handle = 'telegram:@' + Q('d2handleinput').value; }
15897 else if (Q('d2serviceselect').value == 4) { handle = 'discord:' + Q('d2handleinput').value; }
15898 + else if (Q('d2serviceselect').value == 8) { handle = 'xmpp:' + Q('d2handleinput').value; }
15899 if (handle != null) { meshserver.send({ action: 'edituser', id: currentUser._id, msghandle: handle }); }
15900 }
15901