From df3f87e903e2ccd21cca141f3792aae1cde24d26 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Sat, 11 Dec 2021 19:09:01 +0100 Subject: [PATCH] XMPP external components. --- CHANGELOG.md | 4 +++ client/admin-plugin-client-plugin.ts | 7 ++++ documentation/prosody.md | 9 +++++ server/lib/prosody/config.ts | 31 ++++++++++++---- server/lib/prosody/config/components.ts | 38 ++++++++++++++++++++ server/lib/prosody/config/content.ts | 29 ++++++++++++--- server/lib/settings.ts | 48 ++++++++++++++++++++++++- 7 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 server/lib/prosody/config/components.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e9c33b..60473e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## (unreleased yet) +### Features + +* Builtin Prosody: you can now allow «external XMPP components» to connect. This can be used for exemple to connect bots or bridges. For now, only connections from localhost will be allowed. + ### Minor changes and fixes * Spanish translations (thanks [rnek0](https://github.com/rnek0)). diff --git a/client/admin-plugin-client-plugin.ts b/client/admin-plugin-client-plugin.ts index 0576420c..47d45904 100644 --- a/client/admin-plugin-client-plugin.ts +++ b/client/admin-plugin-client-plugin.ts @@ -211,12 +211,19 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re case 'prosody-muc-log-by-default': case 'prosody-muc-expiration': case 'prosody-c2s': + case 'prosody-components': return options.formValues['chat-type'] !== ('builtin-prosody' as ChatType) case 'prosody-c2s-port': return !( options.formValues['chat-type'] === ('builtin-prosody' as ChatType) && options.formValues['prosody-c2s'] === true ) + case 'prosody-components-port': + case 'prosody-components-list': + return !( + options.formValues['chat-type'] === ('builtin-prosody' as ChatType) && + options.formValues['prosody-components'] === true + ) case 'chat-server': case 'chat-room': case 'chat-bosh-uri': diff --git a/documentation/prosody.md b/documentation/prosody.md index 8efa2a04..547dff85 100644 --- a/documentation/prosody.md +++ b/documentation/prosody.md @@ -100,6 +100,15 @@ The port that will be used by the c2s module of the builtin Prosody server. XMPP clients shall use this port to connect. Change it if this port is already in use on your server. +#### Enable external XMPP components + +This settings enable XMPP external components to connect to the server. +For now, this option **only allows connections from localhost components**. + +This feature could be used to connect bridges or bots. + +More informations on Prosody external components [here](https://prosody.im/doc/components). + ## Moderation You can access rooms settings and moderation tools by opening the chat in a new window, diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index cab7285e..c2a8c6a0 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -1,3 +1,4 @@ +import type { ProsodyLogLevel } from './config/content' import * as fs from 'fs' import * as path from 'path' import { getBaseRouterRoute } from '../helpers' @@ -5,7 +6,7 @@ import { ProsodyFilePaths } from './config/paths' import { ConfigLogExpiration, ProsodyConfigContent } from './config/content' import { getProsodyDomain } from './config/domain' import { getAPIKey } from '../apikey' -import type { ProsodyLogLevel } from './config/content' +import { parseExternalComponents } from './config/components' async function getWorkingDir (options: RegisterServerOptions): Promise { const peertubeHelpers = options.peertubeHelpers @@ -72,7 +73,7 @@ interface ProsodyConfig { roomType: 'video' | 'channel' logByDefault: boolean logExpiration: ConfigLogExpiration - valuesToHideInDiagnostic: {[key: string]: string} + valuesToHideInDiagnostic: Map } async function getProsodyConfig (options: RegisterServerOptions): Promise { const logger = options.peertubeHelpers.logger @@ -83,12 +84,15 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise() const port = (settings['prosody-port'] as string) || '52800' if (!/^\d+$/.test(port)) { throw new Error('Invalid port') @@ -96,12 +100,13 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise { + return line.replace(/#.*$/, '') + .replace(/^\s+/, '') + .replace(/\s+$/, '') + }) + lines = lines.filter(line => line !== '') + + const r: ExternalComponent[] = [] + for (const line of lines) { + const matches = line.match(/^([\w.]+)\s*:\s*(\w+)$/) + if (matches) { + let name = matches[1] + if (!name.includes('.')) { + name = name + '.' + prosodyDomain + } + r.push({ + name, + secret: matches[2] + }) + } + } + return r +} + +export { + ExternalComponent, + parseExternalComponents +} diff --git a/server/lib/prosody/config/content.ts b/server/lib/prosody/config/content.ts index da2e18c1..b04e875d 100644 --- a/server/lib/prosody/config/content.ts +++ b/server/lib/prosody/config/content.ts @@ -1,4 +1,5 @@ import type { ProsodyFilePaths } from './paths' +import type { ExternalComponent } from './components' type ConfigEntryValue = boolean | number | string | ConfigEntryValue[] @@ -102,16 +103,19 @@ class ProsodyConfigVirtualHost extends ProsodyConfigBlock { class ProsodyConfigComponent extends ProsodyConfigBlock { name: string - type: string + type?: string - constructor (type: string, name: string) { + constructor (name: string, type?: string) { super(' ') this.type = type this.name = name } write (): string { - return `Component "${this.name}" "${this.type}"\n` + super.write() + if (this.type !== undefined) { + return `Component "${this.name}" "${this.type}"\n` + super.write() + } + return `Component "${this.name}"\n` + super.write() } } @@ -123,6 +127,7 @@ class ProsodyConfigContent { authenticated?: ProsodyConfigVirtualHost anon: ProsodyConfigVirtualHost muc: ProsodyConfigComponent + externalComponents: ProsodyConfigComponent[] = [] log: string prosodyDomain: string @@ -132,7 +137,7 @@ class ProsodyConfigContent { this.log = '' this.prosodyDomain = prosodyDomain this.anon = new ProsodyConfigVirtualHost('anon.' + prosodyDomain) - this.muc = new ProsodyConfigComponent('muc', 'room.' + prosodyDomain) + this.muc = new ProsodyConfigComponent('room.' + prosodyDomain, 'muc') this.global.set('daemonize', false) this.global.set('allow_registration', false) @@ -228,6 +233,17 @@ class ProsodyConfigContent { this.global.set('c2s_ports', [c2sPort]) } + useExternalComponents (componentsPort: string, components: ExternalComponent[]): void { + this.global.set('component_ports', [componentsPort]) + this.global.set('component_interfaces', ['127.0.0.1', '::1']) + + for (const component of components) { + const c = new ProsodyConfigComponent(component.name) + c.set('component_secret', component.secret) + this.externalComponents.push(c) + } + } + useMucHttpDefault (url: string): void { this.muc.add('modules_enabled', 'muc_http_defaults') this.muc.add('muc_create_api_url', url) @@ -309,6 +325,11 @@ class ProsodyConfigContent { content += '\n\n' content += this.muc.write() content += '\n\n' + for (const externalComponent of this.externalComponents) { + content += '\n\n' + content += externalComponent.write() + content += '\n\n' + } return content } } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 703fc7e3..dd692980 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -380,7 +380,53 @@ This option alone only allows connections from localhost clients.` `The port that will be used by the c2s module of the builtin Prosody server.
XMPP clients shall use this port to connect.
Change it if this port is already in use on your server.
-Keep it close this port on your firewall for now, it will not be accessed from the outer world.` +You can keep this port closed on your firewall for now, it will not be accessed from the outer world.` + }) + + registerSetting({ + name: 'prosody-components', + label: 'Enable custom Prosody external components', + type: 'input-checkbox', + default: false, + private: true, + descriptionHTML: +`Enable the use of external XMPP components.
+This option alone only allows connections from localhost.
+This feature can for example be used to connect some bots to the chatting rooms.` + }) + + registerSetting({ + name: 'prosody-components-port', + label: 'Prosody external components port', + type: 'input', + default: '53470', + private: true, + descriptionHTML: +`The port that will be used by XMPP components to connect to the Prosody server.
+Change it if this port is already in use on your server.
+You can keep this port closed on your firewall for now, it will not be accessed from the outer world.` + }) + + registerSetting({ + name: 'prosody-components-list', + label: 'External components', + type: 'input-textarea', + default: '', + private: true, + descriptionHTML: +`The external components to create: +
    +
  • One per line.
  • +
  • Use the format «component_name:component_secret» (spaces will be trimmed)
  • +
  • You can add comments: everything after the # character will be stripped off, and empty lines ignored
  • +
  • The name can only contain alphanumeric characters and dots
  • +
  • + If the name contains only alphanumeric characters, it will be suffixed with the XMPP domain. + For exemple «bridge» will become «bridge.your_domain.tld». + You can also specify a full domain name, but you have to make sure to configure your DNS correctly. +
  • +
  • Only use alphanumeric characters in the secret passphrase (use at least 15 characters).
  • +
` }) // ********** settings changes management