diff --git a/amtmanager.js b/amtmanager.js index d690a9ae..dd792aca 100644 --- a/amtmanager.js +++ b/amtmanager.js @@ -948,6 +948,7 @@ module.exports.CreateAmtManager = function (parent) { // 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 function performPowerAction(nodeid, action) { + console.log('performPowerAction', nodeid, action); var devices = obj.amtDevices[nodeid]; if (devices == null) return; for (var i in devices) { diff --git a/docs/docs/messaging/index.md b/docs/docs/messaging/index.md index bc157d76..9b9cb2a4 100644 --- a/docs/docs/messaging/index.md +++ b/docs/docs/messaging/index.md @@ -80,6 +80,26 @@ Discord integration requires that MeshCentral be run on NodeJS v17 or higher. On 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. +## XMPP Setup + +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. + +```json +{ + "messaging": { + "xmpp": { + service: "xmppserver.com", + credentials: { + username: 'username', + password: 'password' + } + } + } +} +``` + +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. + ## User Setup 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. diff --git a/meshcentral.js b/meshcentral.js index 298383d6..6f220444 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -4030,6 +4030,7 @@ function mainStart() { if (config.messaging != null) { if (config.messaging.telegram != null) { modules.push('telegram'); modules.push('input'); } 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); } } + if (config.messaging.xmpp != null) { modules.push('@xmpp/client'); } } // Setup web based push notifications diff --git a/meshmessaging.js b/meshmessaging.js index 274d92cd..a7770ae2 100644 --- a/meshmessaging.js +++ b/meshmessaging.js @@ -40,16 +40,31 @@ "token": "xxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxx" } } + +// For XMPP login, add this in config.json +"messaging": { + "xmpp": { + service: "xmppserver.com", + //domain: "xmppserver.com", + //resource: "example", + credentials: { + username: 'username', + password: 'password' + } + } +} */ // Construct a messaging server object module.exports.CreateServer = function (parent) { var obj = {}; obj.parent = parent; - obj.providers = 0; // 1 = Telegram, 2 = Signal, 4 = Discord + obj.providers = 0; // 1 = Telegram, 2 = Signal, 4 = Discord, 8 = XMPP obj.telegramClient = null; obj.discordClient = null; obj.discordUrl = null; + obj.xmppClient = null; + var xmppXml = null; // Telegram client setup if (parent.config.messaging.telegram) { @@ -137,6 +152,30 @@ module.exports.CreateServer = function (parent) { } } + // XMPP client setup + if (parent.config.messaging.xmpp) { + // Validate Discord configuration values + var xmppOK = true; + if (typeof parent.config.messaging.xmpp.service != 'string') { console.log('Invalid or missing XMPP service.'); xmppOK = false; } + + if (xmppOK) { + // Setup XMPP + const { client, xml } = require('@xmpp/client'); + const xmpp = client(parent.config.messaging.xmpp); + xmpp.on('error', function (err) { parent.debug('email', 'XMPP error: ' + err); console.error('XMPP error', err); }); + xmpp.on('offline', function () { parent.debug('email', 'XMPP client is offline.'); console.log('XMPP offline'); }); + //xmpp.on('stanza', async function (stanza) { if (stanza.is("message")) { await xmpp.send(xml('presence', { type: 'unavailable' })); await xmpp.stop(); } }); + xmpp.on('online', async function (address) { + // await xmpp.send(xml("presence")); const message = xml("message", { type: "chat", to: "username@server.com" }, xml("body", {}, "hello world")); await xmpp.send(message); + xmppXml = xml; + obj.xmppClient = xmpp; + obj.providers += 8; // Enable XMPP messaging + console.log("MeshCentral XMPP client is connected."); + }); + xmpp.start().catch(console.error); + } + } + // Send a direct message to a specific userid async function discordSendMsg(userId, message) { const user = await obj.discordClient.users.fetch(userId).catch(function () { return null; }); @@ -158,6 +197,13 @@ module.exports.CreateServer = function (parent) { }); } + // Send an XMPP message + async function sendXmppMessage(to, msg, func) { + const message = xmppXml('message', { type: 'chat', to: to.substring(5) }, xmppXml('body', {}, msg)); + await obj.xmppClient.send(message); + if (func != null) { func(true); } + } + // Send an user message obj.sendMessage = function(to, msg, func) { if ((to.startsWith('telegram:')) && (obj.telegramClient != null)) { // Telegram @@ -168,7 +214,13 @@ module.exports.CreateServer = function (parent) { } sendTelegramMessage(to, msg, func); } else if ((to.startsWith('discord:')) && (obj.discordClient != null)) { // Discord - discordFindUserByTag(to.substring(8), function (userid) { discordSendMsg(userid, msg); if (func != null) { func(true); } }); + discordFindUserByTag(to.substring(8), function (userid) { + parent.debug('email', 'Sending Discord message to: ' + to.substring(9) + ', ' + userid + ': ' + msg); + discordSendMsg(userid, msg); if (func != null) { func(true); } + }); + } else if ((to.startsWith('xmpp:')) && (obj.xmppClient != null)) { // XMPP + parent.debug('email', 'Sending XMPP message to: ' + to.substring(5) + ': ' + msg); + sendXmppMessage(to, msg, func); } else { // No providers found func(false, "No messaging providers found for this message."); diff --git a/meshuser.js b/meshuser.js index d9eeae08..811b5ae6 100644 --- a/meshuser.js +++ b/meshuser.js @@ -6695,6 +6695,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var handle = null; if ((command.service == 1) && ((parent.parent.msgserver.providers & 1) != 0)) { handle = 'telegram:@' + command.handle; } if ((command.service == 4) && ((parent.parent.msgserver.providers & 4) != 0)) { handle = 'discord:' + command.handle; } + if ((command.service == 8) && ((parent.parent.msgserver.providers & 8) != 0)) { handle = 'xmpp:' + command.handle; } if (handle == null) return; // Send a verification message @@ -6837,6 +6838,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if ((parent.parent.msgserver.providers & 1) != 0) { r.push("Usage: MSG \"telegram:@UserHandle\" \"Message\"."); } if ((parent.parent.msgserver.providers & 2) != 0) { r.push("Usage: MSG \"signal:UserHandle\" \"Message\"."); } if ((parent.parent.msgserver.providers & 4) != 0) { r.push("Usage: MSG \"discord:Username#0000\" \"Message\"."); } + if ((parent.parent.msgserver.providers & 8) != 0) { r.push("Usage: MSG \"xmpp:username@server.com\" \"Message\"."); } cmdData.result = r.join('\r\n'); } else { parent.parent.msgserver.sendMessage(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], function (status, msg) { diff --git a/mpsserver.js b/mpsserver.js index 0fe4d631..515069bb 100644 --- a/mpsserver.js +++ b/mpsserver.js @@ -30,7 +30,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { const net = require('net'); const tls = require('tls'); const MAX_IDLE = 90000; // 90 seconds max idle time, higher than the typical KEEP-ALIVE periode of 60 seconds - const KEEPALIVE_INTERVAL = 30; // 30 seconds is typical keepalive interval for AMT CIRA connection + const KEEPALIVE_INTERVAL = 30; // 30 seconds is typical keepalive interval for AMT CIRA connection // This MPS server is also a tiny HTTPS server. HTTP responses are here. obj.httpResponses = { diff --git a/views/default.handlebars b/views/default.handlebars index e8f85a33..4abe71ac 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -11985,6 +11985,7 @@ if ((serverinfo.userMsgProviders & 1) != 0) { y += ''; } if ((serverinfo.userMsgProviders & 2) != 0) { y += ''; } if ((serverinfo.userMsgProviders & 4) != 0) { y += ''; } + if ((serverinfo.userMsgProviders & 8) != 0) { y += ''; } y += ''; x += '
' + "Service" + '' + y; x += '
' + "Handle" + ''; @@ -11998,7 +11999,9 @@ function account_manageMessagingValidate(x) { if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); } - if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; } else { Q('d2handleinput')['placeholder'] = "Username"; } + if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; } + else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "username@server.com"; } + else { Q('d2handleinput')['placeholder'] = "Username"; } var ok = (Q('d2handleinput').value.length > 0); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } } 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 @@ if ((serverinfo.userMsgProviders & 1) != 0) { y += ''; } if ((serverinfo.userMsgProviders & 2) != 0) { y += ''; } if ((serverinfo.userMsgProviders & 4) != 0) { y += ''; } + if ((serverinfo.userMsgProviders & 8) != 0) { y += ''; } y += ''; x += '
' + "Service" + '' + y; x += '
' + "Handle" + ''; @@ -15870,6 +15874,7 @@ if (userinfo.msghandle) { if (userinfo.msghandle.startsWith('telegram:') && ((serverinfo.userMsgProviders & 1) != 0)) { Q('d2serviceselect').value = 1; Q('d2handleinput').value = userinfo.msghandle.substring(10); } if (userinfo.msghandle.startsWith('discord:') && ((serverinfo.userMsgProviders & 4) != 0)) { Q('d2serviceselect').value = 4; Q('d2handleinput').value = userinfo.msghandle.substring(8); } + if (userinfo.msghandle.startsWith('xmpp:') && ((serverinfo.userMsgProviders & 8) != 0)) { Q('d2serviceselect').value = 4; Q('d2handleinput').value = userinfo.msghandle.substring(5); } } p30editMessagingValidate(); } @@ -15877,6 +15882,10 @@ function p30editMessagingValidate(x) { QE('d2handleinput', Q('d2serviceselect').value != 0); if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); } + if (Q('d2serviceselect').value == 0) { Q('d2handleinput')['placeholder'] = ''; } + else if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; } + else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "username@server.com"; } + else { Q('d2handleinput')['placeholder'] = "Username"; } if (x == 1) { dialogclose(1); } } @@ -15886,6 +15895,7 @@ if ((Q('d2handleinput').value == '') || (Q('d2serviceselect').value == 0)) { handle = ''; } else if (Q('d2serviceselect').value == 1) { handle = 'telegram:@' + Q('d2handleinput').value; } else if (Q('d2serviceselect').value == 4) { handle = 'discord:' + Q('d2handleinput').value; } + else if (Q('d2serviceselect').value == 8) { handle = 'xmpp:' + Q('d2handleinput').value; } if (handle != null) { meshserver.send({ action: 'edituser', id: currentUser._id, msghandle: handle }); } }