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