1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-06 03:50:15 +02:00

Merge branch 'master' of github.com:processone/ejabberd

This commit is contained in:
Jérôme Sautret 2024-12-18 16:24:12 +01:00
commit 92b2bb7532
33 changed files with 1319 additions and 266 deletions

View file

@ -240,6 +240,7 @@ ejabberd reads `EJABBERD_MACRO_*` environment variables
and uses them to define the `*`
[macros](https://docs.ejabberd.im/admin/configuration/file-format/#macros-in-configuration-file),
overwriting the corresponding macro definition if it was set in the configuration file.
This is supported since ejabberd 24.12.
For example, if you configure this in `ejabberd.yml`:
@ -273,12 +274,12 @@ For this you can either:
Example to connect a local `ejabberdctl` to a containerized ejabberd:
1. When creating the container, export port 5210, and set `ERLANG_COOKIE`:
```sh
docker run --name ejabberd -it \
-e ERLANG_COOKIE=`cat $HOME/.erlang.cookie` \
-p 5210:5210 -p 5222:5222 \
ghcr.io/processone/ejabberd
```
```sh
docker run --name ejabberd -it \
-e ERLANG_COOKIE=`cat $HOME/.erlang.cookie` \
-p 5210:5210 -p 5222:5222 \
ghcr.io/processone/ejabberd
```
2. Set `ERL_DIST_PORT=5210` in ejabberdctl.cfg of container and local ejabberd
3. Restart the container
4. Now use `ejabberdctl` in your local ejabberd deployment
@ -320,7 +321,7 @@ docker buildx build \
### Podman build
It's also possible to use podman instead of docker, just notice:
To build the image using podman instead of docker, notice:
- `EXPOSE 4369-4399` port range is not supported, remove that in Dockerfile
- It mentions that `healthcheck` is not supported by the Open Container Initiative image format
- to start with command `live`, you may want to add environment variable `EJABBERD_BYPASS_WARNINGS=true`
@ -372,7 +373,9 @@ Composer Examples
### Minimal Example
This is the barely minimal file to get a usable ejabberd.
Store it as `docker-compose.yml`:
If using Docker, write this `docker-compose.yml` file
and start it with `docker-compose up`:
```yaml
services:
@ -386,11 +389,34 @@ services:
- "5443:5443"
```
Create and start the container with the command:
```bash
docker-compose up
If using Podman, write this `minimal.yml` file
and start it with `podman kube play minimal.yml`:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: ejabberd
spec:
containers:
- name: ejabberd
image: ghcr.io/processone/ejabberd
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5269
hostPort: 5269
- containerPort: 5280
hostPort: 5280
- containerPort: 5443
hostPort: 5443
```
### Customized Example
This example shows the usage of several customizations:
@ -420,7 +446,9 @@ mkdir database
sudo chown 9000:9000 database
```
Now write this `docker-compose.yml` file:
If using Docker, write this `docker-compose.yml` file
and start it with `docker-compose up`:
```yaml
version: '3.7'
@ -444,6 +472,56 @@ services:
- ./database:/opt/ejabberd/database
```
If using Podman, write this `custom.yml` file
and start it with `podman kube play custom.yml`:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: ejabberd
spec:
containers:
- name: ejabberd
image: ghcr.io/processone/ejabberd
env:
- name: CTL_ON_CREATE
value: register admin example.com asd
- name: CTL_ON_START
value: registered_users example.com ;
status
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5269
hostPort: 5269
- containerPort: 5280
hostPort: 5280
- containerPort: 5443
hostPort: 5443
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
- mountPath: /opt/ejabberd/database
name: db
volumes:
- name: config
hostPath:
path: ./ejabberd.yml
type: File
- name: db
hostPath:
path: ./database
type: DirectoryOrCreate
```
### Clustering Example
In this example, the main container is created first.
@ -458,6 +536,9 @@ and it should exist in the second node after join.
Notice that in this example the main container does not have access
to the exterior; the replica exports the ports and can be accessed.
If using Docker, write this `docker-compose.yml` file
and start it with `docker-compose up`:
```yaml
version: '3.7'
@ -477,15 +558,183 @@ services:
depends_on:
main:
condition: service_healthy
ports:
- "5222:5222"
- "5269:5269"
- "5280:5280"
- "5443:5443"
environment:
- ERLANG_NODE_ARG=ejabberd@replica
- ERLANG_COOKIE=dummycookie123
- CTL_ON_CREATE=join_cluster ejabberd@main
- CTL_ON_START=registered_users localhost ;
status
ports:
- "5222:5222"
- "5269:5269"
- "5280:5280"
- "5443:5443"
```
If using Podman, write this `cluster.yml` file
and start it with `podman kube play cluster.yml`.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: cluster
spec:
containers:
- name: first
image: ghcr.io/processone/ejabberd
env:
- name: ERLANG_NODE_ARG
value: main@cluster
- name: ERLANG_COOKIE
value: dummycookie123
- name: CTL_ON_CREATE
value: register admin localhost asd
- name: CTL_ON_START
value: stats registeredusers ;
status
- name: EJABBERD_MACRO_PORT_C2S
value: 6222
- name: EJABBERD_MACRO_PORT_C2S_TLS
value: 6223
- name: EJABBERD_MACRO_PORT_S2S
value: 6269
- name: EJABBERD_MACRO_PORT_HTTP_TLS
value: 6443
- name: EJABBERD_MACRO_PORT_HTTP
value: 6280
- name: EJABBERD_MACRO_PORT_MQTT
value: 6883
- name: EJABBERD_MACRO_PORT_PROXY65
value: 6777
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
- name: second
image: ghcr.io/processone/ejabberd
env:
- name: ERLANG_NODE_ARG
value: replica@cluster
- name: ERLANG_COOKIE
value: dummycookie123
- name: CTL_ON_CREATE
value: join_cluster main@cluster ;
started ;
list_cluster
- name: CTL_ON_START
value: stats registeredusers ;
check_password admin localhost asd ;
status
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5280
hostPort: 5280
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
volumes:
- name: config
hostPath:
path: ./conf/ejabberd.yml
type: File
```
Your configuration file should use those macros to allow each ejabberd node
use different listening port numbers:
```diff
diff --git a/ejabberd.yml.example b/ejabberd.yml.example
index 39e423a64..6e875b48f 100644
--- a/ejabberd.yml.example
+++ b/ejabberd.yml.example
@@ -24,9 +24,19 @@ loglevel: info
# - /etc/letsencrypt/live/domain.tld/fullchain.pem
# - /etc/letsencrypt/live/domain.tld/privkey.pem
+define_macro:
+ PORT_C2S: 5222
+ PORT_C2S_TLS: 5223
+ PORT_S2S: 5269
+ PORT_HTTP_TLS: 5443
+ PORT_HTTP: 5280
+ PORT_STUN: 5478
+ PORT_MQTT: 1883
+ PORT_PROXY65: 7777
+
listen:
-
- port: 5222
+ port: PORT_C2S
ip: "::"
module: ejabberd_c2s
max_stanza_size: 262144
@@ -34,7 +44,7 @@ listen:
access: c2s
starttls_required: true
-
- port: 5223
+ port: PORT_C2S_TLS
ip: "::"
module: ejabberd_c2s
max_stanza_size: 262144
@@ -42,13 +52,13 @@ listen:
access: c2s
tls: true
-
- port: 5269
+ port: PORT_S2S
ip: "::"
module: ejabberd_s2s_in
max_stanza_size: 524288
shaper: s2s_shaper
-
- port: 5443
+ port: PORT_HTTP_TLS
ip: "::"
module: ejabberd_http
tls: true
@@ -60,14 +70,14 @@ listen:
/upload: mod_http_upload
/ws: ejabberd_http_ws
-
- port: 5280
+ port: PORT_HTTP
ip: "::"
module: ejabberd_http
request_handlers:
/admin: ejabberd_web_admin
/.well-known/acme-challenge: ejabberd_acme
-
- port: 5478
+ port: PORT_STUN
ip: "::"
transport: udp
module: ejabberd_stun
@@ -77,7 +87,7 @@ listen:
## The server's public IPv6 address:
# turn_ipv6_address: "2001:db8::3"
-
- port: 1883
+ port: PORT_MQTT
ip: "::"
module: mod_mqtt
backlog: 1000
@@ -207,6 +217,7 @@ modules:
mod_proxy65:
access: local
max_connections: 5
+ port: PORT_PROXY65
mod_pubsub:
access_createnode: pubsub_createnode
plugins:
```

View file

@ -87,6 +87,11 @@ or some other method from [ProcessOne Contact][p1contact].
For commercial offering and support, including [ejabberd Business Edition][p1home]
and [Fluux (ejabberd in the Cloud)][fluux], please check [ProcessOne ejabberd page][p1home].
Security
--------
For information on how to report security vulnerabilities, please refer to the [SECURITY.md](SECURITY.md) file. It contains guidelines on how to report vulnerabilities privately and securely, ensuring that any issues are addressed in a timely and confidential manner.
Community
---------

45
SECURITY.md Normal file
View file

@ -0,0 +1,45 @@
# Security Policy
## Supported Versions
We recommend that all users always use the latest version of ejabberd.
To ensure the best experience and security, upgrade to the latest version available on [this repo](https://github.com/processone/ejabberd).
## Reporting a Vulnerability
### Private Reporting
**Preferred Method**: Use GitHub's private vulnerability reporting system by clicking the "Report a Vulnerability" button in the [Security tab of this repository](https://github.com/processone/ejabberd/security). This ensures your report is securely transmitted and tracked.
**Alternative**: If you cannot use the GitHub system, send an email to **`contact@process-one.net`** with the following details:
- A clear description of the vulnerability.
- Steps to reproduce the issue.
- Any potential impact or exploitation scenarios.
### Response Time
We aim to acknowledge receipt of your report within 72 hours. You can expect regular updates on the status of your report.
### Resolution
If the vulnerability is confirmed, we will work on a patch or mitigation strategy.
We will notify you once the issue is resolved and coordinate a public disclosure if needed.
### Acknowledgements
We value and appreciate the contributions of security researchers and community members.
If you wish, we are happy to acknowledge your efforts publicly by listing your name (or alias) below in this document.
Please let us know if you would like to be recognized when reporting the vulnerability.
## Public Discussion
For general inquiries or discussions about the projects security, feel free to chat with us here:
- XMPP room: `ejabberd@conference.process-one.net`
- [GitHub Discussions](https://github.com/processone/ejabberd/discussions)
However, please note that if the issue is **critical** or potentially exploitable, **do not share it publicly**. Instead, we strongly recommend you contact the maintainers directly via the private reporting methods outlined above to ensure a secure and timely response.
Thank you for helping us improve the security of ejabberd!

View file

@ -776,12 +776,21 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0480.html"/>
<xmpp:version>0.1</xmpp:version>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>24.10</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_scram_upgrade</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0484.html"/>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>24.12</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_auth_fast</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0485.html"/>

View file

@ -2,12 +2,12 @@
.\" Title: ejabberd.yml
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
.\" Date: 10/28/2024
.\" Date: 12/17/2024
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "10/28/2024" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "12/17/2024" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -82,12 +82,12 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
.sp
Some options can be specified for particular virtual host(s) only using \fIhost_config\fR or \fIappend_host_config\fR options\&. Such options are called \fIlocal\fR\&. Examples are \fImodules\fR, \fIauth_method\fR and \fIdefault_db\fR\&. The options that cannot be defined per virtual host are called \fIglobal\fR\&. Examples are \fIloglevel\fR, \fIcertfiles\fR and \fIlisten\fR\&. It is a configuration mistake to put \fIglobal\fR options under \fIhost_config\fR or \fIappend_host_config\fR section \- ejabberd will refuse to load such configuration\&.
.sp
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/24\&.10/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/24\&.12/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
.sp
Note that this document is intended to provide comprehensive description of all configuration options that can be consulted to understand the meaning of a particular option, its format and possible values\&. It will be quite hard to understand how to configure ejabberd by reading this document only \- for this purpose the reader is recommended to read online Configuration Guide available at https://docs\&.ejabberd\&.im/admin/configuration\&.
.SH "TOP LEVEL OPTIONS"
.sp
This section describes top level options of ejabberd 24\&.10\&. The options that changed in this version are marked with 🟤\&.
This section describes top level options of ejabberd 24\&.12\&. The options that changed in this version are marked with 🟤\&.
.PP
\fBaccess_rules\fR: \fI{AccessName: {allow|deny: ACLRules|ACLName}}\fR
.RS 4
@ -512,7 +512,8 @@ c2s_ciphers:
.PP
\fBc2s_dhfile\fR: \fIPath\fR
.RS 4
Full path to a file containing custom DH parameters to use for c2s connections\&. Such a file could be created with the command "openssl dhparam \-out dh\&.pem 2048"\&. If this option is not specified, 2048\-bit MODP Group with 256\-bit Prime Order Subgroup will be used as defined in RFC5114 Section 2\&.3\&.
Full path to a file containing custom DH parameters to use for c2s connections\&. Such a file could be created with the command
\fI"openssl dhparam \-out dh\&.pem 2048"\fR\&. If this option is not specified, 2048\-bit MODP Group with 256\-bit Prime Order Subgroup will be used as defined in RFC5114 Section 2\&.3\&.
.RE
.PP
\fBc2s_protocol_options\fR: \fI[Option, \&.\&.\&.]\fR
@ -639,7 +640,7 @@ listener as well\&. If set to
already enabled, with encryption if available\&. If set to
\fIundefined\fR, it builds the URL using the deprecated
\fIcaptcha_host\fR
+ /captcha\&. The default value is
\fI+ /captcha\fR\&. The default value is
\fIauto\fR\&.
.RE
.PP
@ -758,28 +759,43 @@ are:
The number of components to balance\&.
.RE
.PP
\fBtype\fR: \fIrandom | source | destination | bare_source | bare_destination\fR
\fBtype\fR: \fIValue\fR
.RS 4
How to deliver stanzas to connected components:
\fIrandom\fR
\- an instance is chosen at random;
\fIdestination\fR
\- an instance is chosen by the full JID of the packet\(cqs
How to deliver stanzas to connected components\&. The default value is
\fIrandom\fR\&. Possible values:
.RE
.PP
\fB\- bare_destination\fR
.RS 4
by the bare JID (without resource) of the packet\(cqs
\fIto\fR
attribute;
\fIsource\fR
\- by the full JID of the packet\(cqs
attribute
.RE
.PP
\fB\- bare_source\fR
.RS 4
by the bare JID (without resource) of the packet\(cqs
\fIfrom\fR
attribute;
\fIbare_destination\fR
\- by the bare JID (without resource) of the packet\(cqs
attribute is used
.RE
.PP
\fB\- destination\fR
.RS 4
an instance is chosen by the full JID of the packet\(cqs
\fIto\fR
attribute;
\fIbare_source\fR
\- by the bare JID (without resource) of the packet\(cqs
attribute
.RE
.PP
\fB\- random\fR
.RS 4
an instance is chosen at random
.RE
.PP
\fB\- source\fR
.RS 4
by the full JID of the packet\(cqs
\fIfrom\fR
attribute is used\&. The default value is
\fIrandom\fR\&.
attribute
.RE
.sp
\fBExample\fR:
@ -1104,13 +1120,16 @@ section for details\&.
\fINote\fR
about this option: added in 22\&.10\&. The number of messages to accept in
log_burst_limit_window_time
period before starting to drop them\&. Default 500
period before starting to drop them\&. Default
500
.RE
.PP
\fBlog_burst_limit_window_time\fR: \fINumber\fR
.RS 4
\fINote\fR
about this option: added in 22\&.10\&. The time period to rate\-limit log messages by\&. Defaults to 1 second\&.
about this option: added in 22\&.10\&. The time period to rate\-limit log messages by\&. Defaults to
1
second\&.
.RE
.PP
\fBlog_modules_fully\fR: \fI[Module, \&.\&.\&.]\fR
@ -1132,9 +1151,8 @@ crash\&.log\&.0\&.
\fBlog_rotate_size\fR: \fIpos_integer() | infinity\fR
.RS 4
The size (in bytes) of a log file to trigger rotation\&. If set to
\fIinfinity\fR, log rotation is disabled\&. The default value is
\fI10485760\fR
(that is, 10 Mb)\&.
\fIinfinity\fR, log rotation is disabled\&. The default value is 10 Mb expressed in bytes:
\fI10485760\fR\&.
.RE
.PP
\fBloglevel\fR: \fInone | emergency | alert | critical | error | warning | notice | info | debug\fR
@ -1175,7 +1193,7 @@ This option can be used to tune tick time parameter of
.RS 4
Whether to use the
\fIdatabase\&.md#default\-and\-new\-schemas|new SQL schema\fR\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/24\&.10/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
https://github\&.com/processone/ejabberd/tree/24\&.12/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
\fInew\fR
schema can handle several XMPP domains in a single ejabberd database\&. Using this
\fInew\fR
@ -1295,14 +1313,16 @@ which means it first tries connecting with IPv6, if that fails it tries using IP
\fBoutgoing_s2s_ipv4_address\fR: \fIAddress\fR
.RS 4
\fINote\fR
about this option: added in 20\&.12\&. Specify the IPv4 address that will be used when establishing an outgoing S2S IPv4 connection, for example "127\&.0\&.0\&.1"\&. The default value is
about this option: added in 20\&.12\&. Specify the IPv4 address that will be used when establishing an outgoing S2S IPv4 connection, for example
\fI"127\&.0\&.0\&.1"\fR\&. The default value is
\fIundefined\fR\&.
.RE
.PP
\fBoutgoing_s2s_ipv6_address\fR: \fIAddress\fR
.RS 4
\fINote\fR
about this option: added in 20\&.12\&. Specify the IPv6 address that will be used when establishing an outgoing S2S IPv6 connection, for example "::FFFF:127\&.0\&.0\&.1"\&. The default value is
about this option: added in 20\&.12\&. Specify the IPv6 address that will be used when establishing an outgoing S2S IPv6 connection, for example
\fI"::FFFF:127\&.0\&.0\&.1"\fR\&. The default value is
\fIundefined\fR\&.
.RE
.PP
@ -1414,11 +1434,13 @@ or
if the latter is not set\&.
.RE
.PP
\fBredis_server\fR: \fIHostname\fR
\fBredis_server 🟤\fR: \fIHost | IP Address | Unix Socket Path\fR
.RS 4
A hostname or an IP address of the
\fINote\fR
about this option: improved in 24\&.12\&. A hostname, IP address or unix domain socket file of the
\fIdatabase\&.md#redis|Redis\fR
server\&.The default is
server\&. Setup the path to unix domain socket like:
\fI"unix:/path/to/socket"\fR\&. The default value is
\fIlocalhost\fR\&.
.RE
.PP
@ -1527,7 +1549,8 @@ s2s_ciphers:
.PP
\fBs2s_dhfile\fR: \fIPath\fR
.RS 4
Full path to a file containing custom DH parameters to use for s2s connections\&. Such a file could be created with the command "openssl dhparam \-out dh\&.pem 2048"\&. If this option is not specified, 2048\-bit MODP Group with 256\-bit Prime Order Subgroup will be used as defined in RFC5114 Section 2\&.3\&.
Full path to a file containing custom DH parameters to use for s2s connections\&. Such a file could be created with the command
\fI"openssl dhparam \-out dh\&.pem 2048"\fR\&. If this option is not specified, 2048\-bit MODP Group with 256\-bit Prime Order Subgroup will be used as defined in RFC5114 Section 2\&.3\&.
.RE
.PP
\fBs2s_dns_retries\fR: \fINumber\fR
@ -1775,7 +1798,8 @@ The password for SQL authentication\&. The default is empty string\&.
.PP
\fBsql_pool_size\fR: \fISize\fR
.RS 4
Number of connections to the SQL server that ejabberd will open for each virtual host\&. The default value is 10\&. WARNING: for SQLite this value is
Number of connections to the SQL server that ejabberd will open for each virtual host\&. The default value is
\fI10\fR\&. WARNING: for SQLite this value is
\fI1\fR
by default and it\(cqs not recommended to change it due to potential race conditions\&.
.RE
@ -1830,7 +1854,8 @@ this can also be an ODBC connection string\&. When
is
\fImysql\fR
or
\fIpgsql\fR, this can be the path to a unix domain socket expressed like: "unix:/path/to/socket"\&.The default value is
\fIpgsql\fR, this can be the path to a unix domain socket expressed like:
\fI"unix:/path/to/socket"\fR\&.The default value is
\fIlocalhost\fR\&.
.RE
.PP
@ -1944,7 +1969,8 @@ This option enables validation for
header to protect against connections from other domains than given in the configuration file\&. In this way, the lower layer load balancer can be chosen for a specific ejabberd implementation while still providing a secure WebSocket connection\&. The default value is
\fIignore\fR\&. An example value of the
\fIURL\fR
is "https://test\&.example\&.org:8081"\&.
is
\fI"https://test\&.example\&.org:8081"\fR\&.
.RE
.PP
\fBwebsocket_ping_interval\fR: \fItimeout()\fR
@ -1964,7 +1990,7 @@ seconds\&.
.RE
.SH "MODULES"
.sp
This section describes modules options of ejabberd 24\&.10\&. The modules that changed in this version are marked with 🟤\&.
This section describes modules options of ejabberd 24\&.12\&. The modules that changed in this version are marked with 🟤\&.
.SS "mod_adhoc"
.sp
This module implements XEP\-0050: Ad\-Hoc Commands\&. It\(cqs an auxiliary module and is only needed by some of the other modules\&.
@ -1989,41 +2015,11 @@ This module provides additional administrative commands\&.
.sp
Details for some commands:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIban_account\fR: This command kicks all the connected sessions of the account from the server\&. It also changes their password to a randomly generated one, so they can\(cqt login anymore unless a server administrator changes their password again\&. It is possible to define the reason of the ban\&. The new password also includes the reason and the date and time of the ban\&. See an example below\&.
.RE
\fIban_account\fR API: This command kicks all the connected sessions of the account from the server\&. It also changes their password to a randomly generated one, so they can\(cqt login anymore unless a server administrator changes their password again\&. It is possible to define the reason of the ban\&. The new password also includes the reason and the date and time of the ban\&. See an example below\&.
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIpushroster\fR: (and
\fIpushroster\-all\fR) The roster file must be placed, if using Windows, on the directory where you installed ejabberd:
C:/Program Files/ejabberd
or similar\&. If you use other Operating System, place the file on the same directory where the \&.beam files are installed\&. See below an example roster file\&.
.RE
\fIpush_roster\fR API (and \fIpush_roster_all\fR API): The roster file must be placed, if using Windows, on the directory where you installed ejabberd: C:/Program Files/ejabberd or similar\&. If you use other Operating System, place the file on the same directory where the \&.beam files are installed\&. See below an example roster file\&.
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIsrg_create\fR: If you want to put a group Name with blank spaces, use the characters "\*(Aq and \*(Aq" to define when the Name starts and ends\&. See an example below\&.
.RE
\fIsrg_create\fR API: If you want to put a group Name with blank spaces, use the characters \fI"\fR\*(Aq and \fI\*(Aq"\fR to define when the Name starts and ends\&. See an example below\&.
.sp
The module has no options\&.
.sp
@ -2056,7 +2052,7 @@ modules:
.RE
.\}
.sp
Content of roster file for \fIpushroster\fR command:
Content of roster file for \fIpush_roster\fR API:
.sp
.if n \{\
.RS 4
@ -2070,7 +2066,7 @@ Content of roster file for \fIpushroster\fR command:
.RE
.\}
.sp
With this call, the sessions of the local account which JID is boby@example\&.org will be kicked, and its password will be set to something like \fIBANNED_ACCOUNT\(em20080425T21:45:07\(em2176635\(emSpammed_rooms\fR
With this call, the sessions of the local account which JID is \fIboby@example\&.org\fR will be kicked, and its password will be set to something like \fIBANNED_ACCOUNT\(em20080425T21:45:07\(em2176635\(emSpammed_rooms\fR
.sp
.if n \{\
.RS 4
@ -2082,7 +2078,7 @@ ejabberdctl vhost example\&.org ban_account boby "Spammed rooms"
.RE
.\}
.sp
Call to srg_create using double\-quotes and single\-quotes:
Call to \fIsrg_create\fR API using double\-quotes and single\-quotes:
.sp
.if n \{\
.RS 4
@ -2215,6 +2211,58 @@ Same as top\-level
option, but applied to this module only\&.
.RE
.RE
.SS "mod_auth_fast 🟤"
.sp
\fINote\fR about this option: added in 24\&.12\&.
.sp
The module adds support for XEP\-0480: Fast Authentication Streamlining Tokens that allows users to authenticate using self managed tokens\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBdb_type\fR: \fImnesia | sql\fR
.RS 4
Same as top\-level
\fIdefault_db\fR
option, but applied to this module only\&.
.RE
.PP
\fBtoken_lifetime\fR: \fItimeout()\fR
.RS 4
Time that tokens will be keept, measured from it\(cqs creation time\&. Default value set to 30 days
.RE
.PP
\fBtoken_refresh_age\fR: \fItimeout()\fR
.RS 4
This time determines age of token, that qualifies for automatic refresh\&. Default value set to 1 day
.RE
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
modules:
mod_auth_fast:
token_timeout: 14days
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_avatar"
.sp
The purpose of the module is to cope with legacy and modern XMPP clients posting avatars\&. The process is described in XEP\-0398: User Avatar to vCard\-Based Avatars Conversion\&.
@ -2541,7 +2589,7 @@ This module serves a simple page for the Converse XMPP web browser client\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR\fIejabberd_http\fR\fIlisten\-options\&.md#request_handlers|request_handlers\fR\&.
.sp
Make sure either \fImod_bosh\fR or \fIejabberd_http_ws\fR \fIlisten\-options\&.md#request_handlers|request_handlers\fR are enabled\&.
Make sure either \fImod_bosh\fR or \fIlisten\&.md#ejabberd_http_ws|ejabberd_http_ws\fR are enabled in at least one \fIrequest_handlers\fR\&.
.sp
When \fIconversejs_css\fR and \fIconversejs_script\fR are \fIauto\fR, by default they point to the public Converse client\&.
.sp
@ -2601,9 +2649,9 @@ is replaced with the hostname\&. The default value is
.PP
\fBwebsocket_url\fR: \fIauto | WebSocketURL\fR
.RS 4
A WebSocket URL to which Converse can connect to\&. The keyword
A WebSocket URL to which Converse can connect to\&. The
\fI@HOST@\fR
is replaced with the real virtual host name\&. If set to
keyword is replaced with the real virtual host name\&. If set to
\fIauto\fR, it will build the URL of the first configured WebSocket request handler\&. The default value is
\fIauto\fR\&.
.RE
@ -2930,7 +2978,7 @@ This module serves small \fIhost\-meta\fR files as described in XEP\-0156: Disco
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR\fIejabberd_http\fR\fIlisten\-options\&.md#request_handlers|request_handlers\fR\&.
.sp
Notice it only works if ejabberd_http has tls enabled\&.
Notice it only works if \fIlisten\&.md#ejabberd_http|ejabberd_http\fR has \fIlisten\-options\&.md#tls|tls\fR enabled\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -2998,11 +3046,26 @@ This module provides a ReST interface to call \fI\&.\&./\&.\&./developer/ejabber
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR\fIejabberd_http\fR\fIlisten\-options\&.md#request_handlers|request_handlers\fR\&.
.sp
To use a specific API version N, when defining the URL path in the request_handlers, add a \fIvN\fR\&. For example: \fI/api/v2: mod_http_api\fR
To use a specific API version N, when defining the URL path in the request_handlers, add a vN\&. For example: \fI/api/v2: mod_http_api\fR\&.
.sp
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/<command_name>\fR
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/COMMAND\-NAME\fR
.sp
The module has no options\&.
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBdefault_version\fR: \fIinteger() | string()\fR
.RS 4
What API version to use when none is specified in the URL path\&. If setting an ejabberd version, it will use the latest API version that was available in that ejabberd version\&. For example, setting
\fI"24\&.06"\fR
in this option implies
\fI2\fR\&. The default value is the latest version\&.
.RE
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -3024,7 +3087,8 @@ listen:
/api: mod_http_api
modules:
mod_http_api: {}
mod_http_api:
default_version: 2
.fi
.if n \{\
.RE
@ -3176,26 +3240,34 @@ This option specifies additional header fields to be included in all HTTP respon
.RS 4
This option defines the permission bits of the
\fIdocroot\fR
directory and any directories created during file uploads\&. The bits are specified as an octal number (see the chmod(1) manual page) within double quotes\&. For example: "0755"\&. The default is undefined, which means no explicit permissions will be set\&.
directory and any directories created during file uploads\&. The bits are specified as an octal number (see the
\fIchmod(1)\fR
manual page) within double quotes\&. For example:
\fI"0755"\fR\&. The default is undefined, which means no explicit permissions will be set\&.
.RE
.PP
\fBdocroot\fR: \fIPath\fR
.RS 4
Uploaded files are stored below the directory specified (as an absolute path) with this option\&. The keyword @HOME@ is replaced with the home directory of the user running ejabberd, and the keyword @HOST@ with the virtual host name\&. The default value is "@HOME@/upload"\&.
Uploaded files are stored below the directory specified (as an absolute path) with this option\&. The keyword
\fI@HOME@\fR
is replaced with the home directory of the user running ejabberd, and the keyword
\fI@HOST@\fR
with the virtual host name\&. The default value is
\fI"@HOME@/upload"\fR\&.
.RE
.PP
\fBexternal_secret\fR: \fIText\fR
.RS 4
This option makes it possible to offload all HTTP Upload processing to a separate HTTP server\&. Both ejabberd and the HTTP server should share this secret and behave exactly as described at
Prosody\(cqs mod_http_upload_external
in the
\fIImplementation\fR
section\&. There is no default value\&.
Prosody\(cqs mod_http_upload_external: Implementation\&. There is no default value\&.
.RE
.PP
\fBfile_mode\fR: \fIPermission\fR
.RS 4
This option defines the permission bits of uploaded files\&. The bits are specified as an octal number (see the chmod(1) manual page) within double quotes\&. For example: "0644"\&. The default is undefined, which means no explicit permissions will be set\&.
This option defines the permission bits of uploaded files\&. The bits are specified as an octal number (see the
\fIchmod(1)\fR
manual page) within double quotes\&. For example:
\fI"0644"\fR\&. The default is undefined, which means no explicit permissions will be set\&.
.RE
.PP
\fBget_url\fR: \fIURL\fR
@ -3203,8 +3275,9 @@ This option defines the permission bits of uploaded files\&. The bits are specif
This option specifies the initial part of the GET URLs used for downloading the files\&. The default value is
\fIundefined\fR\&. When this option is
\fIundefined\fR, this option is set to the same value as
\fIput_url\fR\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: if GET requests are handled by
\fImod_http_upload\fR, the
\fIput_url\fR\&. The keyword
\fI@HOST@\fR
is replaced with the virtual host name\&. NOTE: if GET requests are handled by this module, the
\fIget_url\fR
must match the
\fIput_url\fR\&. Setting it to a different value only makes sense if an external web server or
@ -3223,7 +3296,8 @@ instead\&.
.RS 4
This option defines the Jabber IDs of the service\&. If the
\fIhosts\fR
option is not specified, the only Jabber ID will be the hostname of the virtual host with the prefix "upload\&."\&. The keyword
option is not specified, the only Jabber ID will be the hostname of the virtual host with the prefix
\fI"upload\&."\fR\&. The keyword
\fI@HOST@\fR
is replaced with the real virtual host name\&.
.RE
@ -3246,12 +3320,16 @@ must be specified\&. The default value is
.PP
\fBname\fR: \fIName\fR
.RS 4
A name of the service in the Service Discovery\&. This will only be displayed by special XMPP clients\&. The default value is "HTTP File Upload"\&.
A name of the service in the Service Discovery\&. The default value is
\fI"HTTP File Upload"\fR\&. Please note this will only be displayed by some XMPP clients\&.
.RE
.PP
\fBput_url\fR: \fIURL\fR
.RS 4
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is "https://@HOST@:5443/upload"\&.
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword
\fI@HOST@\fR
is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is
\fI"https://@HOST@:5443/upload"\fR\&.
.RE
.PP
\fBrm_on_unregister\fR: \fItrue | false\fR
@ -3263,7 +3341,9 @@ This option specifies whether files uploaded by a user should be removed when th
\fBsecret_length\fR: \fILength\fR
.RS 4
This option defines the length of the random string included in the GET and PUT URLs generated by
\fImod_http_upload\fR\&. The minimum length is 8 characters, but it is recommended to choose a larger value\&. The default value is
\fImod_http_upload\fR\&. The minimum length is
\fI8\fR
characters, but it is recommended to choose a larger value\&. The default value is
\fI40\fR\&.
.RE
.PP
@ -3358,7 +3438,7 @@ This module depends on \fImod_http_upload\fR\&.
.PP
\fBaccess_hard_quota\fR: \fIAccessName\fR
.RS 4
This option defines which access rule is used to specify the "hard quota" for the matching JIDs\&. That rule must yield a positive number for any JID that is supposed to have a quota limit\&. This is the number of megabytes a corresponding user may upload\&. When this threshold is exceeded, ejabberd deletes the oldest files uploaded by that user until their disk usage equals or falls below the specified soft quota (see
This option defines which access rule is used to specify the "hard quota" for the matching JIDs\&. That rule must yield a positive number for any JID that is supposed to have a quota limit\&. This is the number of megabytes a corresponding user may upload\&. When this threshold is exceeded, ejabberd deletes the oldest files uploaded by that user until their disk usage equals or falls below the specified soft quota (see also option
\fIaccess_soft_quota\fR)\&. The default value is
\fIhard_upload_quota\fR\&.
.RE
@ -3388,7 +3468,7 @@ directory, once per day\&. The default value is
\fBExamples:\fR
.RS 4
.sp
Please note that it\(cqs not necessary to specify the \fIaccess_hard_quota\fR and \fIaccess_soft_quota\fR options in order to use the quota feature\&. You can stick to the default names and just specify access rules such as those in this example:
Notice it\(cqs not necessary to specify the \fIaccess_hard_quota\fR and \fIaccess_soft_quota\fR options in order to use the quota feature\&. You can stick to the default names and just specify access rules such as those in this example:
.sp
.if n \{\
.RS 4
@ -3600,7 +3680,7 @@ When this option is disabled, for each individual subscriber a separate mucsub m
.sp
\fINote\fR about this option: added in 24\&.02\&.
.sp
Matrix gateway\&.
Matrix gateway\&. Erlang/OTP 25 or higher is required to use this module\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -3853,7 +3933,8 @@ instead\&.
.RS 4
This option defines the Jabber IDs of the service\&. If the
\fIhosts\fR
option is not specified, the only Jabber ID will be the hostname of the virtual host with the prefix "mix\&."\&. The keyword
option is not specified, the only Jabber ID will be the hostname of the virtual host with the prefix
\fI"mix\&."\fR\&. The keyword
\fI@HOST@\fR
is replaced with the real virtual host name\&.
.RE
@ -4048,15 +4129,40 @@ This module adds ability to synchronize local MQTT topics with data on remote se
Identifier of a user that will be assigned as owner of local changes\&.
.RE
.PP
\fBservers\fR: \fI{ServerUrl: {publish: [TopicPairs, subscribe: [TopicPairs], authentication: [AuthInfo]}}]\fR
\fBservers\fR: \fI{ServerUrl: {Key: Value}}\fR
.RS 4
Declaration of data to share, must contain
\fIpublish\fR
or
\fIsubscribe\fR
or both, and
\fIauthentication\fR
section with username/password field or certfile pointing to client certificate\&. Accepted urls can use schema mqtt, mqtts (mqtt with tls), mqtt5, mqtt5s (both to trigger v5 protocol), ws, wss, ws5, wss5\&. Certificate authentication can be only used with mqtts, mqtt5s, wss, wss5\&.
Declaration of data to share for each ServerUrl\&. Server URLs can use schemas:
\fImqtt\fR,
\fImqtts\fR
(mqtt with tls),
\fImqtt5\fR,
\fImqtt5s\fR
(both to trigger v5 protocol),
\fIws\fR,
\fIwss\fR,
\fIws5\fR,
\fIwss5\fR\&. Keys must be:
.PP
\fBauthentication\fR: \fI{AuthKey: AuthValue}\fR
.RS 4
List of authentication information, where AuthKey can be:
\fIusername\fR
and
\fIpassword\fR
fields, or
\fIcertfile\fR
pointing to client certificate\&. Certificate authentication can be used only with mqtts, mqtt5s, wss, wss5\&.
.RE
.PP
\fBpublish\fR: \fI{LocalTopic: RemoteTopic}\fR
.RS 4
Either publish or subscribe must be set, or both\&.
.RE
.PP
\fBsubscribe\fR: \fI{RemoteTopic: LocalTopic}\fR
.RS 4
Either publish or subscribe must be set, or both\&.
.RE
.RE
.RE
.sp
@ -4074,16 +4180,16 @@ section with username/password field or certfile pointing to client certificate\
.nf
modules:
mod_mqtt_bridge:
replication_user: "mqtt@xmpp\&.server\&.com"
servers:
"mqtt://server\&.com":
authentication:
certfile: "/etc/ejabberd/mqtt_server\&.pem"
publish:
"localA": "remoteA" # local changes to \*(AqlocalA\*(Aq will be replicated on remote server as \*(AqremoteA\*(Aq
"topicB": "topicB"
subscribe:
"remoteB": "localB" # changes to \*(AqremoteB\*(Aq on remote server will be stored as \*(AqlocalB\*(Aq on local server
authentication:
certfile: "/etc/ejabberd/mqtt_server\&.pem"
replication_user: "mqtt@xmpp\&.server\&.com"
.fi
.if n \{\
.RE
@ -4311,7 +4417,7 @@ The room persists even if the last participant leaves\&. The default value is
\fIfalse\fR\&.
.RE
.PP
\fBpresence_broadcast\fR: \fI[moderator | participant | visitor, \&.\&.\&.]\fR
\fBpresence_broadcast\fR: \fI[Role]\fR
.RS 4
List of roles for which presence is broadcasted\&. The list can contain one or several of:
\fImoderator\fR,
@ -4376,7 +4482,10 @@ Timeout before hibernating the room process, expressed in seconds\&. The default
.PP
\fBhistory_size\fR: \fISize\fR
.RS 4
A small history of the current discussion is sent to users when they enter the room\&. With this option you can define the number of history messages to keep and send to users joining the room\&. The value is a non\-negative integer\&. Setting the value to 0 disables the history feature and, as a result, nothing is kept in memory\&. The default value is 20\&. This value affects all rooms on the service\&. NOTE: modern XMPP clients rely on Message Archives (XEP\-0313), so feel free to disable the history feature if you\(cqre only using modern clients and have
A small history of the current discussion is sent to users when they enter the room\&. With this option you can define the number of history messages to keep and send to users joining the room\&. The value is a non\-negative integer\&. Setting the value to
\fI0\fR
disables the history feature and, as a result, nothing is kept in memory\&. The default value is
\fI20\fR\&. This value affects all rooms on the service\&. NOTE: modern XMPP clients rely on Message Archives (XEP\-0313), so feel free to disable the history feature if you\(cqre only using modern clients and have
\fImod_mam\fR
module loaded\&.
.RE
@ -4462,12 +4571,16 @@ This option defines after how many users in the room, it is considered overcrowd
.PP
\fBmin_message_interval\fR: \fINumber\fR
.RS 4
This option defines the minimum interval between two messages send by an occupant in seconds\&. This option is global and valid for all rooms\&. A decimal value can be used\&. When this option is not defined, message rate is not limited\&. This feature can be used to protect a MUC service from occupant abuses and limit number of messages that will be broadcasted by the service\&. A good value for this minimum message interval is 0\&.4 second\&. If an occupant tries to send messages faster, an error is send back explaining that the message has been discarded and describing the reason why the message is not acceptable\&.
This option defines the minimum interval between two messages send by an occupant in seconds\&. This option is global and valid for all rooms\&. A decimal value can be used\&. When this option is not defined, message rate is not limited\&. This feature can be used to protect a MUC service from occupant abuses and limit number of messages that will be broadcasted by the service\&. A good value for this minimum message interval is
\fI0\&.4\fR
second\&. If an occupant tries to send messages faster, an error is send back explaining that the message has been discarded and describing the reason why the message is not acceptable\&.
.RE
.PP
\fBmin_presence_interval\fR: \fINumber\fR
.RS 4
This option defines the minimum of time between presence changes coming from a given occupant in seconds\&. This option is global and valid for all rooms\&. A decimal value can be used\&. When this option is not defined, no restriction is applied\&. This option can be used to protect a MUC service for occupants abuses\&. If an occupant tries to change its presence more often than the specified interval, the presence is cached by ejabberd and only the last presence is broadcasted to all occupants in the room after expiration of the interval delay\&. Intermediate presence packets are silently discarded\&. A good value for this option is 4 seconds\&.
This option defines the minimum of time between presence changes coming from a given occupant in seconds\&. This option is global and valid for all rooms\&. A decimal value can be used\&. When this option is not defined, no restriction is applied\&. This option can be used to protect a MUC service for occupants abuses\&. If an occupant tries to change its presence more often than the specified interval, the presence is cached by ejabberd and only the last presence is broadcasted to all occupants in the room after expiration of the interval delay\&. Intermediate presence packets are silently discarded\&. A good value for this option is
\fI4\fR
seconds\&.
.RE
.PP
\fBname\fR: \fIstring()\fR
@ -4573,7 +4686,7 @@ This module depends on \fImod_muc\fR\&.
\fINote\fR
about this option: added in 22\&.05\&. How many users can be subscribed to a room at once using the
\fIsubscribe_room_many\fR
command\&. The default value is
API\&. The default value is
\fI50\fR\&.
.RE
.RE
@ -5031,22 +5144,8 @@ modules:
.SS "mod_offline"
.sp
This module implements XEP\-0160: Best Practices for Handling Offline Messages and XEP\-0013: Flexible Offline Message Retrieval\&. This means that all messages sent to an offline user will be stored on the server until that user comes online again\&. Thus it is very similar to how email works\&. A user is considered offline if no session presence priority > 0 are currently open\&.
.if n \{\
.sp
.\}
.RS 4
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBNote\fR
.ps -1
.br
.sp
\fIejabberdctl\fR has a command to delete expired messages (see chapter \fI\&.\&./guide/managing\&.md|Managing an ejabberd server\fR in online documentation\&.
.sp .5v
.RE
The \fIdelete_expired_messages\fR API allows to delete expired messages, and \fIdelete_old_messages\fR API deletes older ones\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -5058,7 +5157,9 @@ This module implements XEP\-0160: Best Practices for Handling Offline Messages a
.PP
\fBaccess_max_user_messages\fR: \fIAccessName\fR
.RS 4
This option defines which access rule will be enforced to limit the maximum number of offline messages that a user can have (quota)\&. When a user has too many offline messages, any new messages that they receive are discarded, and a <resource\-constraint/> error is returned to the sender\&. The default value is
This option defines which access rule will be enforced to limit the maximum number of offline messages that a user can have (quota)\&. When a user has too many offline messages, any new messages that they receive are discarded, and a
\fI<resource\-constraint/>\fR
error is returned to the sender\&. The default value is
\fImax_user_offline_messages\fR\&.
.RE
.PP
@ -5092,8 +5193,12 @@ option, but applied to this module only\&.
.PP
\fBstore_empty_body\fR: \fItrue | false | unless_chat_state\fR
.RS 4
Whether or not to store messages that lack a <body/> element\&. The default value is
\fIunless_chat_state\fR, which tells ejabberd to store messages even if they lack the <body/> element, unless they only contain a chat state notification (as defined in
Whether or not to store messages that lack a
\fI<body/>\fR
element\&. The default value is
\fIunless_chat_state\fR, which tells ejabberd to store messages even if they lack the
\fI<body/>\fR
element, unless they only contain a chat state notification (as defined in
XEP\-0085: Chat State Notifications\&.
.RE
.PP
@ -5112,11 +5217,11 @@ option, but applied to this module only\&.
.PP
\fBuse_mam_for_storage\fR: \fItrue | false\fR
.RS 4
This is an experimental option\&. Enabling this option,
\fImod_offline\fR
uses the
This is an experimental option\&. By enabling the option, this module uses the
\fIarchive\fR
table from
\fImod_mam\fR
archive table instead of its own spool table to retrieve the messages received when the user was offline\&. This allows client developers to slowly drop XEP\-0160 and rely on XEP\-0313 instead\&. It also further reduces the storage required when you enable MucSub\&. Enabling this option has a known drawback for the moment: most of flexible message retrieval queries don\(cqt work (those that allow retrieval/deletion of messages by id), but this specification is not widely used\&. The default value is
instead of its own spool table to retrieve the messages received when the user was offline\&. This allows client developers to slowly drop XEP\-0160 and rely on XEP\-0313 instead\&. It also further reduces the storage required when you enable MucSub\&. Enabling this option has a known drawback for the moment: most of flexible message retrieval queries don\(cqt work (those that allow retrieval/deletion of messages by id), but this specification is not widely used\&. The default value is
\fIfalse\fR
to keep former behaviour as default\&.
.RE
@ -5307,7 +5412,7 @@ This module implements XEP\-0016: Privacy Lists\&.
.ps -1
.br
.sp
Nowadays modern XMPP clients rely on XEP\-0191: Blocking Command which is implemented by \fImod_blocking\fR module\&. However, you still need \fImod_privacy\fR loaded in order for \fImod_blocking\fR to work\&.
Nowadays modern XMPP clients rely on XEP\-0191: Blocking Command which is implemented by \fImod_blocking\fR\&. However, you still need \fImod_privacy\fR loaded in order for \fImod_blocking\fR to work\&.
.sp .5v
.RE
.sp
@ -5360,7 +5465,7 @@ This module adds support for XEP\-0049: Private XML Storage\&.
.sp
Using this method, XMPP entities can store private data on the server, retrieve it whenever necessary and share it between multiple connected clients of the same user\&. The data stored might be anything, as long as it is a valid XML\&. One typical usage is storing a bookmark of all user\(cqs conferences (XEP\-0048: Bookmarks)\&.
.sp
It also implements the bookmark conversion described in XEP\-0402: PEP Native Bookmarks, see the command \fIbookmarks_to_pep\fR API\&.
It also implements the bookmark conversion described in XEP\-0402: PEP Native Bookmarks, see \fIbookmarks_to_pep\fR API\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -6628,7 +6733,7 @@ This module enables you to create shared roster groups: groups of accounts that
.sp
The big advantages of this feature are that end users do not need to manually add all users to their rosters, and that they cannot permanently delete users from the shared roster groups\&. A shared roster group can have members from any XMPP server, but the presence will only be available from and to members of the same virtual host where the group is created\&. It still allows the users to have / add their own contacts, as it does not replace the standard roster\&. Instead, the shared roster contacts are merged to the relevant users at retrieval time\&. The standard user rosters thus stay unmodified\&.
.sp
Shared roster groups can be edited via the Web Admin, and some API commands called \fIsrg_*\fR\&. Each group has a unique name and those parameters:
Shared roster groups can be edited via the Web Admin, and some API commands called \fIsrg_\fR, for example \fIsrg_add\fR API\&. Each group has a unique name and those parameters:
.sp
.RS 4
.ie n \{\
@ -7980,7 +8085,7 @@ Should the operating system be revealed or not\&. The default value is
.RE
.SH "LISTENERS"
.sp
This section describes listeners options of ejabberd 24\&.10\&.
This section describes listeners options of ejabberd 24\&.12\&.
.sp
TODO
.SH "AUTHOR"
@ -7988,13 +8093,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 24\&.10\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 24\&.12\&. Configuration options of other ejabberd versions may differ significantly\&.
.SH "REPORTING BUGS"
.sp
Report bugs to https://github\&.com/processone/ejabberd/issues
.SH "SEE ALSO"
.sp
Default configuration file: https://github\&.com/processone/ejabberd/blob/24\&.10/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/24\&.12/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp

View file

@ -135,7 +135,7 @@ defmodule Ejabberd.MixProject do
{:eimp, "~> 1.0"},
{:ex_doc, "~> 0.31", only: [:dev, :edoc], runtime: false},
{:fast_tls, "~> 1.1.22"},
{:fast_xml, "~> 1.1.53"},
{:fast_xml, "~> 1.1.53", override: true},
{:fast_yaml, "~> 1.0"},
{:idna, "~> 6.0"},
{:mqtree, "~> 1.0"},
@ -144,7 +144,7 @@ defmodule Ejabberd.MixProject do
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:xmpp, "~> 1.9"},
{:xmpp, git: "https://github.com/processone/xmpp", ref: "8071c86f33b9a8e9d66a10e058be088eecdc670b", override: true},
{:yconf, git: "https://github.com/processone/yconf.git", ref: "9898754f16cbd4585a1c2061d72fa441ecb2e938", override: true}]
++ cond_deps()
end

View file

@ -34,6 +34,6 @@
"stringprep": {:hex, :stringprep, "1.0.30", "46cf0ff631b3e7328f61f20b454d59428d87738f25d709798b5dcbb9b83c23f1", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "f6fc9b3384a03877830f89b2f38580caf3f4a27448a4a333d6a8c3975c220b9a"},
"stun": {:hex, :stun, "1.2.15", "eec510af6509201ff97f1f2c87b7977c833bf29c04e985383370ec21f04e4ccf", [:rebar3], [{:fast_tls, "1.1.22", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "f6d8a541a29fd13f2ce658b676c0cc661262b96e045b52def1644b75ebc0edef"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"xmpp": {:hex, :xmpp, "1.9.0", "d92446bf51d36adda02db63b963fe6d4a1ede33e59b38a43d9b90afd20c25b74", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "c1b91be74a9a9503afa6766f756477516920ffbfeea0c260c2fa171355f53c27"},
"xmpp": {:git, "https://github.com/processone/xmpp", "8071c86f33b9a8e9d66a10e058be088eecdc670b", [ref: "8071c86f33b9a8e9d66a10e058be088eecdc670b"]},
"yconf": {:git, "https://github.com/processone/yconf.git", "9898754f16cbd4585a1c2061d72fa441ecb2e938", [ref: "9898754f16cbd4585a1c2061d72fa441ecb2e938"]},
}

View file

@ -161,7 +161,7 @@
{"Get Pending","Виж чакащи"}.
{"Get User Last Login Time","Покажи времето, когато потребителят е влязъл за последно"}.
{"Get User Statistics","Покажи статистика за потребителя"}.
{"Given Name","Име"}.
{"Given Name","Наименование"}.
{"Grant voice to this person?","Предоставяне на глас за потребителя?"}.
{"has been banned","е със забранен достъп"}.
{"has been kicked because of a system shutdown","е отстранен поради изключване на системата"}.
@ -295,6 +295,7 @@
{"No pending subscriptions found","Не са намерени чакащи абонаменти"}.
{"No privacy list with this name found","Не е намерен списък за поверителност с това име"}.
{"No private data found in this query","Няма открити лични данни в тази заявка"}.
{"No <privileged_iq/> element found","Елементът <privileged_iq/> не е намерен"}.
{"No running node found","Не е намерен работещ нод"}.
{"No services available","Няма налични услуги"}.
{"No statistics found for this item","Не е налична статистика за този елемент"}.
@ -512,7 +513,7 @@
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Строфата ТРЯБВА да съдържа само един <active/> елемент, един <default/> елемент или един <list/> елемент"}.
{"The subscription identifier associated with the subscription request","Идентификаторът на абонамента, свързан със заявката за абонамент"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","URL адрес на XSL трансформацията, която може да се приложи към прикачените данни, за да се генерира подходящ елемент от тялото на съобщението."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","URL адрес на XSL трансформацията, която може да се приложи към формата на прикачените данни, за да се генерира валиден резултат от Data Forms, който клиентът може да покаже с помощта на общ механизъм за визуализация на Data Forms."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","URL адрес на XSL трансформацията, която може да се приложи към формата на прикачените данни, за да се генерира валиден резултат от Data Forms, който клиентът може да покаже с помощта на общ механизъм за визуализация на Data Forms"}.
{"There was an error changing the password: ","Възникна грешка при промяна на паролата: "}.
{"There was an error creating the account: ","Възникна грешка при създаването на профила: "}.
{"There was an error deleting the account: ","Възникна грешка при изтриването на профила: "}.
@ -537,6 +538,7 @@
{"Too many unacked stanzas","Твърде много непотвърдени строфи"}.
{"Too many users in this conference","Твърде много потребители в тази конференция"}.
{"Traffic rate limit is exceeded","Лимитът за трафик е надвишен"}.
{"~ts's MAM Archive","~ts's MAM архив"}.
{"~ts's Offline Messages Queue","Офлайн съобщения на ~ts"}.
{"Tuesday","Вторник"}.
{"Unable to generate a CAPTCHA","Не може да се генерира CAPTCHA"}.
@ -553,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","Актуализирай съобщението на деня на всички хостове (не изпращай)"}.
{"Update specs to get modules source, then install desired ones.","Актуализирайте спецификациите, за да получите източник на модули, след което инсталирайте желаните."}.
{"Update Specs","Актуализирай спецификациите"}.
{"Updating the vCard is not supported by the vCard storage backend","Актуализирането на vCard не се поддържа от настройката за съхранение на vCard"}.
{"Upgrade","Обнови"}.
{"URL for Archived Discussion Logs","URL адрес за дневници на архивирани дискусии"}.
{"User already exists","Потребителят вече съществува"}.
{"User (jid)","Потребител (jid)"}.
{"User JID","Потребител JID"}.
{"User Management","Управление на потребители"}.
{"User not allowed to perform an IQ set on another user's vCard.","Потребителят не може да извършва IQ настрийка на vCard на друг потребител."}.
{"User removed","Потребителят е премахнат"}.
{"User session not found","Потребителската сесия не е намерена"}.
{"User session terminated","Потребителската сесия е прекратена"}.

View file

@ -295,6 +295,7 @@
{"No pending subscriptions found","No s'han trobat subscripcions pendents"}.
{"No privacy list with this name found","No s'ha trobat cap llista de privacitat amb aquest nom"}.
{"No private data found in this query","No s'ha trobat dades privades en esta petició"}.
{"No <privileged_iq/> element found","No s'ha trobat cap element <privileged_iq/>"}.
{"No running node found","No s'ha trobat node en marxa"}.
{"No services available","No n'hi ha serveis disponibles"}.
{"No statistics found for this item","No n'hi ha estadístiques disponibles per a aquest element"}.
@ -537,6 +538,7 @@
{"Too many unacked stanzas","Massa missatges sense haver reconegut la seva recepció"}.
{"Too many users in this conference","N'hi ha massa usuaris en esta sala de conferència"}.
{"Traffic rate limit is exceeded","El límit de tràfic ha sigut sobrepassat"}.
{"~ts's MAM Archive","Arxiu MAM de ~ts"}.
{"~ts's Offline Messages Queue","~ts's cua de missatges offline"}.
{"Tuesday","Dimarts"}.
{"Unable to generate a CAPTCHA","No s'ha pogut generar un CAPTCHA"}.
@ -553,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","Actualitza el missatge del dia en tots els hosts (no enviar)"}.
{"Update specs to get modules source, then install desired ones.","Actualitza les especificacions per obtindre el codi font dels mòduls, després instal·la els que vulgues."}.
{"Update Specs","Actualitzar Especificacions"}.
{"Updating the vCard is not supported by the vCard storage backend","El sistema d'almacenament de vCard no te capacitat per a actualitzar la vCard"}.
{"Upgrade","Actualitza"}.
{"URL for Archived Discussion Logs","URL dels Arxius de Discussions"}.
{"User already exists","El usuari ja existeix"}.
{"User JID","JID del usuari"}.
{"User (jid)","Usuari (jid)"}.
{"User Management","Gestió d'Usuaris"}.
{"User not allowed to perform an IQ set on another user's vCard.","L'usuari no te permis per a modificar la vCard d'altre usuari."}.
{"User removed","Usuari borrat"}.
{"User session not found","Sessió d'usuari no trobada"}.
{"User session terminated","Sessió d'usuari terminada"}.

View file

@ -223,6 +223,7 @@
{"leaves the room","verlässt den Raum"}.
{"List of users with hats","Liste der Benutzer mit Funktionen"}.
{"List users with hats","Benutzer mit Funktionen auflisten"}.
{"Logged Out","Abgemeldet"}.
{"Logging","Protokollierung"}.
{"Make participants list public","Teilnehmerliste öffentlich machen"}.
{"Make room CAPTCHA protected","Raum mittels CAPTCHA schützen"}.
@ -293,6 +294,7 @@
{"No pending subscriptions found","Keine ausstehenden Abonnements gefunden"}.
{"No privacy list with this name found","Keine Privacy-Liste mit diesem Namen gefunden"}.
{"No private data found in this query","Keine privaten Daten in dieser Anfrage gefunden"}.
{"No <privileged_iq/> element found","Kein <privileged_iq/>-Element gefunden"}.
{"No running node found","Kein laufender Knoten gefunden"}.
{"No services available","Keine Dienste verfügbar"}.
{"No statistics found for this item","Keine Statistiken für dieses Item gefunden"}.
@ -532,6 +534,7 @@
{"Too many unacked stanzas","Zu viele unbestätigte Stanzas"}.
{"Too many users in this conference","Zu viele Benutzer in dieser Konferenz"}.
{"Traffic rate limit is exceeded","Datenratenlimit wurde überschritten"}.
{"~ts's MAM Archive","~ts's MAM Archiv"}.
{"~ts's Offline Messages Queue","Offline-Nachrichten-Warteschlange von ~ts"}.
{"Tuesday","Dienstag"}.
{"Unable to generate a CAPTCHA","Konnte kein CAPTCHA erstellen"}.
@ -588,6 +591,7 @@
{"Whether to allow subscriptions","Ob Abonnements erlaubt sind"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Ob alle Abonnements temporär gemacht werden sollen, basierend auf der Abonnentenpräsenz"}.
{"Whether to notify owners about new subscribers and unsubscribes","Ob Besitzer über neue Abonnenten und Abbestellungen benachrichtigt werden sollen"}.
{"Who can send private messages","Wer kann private Nachrichten senden"}.
{"Who may associate leaf nodes with a collection","Wer Blattknoten mit einer Sammlung verknüpfen darf"}.
{"Wrong parameters in the web formulary","Falsche Parameter im Webformular"}.
{"Wrong xmlns","Falscher xmlns"}.

View file

@ -295,6 +295,7 @@
{"No pending subscriptions found","No se han encontrado suscripciones pendientes"}.
{"No privacy list with this name found","No se ha encontrado una lista de privacidad con este nombre"}.
{"No private data found in this query","No se ha encontrado ningún elemento de dato privado en esta petición"}.
{"No <privileged_iq/> element found","No se encontró ningún elemento <privileged_iq/>"}.
{"No running node found","No se ha encontrado ningún nodo activo"}.
{"No services available","No hay servicios disponibles"}.
{"No statistics found for this item","No se han encontrado estadísticas para este elemento"}.
@ -537,6 +538,7 @@
{"Too many unacked stanzas","Demasiados mensajes sin haber reconocido recibirlos"}.
{"Too many users in this conference","Demasiados usuarios en esta sala"}.
{"Traffic rate limit is exceeded","Se ha excedido el límite de tráfico"}.
{"~ts's MAM Archive","Archivo MAM de ~ts"}.
{"~ts's Offline Messages Queue","Cola de mensajes diferidos de ~ts"}.
{"Tuesday","Martes"}.
{"Unable to generate a CAPTCHA","No se pudo generar un CAPTCHA"}.
@ -553,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","Actualizar el mensaje del día en todos los dominos (pero no enviarlo)"}.
{"Update specs to get modules source, then install desired ones.","Actualizar Especificaciones para conseguir el código fuente de los módulos, luego instala los que quieras."}.
{"Update Specs","Actualizar Especificaciones"}.
{"Updating the vCard is not supported by the vCard storage backend","La actualización de la vCard no es compatible con el vCard almacenamiento backend"}.
{"Upgrade","Actualizar"}.
{"URL for Archived Discussion Logs","URL del registro de discusiones archivadas"}.
{"User already exists","El usuario ya existe"}.
{"User JID","Jabber ID del usuario"}.
{"User (jid)","Usuario (jid)"}.
{"User Management","Administración de usuarios"}.
{"User not allowed to perform an IQ set on another user's vCard.","No se permite al usuario realizar un IQ establecido en la vCard de otro usuario."}.
{"User removed","Usuario eliminado"}.
{"User session not found","Sesión de usuario no encontrada"}.
{"User session terminated","Sesión de usuario terminada"}.

View file

@ -184,6 +184,7 @@
{"Incorrect value of 'action' attribute","Valeur de l'attribut 'action' incorrecte"}.
{"Incorrect value of 'action' in data form","Valeur de l'attribut 'action' incorrecte dans le formulaire"}.
{"Incorrect value of 'path' in data form","Valeur de l'attribut 'path' incorrecte dans le formulaire"}.
{"Install","Installer"}.
{"Insufficient privilege","Droits insuffisants"}.
{"Internal server error","Erreur interne du serveur"}.
{"Invalid 'from' attribute in forwarded message","L'attribut 'from' du message transféré est incorrect"}.
@ -480,12 +481,14 @@
{"Unauthorized","Non autorisé"}.
{"Unexpected action","Action inattendu"}.
{"Unexpected error condition: ~p","Condition derreur inattendue : ~p"}.
{"Uninstall","Désinstaller"}.
{"Unregister an XMPP account","Annuler lenregistrement dun compte XMPP"}.
{"Unregister","Désinscrire"}.
{"Unsupported <index/> element","Elément <index/> non supporté"}.
{"Unsupported version","Version non prise en charge"}.
{"Update message of the day (don't send)","Mise à jour du message du jour (pas d'envoi)"}.
{"Update message of the day on all hosts (don't send)","Mettre à jour le message du jour sur tous les domaines (ne pas envoyer)"}.
{"Upgrade","Mise à niveau"}.
{"URL for Archived Discussion Logs","URL des journaux de discussion archivés"}.
{"User already exists","L'utilisateur existe déjà"}.
{"User JID","JID de l'utilisateur"}.

View file

@ -168,6 +168,7 @@
{"has been kicked because of an affiliation change","è stato espulso a causa di un cambiamento di appartenenza"}.
{"has been kicked because the room has been changed to members-only","è stato espulso per la limitazione della stanza ai soli membri"}.
{"has been kicked","è stata/o espulsa/o"}.
{"Hash of the vCard-temp avatar of this room","Hash dell'avatar vCard-temp di questa stanza"}.
{"Hat title","Titolo del Cappello"}.
{"Hat URI","URI Cappello"}.
{"Hats limit exceeded","Limite di cappelli superato"}.
@ -294,6 +295,7 @@
{"No pending subscriptions found","Nessuna sottoscrizione in attesa trovata"}.
{"No privacy list with this name found","Nessun elenco di privacy con questo nome trovato"}.
{"No private data found in this query","Non sono stati trovati dati privati in questa query"}.
{"No <privileged_iq/> element found","Nessun elemento <privileged_iq/> trovato"}.
{"No running node found","Nessun nodo in esecuzione trovato"}.
{"No services available","Nessun servizio disponibile"}.
{"No statistics found for this item","Nessuna statistica trovata per questa voce"}.
@ -536,6 +538,7 @@
{"Too many unacked stanzas","Troppe stanze non riconosciute"}.
{"Too many users in this conference","Troppi utenti in questa conferenza"}.
{"Traffic rate limit is exceeded","Limite di traffico superato"}.
{"~ts's MAM Archive","Archivio MAM di ~ts"}.
{"~ts's Offline Messages Queue","La Coda dei Messaggi Offline di ~ts"}.
{"Tuesday","Martedì"}.
{"Unable to generate a CAPTCHA","Impossibile generare un CAPTCHA"}.
@ -552,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","Aggiornare il messaggio del giorno (MOTD) su tutti gli host (non inviarlo)"}.
{"Update specs to get modules source, then install desired ones.","Aggiorna le specifiche per ottenere il sorgente dei moduli, quindi installa quelli desiderati."}.
{"Update Specs","Aggiorna Specifiche"}.
{"Updating the vCard is not supported by the vCard storage backend","L'aggiornamento della vCard non è supportato dal backend di archiviazione vCard"}.
{"Upgrade","Aggiornamento"}.
{"URL for Archived Discussion Logs","URL per i Registri delle Discussioni Archiviati"}.
{"User already exists","L'utente esiste già"}.
{"User JID","JID utente"}.
{"User (jid)","Utente (jid)"}.
{"User Management","Gestione degli utenti"}.
{"User not allowed to perform an IQ set on another user's vCard.","L'utente non è autorizzato a eseguire un set IQ sulla vCard di un altro utente."}.
{"User removed","Utente rimosso"}.
{"User session not found","Sessione utente non trovata"}.
{"User session terminated","Sessione utente terminata"}.

View file

@ -295,6 +295,7 @@
{"No pending subscriptions found","Não foram encontradas subscrições"}.
{"No privacy list with this name found","Nenhuma lista de privacidade encontrada com este nome"}.
{"No private data found in this query","Nenhum dado privado encontrado nesta consulta"}.
{"No <privileged_iq/> element found","Nenhum elemento <privileged_iq/> foi encontrado"}.
{"No running node found","Nenhum nó em execução foi encontrado"}.
{"No services available","Não há serviços disponíveis"}.
{"No statistics found for this item","Não foram encontradas estatísticas para este item"}.
@ -537,6 +538,7 @@
{"Too many unacked stanzas","Número excessivo de instâncias sem confirmação"}.
{"Too many users in this conference","Há uma quantidade excessiva de usuários nesta conferência"}.
{"Traffic rate limit is exceeded","Limite de banda excedido"}.
{"~ts's MAM Archive","Arquivo ~ts's MAM"}.
{"~ts's Offline Messages Queue","~s's Fila de Mensagens Offline"}.
{"Tuesday","Terça"}.
{"Unable to generate a CAPTCHA","Impossível gerar um CAPTCHA"}.
@ -553,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","Atualizar a mensagem do dia em todos os host (não enviar)"}.
{"Update specs to get modules source, then install desired ones.","Atualize as especificações para obter a fonte dos módulos e instale os que desejar."}.
{"Update Specs","Atualizar as especificações"}.
{"Updating the vCard is not supported by the vCard storage backend","A atualização do vCard não é compatível com o back-end de armazenamento do vCard"}.
{"Upgrade","Atualização"}.
{"URL for Archived Discussion Logs","A URL para o arquivamento dos registros da discussão"}.
{"User already exists","Usuário já existe"}.
{"User (jid)","Usuário (jid)"}.
{"User JID","Usuário JID"}.
{"User Management","Gerenciamento de Usuários"}.
{"User not allowed to perform an IQ set on another user's vCard.","O usuário não tem permissão para executar um conjunto de QI no vCard de outro usuário."}.
{"User removed","O usuário foi removido"}.
{"User session not found","A sessão do usuário não foi encontrada"}.
{"User session terminated","Sessão de usuário terminada"}.

View file

@ -50,6 +50,7 @@
{"Changing role/affiliation is not allowed","Nuk lejohet ndryshim roli/përkatësie"}.
{"Channel already exists","Kanali ekziston tashmë"}.
{"Channel does not exist","Kanali sekziston"}.
{"Channel JID","JID Kanali"}.
{"Channels","Kanale"}.
{"Characters not allowed:","Shenja të palejuara:"}.
{"Chatroom configuration modified","Ndryshoi formësimi i dhomës së fjalosjeve"}.
@ -99,6 +100,8 @@
{"Full List of Room Admins","Listë e Plotë Përgjegjësish Dhome"}.
{"Full List of Room Owners","Listë e Plotë të Zotësh Dhome"}.
{"Full Name","Emër i Plotë"}.
{"Get List of Online Users","Merr Listë Përdoruesish Në Linjë"}.
{"Get List of Registered Users","Merr Listë Përdoruesish të Regjistruar"}.
{"Get Number of Online Users","Merr Numër Përdoruesish Në Linjë"}.
{"Get Number of Registered Users","Merr Numër Përdoruesish të Regjistruar"}.
{"Get User Statistics","Merr Statistika Përdoruesi"}.
@ -106,6 +109,7 @@
{"Grant voice to this person?","Ti akordohet zë këtij personi?"}.
{"has been banned","është dëbuar"}.
{"has been kicked","është përzënë"}.
{"Hat title","Titull kapeleje"}.
{"Host unknown","Strehë e panjohur"}.
{"HTTP File Upload","Ngarkim Kartelash HTTP"}.
{"Idle connection","Lidhje e plogësht"}.
@ -182,6 +186,7 @@
{"No node specified","Su përcaktua nyjë"}.
{"No pending subscriptions found","Su gjetën pajtime pezull"}.
{"No privacy list with this name found","Su gjet listë privatësie me atë emër"}.
{"No <privileged_iq/> element found","Su gjetën elementë <privileged_iq/>"}.
{"No running node found","Su gjet nyjë në funksionim"}.
{"No services available","Ska shërbime të gatshme"}.
{"No statistics found for this item","Su gjetën statistika për këtë objekt"}.
@ -191,6 +196,7 @@
{"Node index not found","Su gjet tregues nyje"}.
{"Node not found","Su gjet nyjë"}.
{"Node ~p","Nyjë ~p"}.
{"Node","Nyjë"}.
{"Nodes","Nyja"}.
{"None","Asnjë"}.
{"Not allowed","E palejuar"}.
@ -203,6 +209,7 @@
{"Number of online users","Numër përdoruesish në linjë"}.
{"Number of registered users","Numër përdoruesish të regjistruar"}.
{"Occupants are allowed to invite others","Të pranishmëve u është lejuar të ftojnë të tjerë"}.
{"Occupants are allowed to query others","Të pranishmëve u është lejuar tu bëjnë kërkim të tjerëve"}.
{"Occupants May Change the Subject","Të pranishmit Mund të Ndryshojnë Subjektin"}.
{"October","Tetor"}.
{"OK","OK"}.
@ -210,12 +217,14 @@
{"Online Users","Përdorues Në Linjë"}.
{"Online","Në linjë"}.
{"Only deliver notifications to available users","Dorëzo njoftime vetëm te përdoruesit e pranishëm"}.
{"Only moderators are allowed to retract messages","Vetëm të moderatorëve u lejohet të tërheqin mbrapsht mesazhe"}.
{"Only occupants are allowed to send messages to the conference","Vetëm të pranishmëve u lejohet të dërgojnë mesazhe te konferenca"}.
{"Only publishers may publish","Vetëm botuesit mund të botojnë"}.
{"Organization Name","Emër Enti"}.
{"Organization Unit","Njësi Organizative"}.
{"Outgoing s2s Connections","Lidhje s2s Ikëse"}.
{"Owner privileges required","Lypset privilegje të zoti"}.
{"Participant ID","ID Pjesëmarrësi"}.
{"Participant","Pjesëmarrës"}.
{"Password Verification","Verifikim Fjalëkalimi"}.
{"Password Verification:","Verifikim Fjalëkalimi:"}.
@ -266,6 +275,7 @@
{"Specify the access model","Specifikoni model hyrjeje"}.
{"Specify the event message type","Përcaktoni llojin e mesazhit për aktin"}.
{"Specify the publisher model","Përcaktoni model botuesi"}.
{"Stanza id is not valid","ID Stanza sështë i vlefshëm"}.
{"Stopped Nodes","Nyja të Ndalura"}.
{"Subject","Subjekti"}.
{"Submitted","Parashtruar"}.
@ -277,6 +287,7 @@
{"The default language of the node","Gjuha parazgjedhje e nyjës"}.
{"The feature requested is not supported by the conference","Veçoria e kërkuar nuk mbulohen nga konferenca"}.
{"The JID of the node creator","JID i krijjuesit të nyjës"}.
{"The list of all online users","Lista e krejt përdoruesve në linjë"}.
{"The name of the node","Emri i nyjës"}.
{"The number of subscribers to the node","Numri i pajtimtarëve te nyja"}.
{"The number of unread or undelivered messages","Numri i mesazheve të palexuar ose të padorëzuar"}.
@ -303,6 +314,7 @@
{"Unregister an XMPP account","Çregjistroni një llogari XMPP"}.
{"Unregister","Çregjistrohuni"}.
{"Unsupported version","Version i pambuluar"}.
{"Updating the vCard is not supported by the vCard storage backend","Përditësimi i vCard-it nuk mbulohet nga mekanizmi i depozitimit të vCard-ve"}.
{"User already exists","Ka tashmë një përdorues të tillë"}.
{"User JID","JID përdoruesi"}.
{"User (jid)","Përdorues (jid)"}.
@ -319,9 +331,11 @@
{"Wednesday","E mërkurë"}.
{"When a new subscription is processed","Kur përpunohet një pajtim i ri"}.
{"Whether to allow subscriptions","Nëse duhen lejuar apo jo pajtime"}.
{"Who can send private messages","Cilët mund të dërgojnë mesazhe private"}.
{"Wrong parameters in the web formulary","Parametër i gabuar në formular web"}.
{"XMPP Account Registration","Regjistrim Llogarish XMPP"}.
{"XMPP Domains","Përkatësi XMPP"}.
{"You are not allowed to send private messages","Skeni leje të dërgoni mesazhe private"}.
{"You are not joined to the channel","Skeni hyrë te kanali"}.
{"You have been banned from this room","Jeni dëbuar prej kësaj dhome"}.
{"You have joined too many conferences","Keni hyrë në shumë konferenca"}.

View file

@ -295,6 +295,7 @@
{"No pending subscriptions found","未找到待处理的订阅"}.
{"No privacy list with this name found","未找到具有此名称的隐私列表"}.
{"No private data found in this query","在此查询中找不到专用数据"}.
{"No <privileged_iq/> element found","未找到 <privileged_iq/> 元素"}.
{"No running node found","找不到正在运行的节点"}.
{"No services available","无可用服务"}.
{"No statistics found for this item","未找到此项目的统计数据"}.
@ -537,6 +538,7 @@
{"Too many unacked stanzas","太多未确认的节"}.
{"Too many users in this conference","此群聊中的用户太多"}.
{"Traffic rate limit is exceeded","超过流量速率限制"}.
{"~ts's MAM Archive","~ts 的 MAM 存档"}.
{"~ts's Offline Messages Queue","~ts 的离线消息队列"}.
{"Tuesday","周二"}.
{"Unable to generate a CAPTCHA","无法生成验证码"}.
@ -553,12 +555,14 @@
{"Update message of the day on all hosts (don't send)","更新所有主机上的每日消息(不发送)"}.
{"Update specs to get modules source, then install desired ones.","更新规格以获取模块源,然后安装所需的模块。"}.
{"Update Specs","更新规格"}.
{"Updating the vCard is not supported by the vCard storage backend","vCard 存储后端不支持更新 vCard"}.
{"Upgrade","升级"}.
{"URL for Archived Discussion Logs","存档讨论日志的 URL"}.
{"User already exists","用户已存在"}.
{"User (jid)","用户 (jid)"}.
{"User JID","用户 JID"}.
{"User Management","用户管理"}.
{"User not allowed to perform an IQ set on another user's vCard.","不允许用户在其他用户的 vCard 上执行 IQ 设置。"}.
{"User removed","用户已移除"}.
{"User session not found","用户会话未找到"}.
{"User session terminated","用户会话已终止"}.

View file

@ -77,7 +77,7 @@
{stringprep, "~> 1.0.29", {git, "https://github.com/processone/stringprep", {tag, "1.0.30"}}},
{if_var_true, stun,
{stun, "~> 1.2.12", {git, "https://github.com/processone/stun", {tag, "1.2.15"}}}},
{xmpp, "~> 1.9.0", {git, "https://github.com/processone/xmpp", {tag, "1.9.0"}}},
{xmpp, "~> 1.9.0", {git, "https://github.com/processone/xmpp", "8071c86f33b9a8e9d66a10e058be088eecdc670b"}},
{yconf, ".*", {git, "https://github.com/processone/yconf", "9898754f16cbd4585a1c2061d72fa441ecb2e938"}}
]}.

View file

@ -10,7 +10,7 @@
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.53">>},0},
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.37">>},0},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0},
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.2">>},1},
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.2">>},0},
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.10">>},0},
{<<"luerl">>,{pkg,<<"luerl">>,<<"1.2.0">>},0},
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.17">>},0},
@ -24,7 +24,10 @@
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.30">>},0},
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.15">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.9.0">>},0},
{<<"xmpp">>,
{git,"https://github.com/processone/xmpp",
{ref,"8071c86f33b9a8e9d66a10e058be088eecdc670b"}},
0},
{<<"yconf">>,
{git,"https://github.com/processone/yconf",
{ref,"9898754f16cbd4585a1c2061d72fa441ecb2e938"}},
@ -55,8 +58,7 @@
{<<"sqlite3">>, <<"E819DEFD280145C328457D7AF897D2E45E8E5270E18812EE30B607C99CDD21AF">>},
{<<"stringprep">>, <<"46CF0FF631B3E7328F61F20B454D59428D87738F25D709798B5DCBB9B83C23F1">>},
{<<"stun">>, <<"EEC510AF6509201FF97F1F2C87B7977C833BF29C04E985383370EC21F04E4CCF">>},
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>},
{<<"xmpp">>, <<"D92446BF51D36ADDA02DB63B963FE6D4A1EDE33E59B38A43D9B90AFD20C25B74">>}]},
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]},
{pkg_hash_ext,[
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
{<<"cache_tab">>, <<"8582B60A4A09B247EF86355BA9E07FCE9E11EDC0345A775C9171F971C72B6351">>},
@ -82,6 +84,5 @@
{<<"sqlite3">>, <<"3C0BA4E13322C2AD49DE4E2DDD28311366ADDE54BEAE8DBA9D9E3888F69D2857">>},
{<<"stringprep">>, <<"F6FC9B3384A03877830F89B2F38580CAF3F4A27448A4A333D6A8C3975C220B9A">>},
{<<"stun">>, <<"F6D8A541A29FD13F2CE658B676C0CC661262B96E045B52DEF1644B75EBC0EDEF">>},
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>},
{<<"xmpp">>, <<"C1B91BE74A9A9503AFA6766F756477516920FFBFEEA0C260C2FA171355F53C27">>}]}
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]}
].

View file

@ -175,7 +175,7 @@ get_commands_spec() ->
"announcement quoted, for example: \n"
"`ejabberdctl evacuate_kindly 60 "
"\\\"The server will stop in one minute.\\\"`",
note = "added in 24.xx",
note = "added in 24.12",
module = ?MODULE, function = evacuate_kindly,
args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
args_example = [60, <<"Server will stop now.">>],

View file

@ -45,7 +45,8 @@
handle_unbinded_packet/2, inline_stream_features/1,
handle_sasl2_inline/2, handle_sasl2_inline_post/3,
handle_bind2_inline/2, handle_bind2_inline_post/3, sasl_options/1,
handle_sasl2_task_next/4, handle_sasl2_task_data/3]).
handle_sasl2_task_next/4, handle_sasl2_task_data/3,
get_fast_tokens_fun/2, fast_mechanisms/1]).
%% Hooks
-export([handle_unexpected_cast/2, handle_unexpected_call/3,
process_auth_result/3, reject_unauthenticated_packet/2,
@ -465,6 +466,20 @@ check_password_digest_fun(_Mech, #{lserver := LServer}) ->
ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P, D, DG)
end.
get_fast_tokens_fun(_Mech, #{lserver := LServer}) ->
fun(User, UA) ->
case gen_mod:is_loaded(LServer, mod_auth_fast) of
false -> false;
_ -> mod_auth_fast:get_tokens(LServer, User, UA)
end
end.
fast_mechanisms(#{lserver := LServer}) ->
case gen_mod:is_loaded(LServer, mod_auth_fast) of
false -> [];
_ -> mod_auth_fast:get_mechanisms(LServer)
end.
bind(<<"">>, State) ->
bind(new_uniq_id(), State);
bind(R, #{user := U, server := S, access := Access, lang := Lang,

View file

@ -1146,7 +1146,7 @@ doc() ->
"or 'ram' if the latter is not set.")}},
{redis_server,
#{value => "Host | IP Address | Unix Socket Path",
note => "improved in 24.xx",
note => "improved in 24.12",
desc =>
?T("A hostname, IP address or unix domain socket file of the "
"_`database.md#redis|Redis`_ server. "

View file

@ -1034,8 +1034,8 @@ get_commands_spec() ->
desc = "List all established sessions",
policy = admin,
module = ?MODULE, function = connected_users, args = [],
result_desc = "List of users sessions",
result_example = [<<"user1@example.com">>, <<"user2@example.com">>],
result_desc = "List of users sessions full JID",
result_example = [<<"user1@example.com/Home">>, <<"user2@example.com/54134">>],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number, tags = [session, statistics],
desc = "Get the number of established sessions",

View file

@ -238,7 +238,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) ->
{SJID, Pass} ->
{HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
try jid:decode(SJID) of
#jid{user = <<"">>, server = User} ->
#jid{luser = <<"">>, lserver = User} ->
case ejabberd_router:is_my_host(HostHTTP) of
true ->
get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
@ -246,7 +246,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) ->
_ ->
{unauthorized, <<"missing-server">>}
end;
#jid{user = User, server = Server} ->
#jid{luser = User, lserver = Server} ->
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass)
catch _:{bad_jid, _} ->
@ -2286,7 +2286,7 @@ make_result(Binary,
Second = proplists:get_value(second, ArgumentsUsed),
FirstUrlencoded =
list_to_binary(string:replace(
misc:url_encode(First), "%40", "@")),
misc:url_encode(First), "%40", "@")),
{GroupId, Host} =
case jid:decode(FirstUrlencoded) of
#jid{luser = <<"">>, server = G} ->

View file

@ -43,7 +43,8 @@
% Sessions
num_resources/2, resource_num/3,
kick_session/4, status_num/2, status_num/1,
status_list/2, status_list/1, connected_users_info/0,
status_list/2, status_list_v3/2,
status_list/1, status_list_v3/1, connected_users_info/0,
connected_users_vhost/1, set_presence/7,
get_presence/2, user_sessions_info/2, get_last/2, set_last/4,
@ -380,6 +381,21 @@ get_commands_spec() ->
{status, string}
]}}
}}},
#ejabberd_commands{name = status_list_host, tags = [session],
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list_v3,
version = 3,
note = "updated in 24.12",
args = [{host, binary}, {status, binary}],
args_example = [<<"myserver.com">>, <<"dnd">>],
args_desc = ["Server name", "Status type to check"],
result_example = [{<<"peter@myserver.com/tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [{jid, string},
{priority, integer},
{status, string}
]}}
}}},
#ejabberd_commands{name = status_list, tags = [session],
desc = "List of logged users with this status",
module = ?MODULE, function = status_list,
@ -396,6 +412,21 @@ get_commands_spec() ->
{status, string}
]}}
}}},
#ejabberd_commands{name = status_list, tags = [session],
desc = "List of logged users with this status",
module = ?MODULE, function = status_list_v3,
version = 3,
note = "updated in 24.12",
args = [{status, binary}],
args_example = [<<"dnd">>],
args_desc = ["Status type to check"],
result_example = [{<<"peter@myserver.com/tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [{jid, string},
{priority, integer},
{status, string}
]}}
}}},
#ejabberd_commands{name = connected_users_info,
tags = [session],
desc = "List all established sessions and their information",
@ -426,8 +457,9 @@ get_commands_spec() ->
module = ?MODULE, function = connected_users_vhost,
args_example = [<<"myexample.com">>],
args_desc = ["Server name"],
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
args = [{host, binary}],
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
result_desc = "List of sessions full JIDs",
result = {connected_users_vhost, {list, {sessions, string}}}},
#ejabberd_commands{name = user_sessions_info,
tags = [session],
@ -683,6 +715,7 @@ get_commands_spec() ->
module = ?MODULE, function = get_roster,
args = [],
args_rename = [{server, host}],
result_example = [{<<"user2@localhost">>, <<"User 2">>, <<"none">>, <<"subscribe">>, [<<"Group1">>]}],
result = {contacts, {list, {contact, {tuple, [
{jid, string},
{nick, string},
@ -696,6 +729,7 @@ get_commands_spec() ->
policy = user,
module = ?MODULE, function = get_roster_count,
args = [],
args_example = [<<"sun">>, <<"localhost">>],
args_rename = [{server, host}],
result_example = 5,
result_desc = "Number",
@ -1333,6 +1367,14 @@ status_list(Host, Status) ->
status_list(Status) ->
status_list(<<"all">>, Status).
status_list_v3(ArgHost, Status) ->
List = status_list(ArgHost, Status),
[{jid:encode(jid:make(User, Host, Resource)), Priority, StatusText}
|| {User, Host, Resource, Priority, StatusText} <- List].
status_list_v3(Status) ->
status_list_v3(<<"all">>, Status).
get_status_list(Host, Status_required) ->
%% Get list of all logged users
@ -2283,9 +2325,9 @@ web_page_node(_, Node, #request{path = [<<"stats">>]} = R) ->
ejabberd_web_admin,
make_command,
[stats, R, [{<<"name">>, <<"uptimeseconds">>}], [{only, value}]]),
UpDaysBin = integer_to_binary(
binary_to_integer(fxml:get_tag_cdata(UpSecs))
div 86400), % 24*60*60
UpDaysBin =
integer_to_binary(binary_to_integer(fxml:get_tag_cdata(UpSecs))
div 86400), % 24*60*60
UpDays =
#xmlel{name = <<"code">>,
attrs = [],

167
src/mod_auth_fast.erl Normal file
View file

@ -0,0 +1,167 @@
%%%-------------------------------------------------------------------
%%% File : mod_auth_fast.erl
%%% Author : Pawel Chmielowski <pawel@process-one.net>
%%% Created : 1 Dec 2024 by Pawel Chmielowski <pawel@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2024 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(mod_auth_fast).
-behaviour(gen_mod).
-protocol({xep, 484, '0.2.0', '24.12', "complete", ""}).
%% gen_mod API
-export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]).
-export([mod_doc/0]).
%% Hooks
-export([c2s_inline_features/2, c2s_handle_sasl2_inline/1,
get_tokens/3, get_mechanisms/1]).
-include_lib("xmpp/include/xmpp.hrl").
-include_lib("xmpp/include/scram.hrl").
-include("logger.hrl").
-include("translate.hrl").
-callback get_tokens(binary(), binary(), binary()) ->
[{current | next, binary(), non_neg_integer()}].
-callback rotate_token(binary(), binary(), binary()) ->
ok | {error, atom()}.
-callback del_token(binary(), binary(), binary(), current | next) ->
ok | {error, atom()}.
-callback set_token(binary(), binary(), binary(), current | next, binary(), non_neg_integer()) ->
ok | {error, atom()}.
%%%===================================================================
%%% API
%%%===================================================================
-spec start(binary(), gen_mod:opts()) -> {ok, [gen_mod:registration()]}.
start(Host, Opts) ->
Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(Host, Opts),
{ok, [{hook, c2s_inline_features, c2s_inline_features, 50},
{hook, c2s_handle_sasl2_inline, c2s_handle_sasl2_inline, 10}]}.
-spec stop(binary()) -> ok.
stop(_Host) ->
ok.
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
if NewMod /= OldMod ->
NewMod:init(Host, NewOpts);
true ->
ok
end,
ok.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
-spec mod_opt_type(atom()) -> econf:validator().
mod_opt_type(db_type) ->
econf:db_type(?MODULE);
mod_opt_type(token_lifetime) ->
econf:timeout(second);
mod_opt_type(token_refresh_age) ->
econf:timeout(second).
-spec mod_options(binary()) -> [{atom(), any()}].
mod_options(Host) ->
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{token_lifetime, 30*24*60*60},
{token_refresh_age, 24*60*60}].
mod_doc() ->
#{desc =>
[?T("The module adds support for "
"https://xmpp.org/extensions/xep-0484.html"
"[XEP-0480: Fast Authentication Streamlining Tokens] that allows users to authenticate "
"using self managed tokens.")],
note => "added in 24.12",
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{token_lifetime,
#{value => "timeout()",
desc => ?T("Time that tokens will be keept, measured from it's creation time. "
"Default value set to 30 days")}},
{token_refresh_age,
#{value => "timeout()",
desc => ?T("This time determines age of token, that qualifies for automatic refresh. "
"Default value set to 1 day")}}],
example =>
["modules:",
" mod_auth_fast:",
" token_timeout: 14days"]}.
get_mechanisms(_LServer) ->
[<<"HT-SHA-256-NONE">>, <<"HT-SHA-256-UNIQ">>, <<"HT-SHA-256-EXPR">>, <<"HT-SHA-256-ENDP">>].
ua_hash(UA) ->
crypto:hash(sha256, UA).
get_tokens(LServer, LUser, UA) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
ToRefresh = erlang:system_time(second) - mod_auth_fast_opt:token_refresh_age(LServer),
lists:map(
fun({Type, Token, CreatedAt}) ->
{{Type, CreatedAt < ToRefresh}, Token}
end, Mod:get_tokens(LServer, LUser, ua_hash(UA))).
c2s_inline_features({Sasl, Bind, Extra}, Host) ->
{Sasl ++ [#fast{mechs = get_mechanisms(Host)}], Bind, Extra}.
gen_token(#{sasl2_ua_id := UA, server := Server, user := User}) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
Token = base64:encode(ua_hash(<<UA/binary, (p1_rand:get_string())/binary>>)),
ExpiresAt = erlang:system_time(second) + mod_auth_fast_opt:token_lifetime(Server),
Mod:set_token(Server, User, ua_hash(UA), next, Token, ExpiresAt),
#fast_token{token = Token, expiry = misc:usec_to_now(ExpiresAt*1000000)}.
c2s_handle_sasl2_inline({#{server := Server, user := User, sasl2_ua_id := UA,
sasl2_axtra_auth_info := Extra} = State, Els, Results} = Acc) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
NeedRegen =
case Extra of
{token, {next, Rotate}} ->
Mod:rotate_token(Server, User, ua_hash(UA)),
Rotate;
{token, {_, true}} ->
true;
_ ->
false
end,
case {lists:keyfind(fast_request_token, 1, Els), lists:keyfind(fast, 1, Els)} of
{#fast_request_token{mech = _Mech}, #fast{invalidate = true}} ->
Mod:del_token(Server, User, ua_hash(UA), current),
{State, Els, [gen_token(State) | Results]};
{_, #fast{invalidate = true}} ->
Mod:del_token(Server, User, ua_hash(UA), current),
Acc;
{#fast_request_token{mech = _Mech}, _} ->
{State, Els, [gen_token(State) | Results]};
_ when NeedRegen ->
{State, Els, [gen_token(State) | Results]};
_ ->
Acc
end.

View file

@ -0,0 +1,123 @@
%%%-------------------------------------------------------------------
%%% File : mod_auth_fast_mnesia.erl
%%% Author : Pawel Chmielowski <pawel@process-one.net>
%%% Created : 1 Dec 2024 by Pawel Chmielowski <pawel@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2024 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_auth_fast_mnesia).
-behaviour(mod_auth_fast).
%% API
-export([init/2]).
-export([get_tokens/3, del_token/4, set_token/6, rotate_token/3]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-record(mod_auth_fast, {key = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary()} | '$1',
token = <<>> :: binary() | '_',
created_at = 0 :: non_neg_integer() | '_',
expires_at = 0 :: non_neg_integer() | '_'}).
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, mod_auth_fast,
[{disc_only_copies, [node()]},
{attributes,
record_info(fields, mod_auth_fast)}]).
-spec get_tokens(binary(), binary(), binary()) ->
[{current | next, binary(), non_neg_integer()}].
get_tokens(LServer, LUser, UA) ->
Now = erlang:system_time(second),
case mnesia:dirty_read(mod_auth_fast, {LServer, LUser, token_id(UA, next)}) of
[#mod_auth_fast{token = Token, created_at = Created, expires_at = Expires}] when Expires > Now ->
[{next, Token, Created}];
[#mod_auth_fast{}] ->
del_token(LServer, LUser, UA, next),
[];
_ ->
[]
end ++
case mnesia:dirty_read(mod_auth_fast, {LServer, LUser, token_id(UA, current)}) of
[#mod_auth_fast{token = Token, created_at = Created, expires_at = Expires}] when Expires > Now ->
[{current, Token, Created}];
[#mod_auth_fast{}] ->
del_token(LServer, LUser, UA, current),
[];
_ ->
[]
end.
-spec rotate_token(binary(), binary(), binary()) ->
ok | {error, atom()}.
rotate_token(LServer, LUser, UA) ->
F = fun() ->
case mnesia:dirty_read(mod_auth_fast, {LServer, LUser, token_id(UA, next)}) of
[#mod_auth_fast{token = Token, created_at = Created, expires_at = Expires}] ->
mnesia:write(#mod_auth_fast{key = {LServer, LUser, token_id(UA, current)},
token = Token, created_at = Created,
expires_at = Expires}),
mnesia:delete({mod_auth_fast, {LServer, LUser, token_id(UA, next)}});
_ ->
ok
end
end,
transaction(F).
-spec del_token(binary(), binary(), binary(), current | next) ->
ok | {error, atom()}.
del_token(LServer, LUser, UA, Type) ->
F = fun() ->
mnesia:delete({mod_auth_fast, {LServer, LUser, token_id(UA, Type)}})
end,
transaction(F).
-spec set_token(binary(), binary(), binary(), current | next, binary(), non_neg_integer()) ->
ok | {error, atom()}.
set_token(LServer, LUser, UA, Type, Token, Expires) ->
F = fun() ->
mnesia:write(#mod_auth_fast{key = {LServer, LUser, token_id(UA, Type)},
token = Token, created_at = erlang:system_time(second),
expires_at = Expires})
end,
transaction(F).
%%%===================================================================
%%% Internal functions
%%%===================================================================
token_id(UA, current) ->
<<"c:", UA/binary>>;
token_id(UA, _) ->
<<"n:", UA/binary>>.
transaction(F) ->
case mnesia:transaction(F) of
{atomic, Res} ->
Res;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.

27
src/mod_auth_fast_opt.erl Normal file
View file

@ -0,0 +1,27 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_auth_fast_opt).
-export([db_type/1]).
-export([token_lifetime/1]).
-export([token_refresh_age/1]).
-spec db_type(gen_mod:opts() | global | binary()) -> atom().
db_type(Opts) when is_map(Opts) ->
gen_mod:get_opt(db_type, Opts);
db_type(Host) ->
gen_mod:get_module_opt(Host, mod_auth_fast, db_type).
-spec token_lifetime(gen_mod:opts() | global | binary()) -> pos_integer().
token_lifetime(Opts) when is_map(Opts) ->
gen_mod:get_opt(token_lifetime, Opts);
token_lifetime(Host) ->
gen_mod:get_module_opt(Host, mod_auth_fast, token_lifetime).
-spec token_refresh_age(gen_mod:opts() | global | binary()) -> pos_integer().
token_refresh_age(Opts) when is_map(Opts) ->
gen_mod:get_opt(token_refresh_age, Opts);
token_refresh_age(Host) ->
gen_mod:get_module_opt(Host, mod_auth_fast, token_refresh_age).

View file

@ -31,7 +31,7 @@
-export([start/2, stop/1, reload/3, process/2, depends/2,
format_arg/2,
mod_options/1, mod_doc/0]).
mod_opt_type/1, mod_options/1, mod_doc/0]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
@ -201,19 +201,25 @@ extract_args(Data) ->
maps:to_list(Maps).
% get API version N from last "vN" element in URL path
get_api_version(#request{path = Path}) ->
get_api_version(lists:reverse(Path));
get_api_version([<<"v", String/binary>> | Tail]) ->
get_api_version(#request{path = Path, host = Host}) ->
get_api_version(lists:reverse(Path), Host).
get_api_version([<<"v", String/binary>> | Tail], Host) ->
case catch binary_to_integer(String) of
N when is_integer(N) ->
N;
_ ->
get_api_version(Tail)
get_api_version(Tail, Host)
end;
get_api_version([_Head | Tail]) ->
get_api_version(Tail);
get_api_version([]) ->
?DEFAULT_API_VERSION.
get_api_version([_Head | Tail], Host) ->
get_api_version(Tail, Host);
get_api_version([], Host) ->
try mod_http_api_opt:default_version(Host)
catch error:{module_not_loaded, ?MODULE, Host} ->
?WARNING_MSG("Using module ~p for host ~s, but it isn't configured "
"in the configuration file", [?MODULE, Host]),
?DEFAULT_API_VERSION
end.
%% ----------------
%% command handlers
@ -549,8 +555,24 @@ hide_sensitive_args(Args=[_H|_T]) ->
hide_sensitive_args(NonListArgs) ->
NonListArgs.
mod_opt_type(default_version) ->
econf:either(
econf:int(0, 3),
econf:and_then(
econf:binary(),
fun(Binary) ->
case binary_to_list(Binary) of
F when F >= "24.06" ->
2;
F when (F > "23.10") and (F < "24.06") ->
1;
F when F =< "23.10" ->
0
end
end)).
mod_options(_) ->
[].
[{default_version, ?DEFAULT_API_VERSION}].
mod_doc() ->
#{desc =>
@ -565,6 +587,15 @@ mod_doc() ->
"For example: '/api/v2: mod_http_api'."), "",
?T("To run a command, send a POST request to the corresponding "
"URL: 'http://localhost:5280/api/COMMAND-NAME'")],
opts =>
[{default_version,
#{value => "integer() | string()",
desc =>
?T("What API version to use when none is specified in the URL path. "
"If setting an ejabberd version, it will use the latest API "
"version that was available in that ejabberd version. "
"For example, setting '\"24.06\"' in this option implies '2'. "
"The default value is the latest version.")}}],
example =>
["listen:",
" -",
@ -574,4 +605,5 @@ mod_doc() ->
" /api: mod_http_api",
"",
"modules:",
" mod_http_api: {}"]}.
" mod_http_api:",
" default_version: 2"]}.

View file

@ -3,11 +3,11 @@
-module(mod_http_api_opt).
-export([admin_ip_access/1]).
-export([default_version/1]).
-spec admin_ip_access(gen_mod:opts() | global | binary()) -> 'none' | acl:acl().
admin_ip_access(Opts) when is_map(Opts) ->
gen_mod:get_opt(admin_ip_access, Opts);
admin_ip_access(Host) ->
gen_mod:get_module_opt(Host, mod_http_api, admin_ip_access).
-spec default_version(gen_mod:opts() | global | binary()) -> any().
default_version(Opts) when is_map(Opts) ->
gen_mod:get_opt(default_version, Opts);
default_version(Host) ->
gen_mod:get_module_opt(Host, mod_http_api, default_version).

View file

@ -994,15 +994,38 @@ iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
#rsm_set{max = Max} ->
Max
end,
{Items, HitMax} = lists:foldr(
fun(_, {Acc, _}) when length(Acc) >= MaxItems ->
{Acc, true};
(R, {Acc, _}) ->
case get_room_disco_item(R, Query) of
{ok, Item} -> {[Item | Acc], false};
{error, _} -> {Acc, false}
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RsmSupported = RMod:rsm_supported(),
GetRooms =
fun GetRooms(AccInit, Rooms) ->
{Items, HitMax, DidSkip, Last, First} = lists:foldr(
fun(_, {Acc, _, Skip, F, L}) when length(Acc) >= MaxItems ->
{Acc, true, Skip, F, L};
({RN, _, _} = R, {Acc, _, Skip, F, _}) ->
F2 = if F == undefined -> RN; true -> F end,
case get_room_disco_item(R, Query) of
{ok, Item} -> {[Item | Acc], false, Skip, F2, RN};
{error, _} -> {Acc, false, true, F2, RN}
end
end, AccInit, Rooms),
if RsmSupported andalso not HitMax andalso DidSkip ->
RSM2 = case RSM of
#rsm_set{'after' = undefined, before = undefined} ->
#rsm_set{max = MaxItems - length(Items), 'after' = Last};
#rsm_set{'after' = undefined} ->
#rsm_set{max = MaxItems - length(Items), 'before' = First};
_ ->
#rsm_set{max = MaxItems - length(Items), 'after' = Last}
end,
GetRooms({Items, false, false, undefined, undefined},
get_online_rooms(ServerHost, Host, RSM2));
true -> {Items, HitMax}
end
end, {[], false}, get_online_rooms(ServerHost, Host, RSM)),
end,
{Items, HitMax} =
GetRooms({[], false, false, undefined, undefined},
get_online_rooms(ServerHost, Host, RSM)),
ResRSM = case Items of
[_|_] when RSM /= undefined; HitMax ->
#disco_item{jid = #jid{luser = First}} = hd(Items),

View file

@ -30,7 +30,8 @@
-export([start/2, stop/1, reload/3, depends/2, mod_doc/0,
muc_online_rooms/1, muc_online_rooms_by_regex/2,
muc_register_nick/3, muc_unregister_nick/2,
muc_register_nick/3, muc_register_nick/4,
muc_unregister_nick/2, muc_unregister_nick/3,
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
@ -38,9 +39,11 @@
get_user_rooms/2, get_user_subscriptions/2, get_room_occupants/2,
get_room_occupants_number/2, send_direct_invitation/5,
change_room_option/4, get_room_options/2,
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
subscribe_room/4, subscribe_room_many/3,
unsubscribe_room/2, get_subscribers/2,
set_room_affiliation/4, set_room_affiliation/5, get_room_affiliations/2,
get_room_affiliations_v3/2, get_room_affiliation/3,
subscribe_room/4, subscribe_room/6,
subscribe_room_many/3, subscribe_room_many_v3/4,
unsubscribe_room/2, unsubscribe_room/4, get_subscribers/2,
get_room_serverhost/1,
web_menu_main/2, web_page_main/2,
web_menu_host/3, web_page_host/3,
@ -102,7 +105,7 @@ get_commands_spec() ->
module = ?MODULE, function = muc_online_rooms,
args_desc = ["MUC service, or `global` for all"],
args_example = ["conference.example.com"],
result_desc = "List of rooms",
result_desc = "List of rooms JIDs",
result_example = ["room1@conference.example.com", "room2@conference.example.com"],
args = [{service, binary}],
args_rename = [{host, service}],
@ -133,6 +136,16 @@ get_commands_spec() ->
args = [{nick, binary}, {jid, binary}, {service, binary}],
args_rename = [{host, service}],
result = {res, rescode}},
#ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick to a User JID in a MUC service",
module = ?MODULE, function = muc_register_nick,
version = 3,
note = "updated in 24.12",
args_desc = ["nick", "user name", "user host", "MUC service"],
args_example = [<<"Tim">>, <<"tim">>, <<"example.org">>, <<"conference.example.org">>],
args = [{nick, binary}, {user, binary}, {host, binary}, {service, binary}],
args_rename = [{host, service}],
result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
desc = "Unregister the nick registered by that account in the MUC service",
module = ?MODULE, function = muc_unregister_nick,
@ -141,26 +154,38 @@ get_commands_spec() ->
args = [{jid, binary}, {service, binary}],
args_rename = [{host, service}],
result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
desc = "Unregister the nick registered by that account in the MUC service",
module = ?MODULE, function = muc_unregister_nick,
version = 3,
note = "updated in 24.12",
args_desc = ["user name", "user host", "MUC service"],
args_example = [<<"tim">>, <<"example.org">>, <<"conference.example.org">>],
args = [{user, binary}, {host, binary}, {service, binary}],
args_rename = [{host, service}],
result = {res, rescode}},
#ejabberd_commands{name = create_room, tags = [muc_room],
desc = "Create a MUC room name@service in host",
module = ?MODULE, function = create_room,
args_desc = ["Room name", "MUC service", "Server host"],
args_example = ["room1", "conference.example.com", "example.com"],
args = [{name, binary}, {service, binary},
args = [{room, binary}, {service, binary},
{host, binary}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_room, tags = [muc_room],
desc = "Destroy a MUC room",
module = ?MODULE, function = destroy_room,
args_desc = ["Room name", "MUC service"],
args_example = ["room1", "conference.example.com"],
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = create_rooms_file, tags = [muc],
desc = "Create the rooms indicated in file",
longdesc = "Provide one room JID per line. Rooms will be created after restart.",
note = "improved in 24.xx",
note = "improved in 24.12",
module = ?MODULE, function = create_rooms_file,
args_desc = ["Path to the text file with one room JID per line"],
args_example = ["/home/ejabberd/rooms.txt"],
@ -177,7 +202,7 @@ get_commands_spec() ->
[{"members_only","true"},
{"affiliations", "owner:bob@example.com,member:peter@example.com"},
{"subscribers", "bob@example.com:Bob:messages:subject,anne@example.com:Anne:messages"}]],
args = [{name, binary}, {service, binary},
args = [{room, binary}, {service, binary},
{host, binary},
{options, {list,
{option, {tuple,
@ -185,6 +210,7 @@ get_commands_spec() ->
{value, binary}
]}}
}}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = destroy_rooms_file, tags = [muc],
desc = "Destroy the rooms indicated in file",
@ -290,7 +316,8 @@ get_commands_spec() ->
args_example = ["room1", "conference.example.com"],
result_desc = "The list of occupants with JID, nick and affiliation",
result_example = [{"user1@example.com/psi", "User 1", "owner"}],
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {occupants, {list,
{occupant, {tuple,
[{jid, string},
@ -306,7 +333,8 @@ get_commands_spec() ->
args_example = ["room1", "conference.example.com"],
result_desc = "Number of room occupants",
result_example = 7,
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {occupants, integer}},
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
@ -322,8 +350,9 @@ get_commands_spec() ->
args_example = [<<"room1">>, <<"conference.example.com">>,
<<>>, <<"Check this out!">>,
"user2@localhost:user3@example.com"],
args = [{name, binary}, {service, binary}, {password, binary},
args = [{room, binary}, {service, binary}, {password, binary},
{reason, binary}, {users, binary}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
desc = "Send a direct invitation to several destinations",
@ -339,8 +368,9 @@ get_commands_spec() ->
args_example = [<<"room1">>, <<"conference.example.com">>,
<<>>, <<"Check this out!">>,
["user2@localhost", "user3@example.com"]],
args = [{name, binary}, {service, binary}, {password, binary},
args = [{room, binary}, {service, binary}, {password, binary},
{reason, binary}, {users, {list, {jid, binary}}}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = change_room_option, tags = [muc_room],
@ -348,8 +378,9 @@ get_commands_spec() ->
module = ?MODULE, function = change_room_option,
args_desc = ["Room name", "MUC service", "Option name", "Value to assign"],
args_example = ["room1", "conference.example.com", "members_only", "true"],
args = [{name, binary}, {service, binary},
args = [{room, binary}, {service, binary},
{option, binary}, {value, binary}],
args_rename = [{name, room}],
result = {res, rescode}},
#ejabberd_commands{name = get_room_options, tags = [muc_room],
desc = "Get options from a MUC room",
@ -358,7 +389,8 @@ get_commands_spec() ->
args_example = ["room1", "conference.example.com"],
result_desc = "List of room options tuples with name and value",
result_example = [{"members_only", "true"}],
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {options, {list,
{option, {tuple,
[{name, string},
@ -393,6 +425,21 @@ get_commands_spec() ->
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, {list, {node, binary}}}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
version = 3,
note = "updated in 24.12",
args_desc = ["user name", "user host", "user nick",
"room name", "MUC service", "list of nodes"],
args_example = ["tom", "localhost", "Tom", "room1", "conference.localhost",
["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
result_desc = "The list of nodes that has subscribed",
result_example = ["urn:xmpp:mucsub:nodes:messages",
"urn:xmpp:mucsub:nodes:affiliations"],
args = [{user, binary}, {host, binary}, {nick, binary}, {room, binary},
{service, binary}, {nodes, {list, {node, binary}}}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
desc = "Subscribe several users to a MUC conference",
note = "added in 22.05",
@ -440,6 +487,32 @@ get_commands_spec() ->
{room, binary},
{nodes, {list, {node, binary}}}],
result = {res, rescode}},
#ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
desc = "Subscribe several users to a MUC conference",
longdesc = "This command accepts up to 50 users at once "
"(this is configurable with the _`mod_muc_admin`_ option "
"`subscribe_room_many_max_users`)",
module = ?MODULE, function = subscribe_room_many_v3,
version = 3,
note = "updated in 24.12",
args_desc = ["List of tuples with users name, host and nick",
"room name",
"MUC service",
"nodes separated by commas: `,`"],
args_example = [[{"tom", "localhost", "Tom"},
{"jerry", "localhost", "Jerry"}],
"room1", "conference.localhost",
["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
args = [{users, {list,
{user, {tuple,
[{user, binary},
{host, binary},
{nick, binary}
]}}
}},
{room, binary}, {service, binary},
{nodes, {list, {node, binary}}}],
result = {res, rescode}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
@ -447,6 +520,15 @@ get_commands_spec() ->
args_example = ["tom@localhost", "room1@conference.localhost"],
args = [{user, binary}, {room, binary}],
result = {res, rescode}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
version = 3,
note = "updated in 24.12",
args_desc = ["user name", "user host", "room name", "MUC service"],
args_example = ["tom", "localhost", "room1", "conference.localhost"],
args = [{user, binary}, {host, binary}, {room, binary}, {service, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_subscribers, tags = [muc_room, muc_sub],
desc = "List subscribers of a MUC conference",
module = ?MODULE, function = get_subscribers,
@ -454,7 +536,8 @@ get_commands_spec() ->
args_example = ["room1", "conference.example.com"],
result_desc = "The list of users that are subscribed to that room",
result_example = ["user2@example.com", "user3@example.com"],
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {subscribers, {list, {jid, string}}}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
@ -464,6 +547,19 @@ get_commands_spec() ->
args = [{name, binary}, {service, binary},
{jid, binary}, {affiliation, binary}],
result = {res, rescode}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room",
longdesc = "If affiliation is `none`, then the affiliation is removed.",
module = ?MODULE, function = set_room_affiliation,
version = 3,
note = "updated in 24.12",
args_desc = ["room name", "MUC service", "user name", "user host", "affiliation to set"],
args_example = ["room1", "conference.example.com", "sun", "localhost", "member"],
args = [{room, binary}, {service, binary},
{user, binary}, {host, binary}, {affiliation, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
desc = "Get the list of affiliations of a MUC room",
module = ?MODULE, function = get_room_affiliations,
@ -480,6 +576,25 @@ get_commands_spec() ->
{reason, string}
]}}
}}},
#ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
desc = "Get the list of affiliations of a MUC room",
module = ?MODULE, function = get_room_affiliations_v3,
version = 3,
note = "updated in 24.12",
args_desc = ["Room name", "MUC service"],
args_example = ["room1", "conference.example.com"],
result_desc = "The list of affiliations with jid, affiliation and reason",
result_example = [{"user1@example.com", member, "member"}],
args = [{room, binary}, {service, binary}],
result = {affiliations, {list,
{affiliation, {tuple,
[{jid, string},
{affiliation, atom},
{reason, string}
]}}
}}},
#ejabberd_commands{name = get_room_affiliation, tags = [muc_room],
desc = "Get affiliation of a user in MUC room",
module = ?MODULE, function = get_room_affiliation,
@ -487,7 +602,8 @@ get_commands_spec() ->
args_example = ["room1", "conference.example.com", "user1@example.com"],
result_desc = "Affiliation of the user",
result_example = member,
args = [{name, binary}, {service, binary}, {jid, binary}],
args = [{room, binary}, {service, binary}, {jid, binary}],
args_rename = [{name, room}],
result = {affiliation, atom}},
#ejabberd_commands{name = get_room_history, tags = [muc_room],
desc = "Get history of messages stored inside MUC room state",
@ -495,7 +611,8 @@ get_commands_spec() ->
module = ?MODULE, function = get_room_history,
args_desc = ["Room name", "MUC service"],
args_example = ["room1", "conference.example.com"],
args = [{name, binary}, {service, binary}],
args = [{room, binary}, {service, binary}],
args_rename = [{name, room}],
result = {history, {list,
{entry, {tuple,
[{timestamp, string},
@ -547,6 +664,9 @@ build_summary_room(Name, Host, Pid) ->
Participants
}.
muc_register_nick(Nick, User, Host, Service) ->
muc_register_nick(Nick, makeencode(User, Host), Service).
muc_register_nick(Nick, FromBinary, Service) ->
try {get_room_serverhost(Service), jid:decode(FromBinary)} of
{ServerHost, From} ->
@ -569,6 +689,9 @@ muc_register_nick(Nick, FromBinary, Service) ->
throw({error, "Internal error"})
end.
muc_unregister_nick(User, Host, Service) ->
muc_unregister_nick(makeencode(User, Host), Service).
muc_unregister_nick(FromBinary, Service) ->
muc_register_nick(<<"">>, FromBinary, Service).
@ -728,12 +851,13 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Affiliations">>, Name, R, RPath}),
Set = [make_command(set_room_affiliation,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[])],
Get = [make_command(get_room_affiliations,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}}])],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}},
{result_links, [{jid, user, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Get ++ Set;
webadmin_muc_host(_Host,
Service,
@ -747,7 +871,7 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"History">>, Name, R, RPath}),
Get = [make_command(get_room_history,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{table_options, {10, RPath}},
{result_links, [{message, paragraph, 1, <<"">>}]}])],
Title ++ Breadcrumb ++ Get;
@ -763,7 +887,7 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Invite">>, Name, R, RPath}),
Set = [make_command(send_direct_invitation,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
@ -778,7 +902,7 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Occupants">>, Name, R, RPath}),
Get = [make_command(get_room_occupants,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}},
{result_links, [{jid, user, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Get;
@ -794,11 +918,11 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Options">>, Name, R, RPath}),
Set = [make_command(change_room_option,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[])],
Get = [make_command(get_room_options,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[])],
Title ++ Breadcrumb ++ Get ++ Set;
webadmin_muc_host(_Host,
@ -816,15 +940,15 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Subscribers">>, Name, R, RPath}),
Set = [make_command(subscribe_room,
R,
[{<<"room">>, jid:encode({Name, Service, <<"">>})}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[]),
make_command(unsubscribe_room,
R,
[{<<"room">>, jid:encode({Name, Service, <<"">>})}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{style, danger}])],
Get = [make_command(get_subscribers,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}},
{result_links, [{jid, user, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Get ++ Set;
@ -840,7 +964,7 @@ webadmin_muc_host(_Host,
make_breadcrumb({room_section, Level, Service, <<"Destroy">>, Name, R, RPath}),
Set = [make_command(destroy_room,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{style, danger}])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
@ -876,7 +1000,7 @@ webadmin_muc_host(_Host, Service, [<<"rooms">> | RPath], R, _Lang, Level, PageTi
{result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
make_command(get_room_occupants_number,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{<<"room">>, Name}, {<<"service">>, Service}],
[{only, raw_and_value}])}
end,
make_command_raw_value(muc_online_rooms, R, [{<<"service">>, Service}])),
@ -1149,7 +1273,7 @@ create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
ServerHost = validate_host(ServerHost1, <<"serverhost">>),
case get_room_pid_validate(Name1, Host1, <<"name">>, <<"host">>) of
case get_room_pid_validate(Name1, Host1, <<"service">>) of
{room_not_found, Name, Host} ->
%% Get the default room options from the muc configuration
DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
@ -1179,7 +1303,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
%% If the room has participants, they are not notified that the room was destroyed;
%% they will notice when they try to chat and receive an error that the room doesn't exist.
destroy_room(Name1, Service1) ->
case get_room_pid_validate(Name1, Service1, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name1, Service1, <<"service">>) of
{room_not_found, _, _} ->
throw({error, "Room doesn't exists"});
{Pid, _, _} ->
@ -1475,7 +1599,7 @@ act_on_room(_Method, list, _) ->
%%----------------------------
get_room_occupants(Room, Host) ->
case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
case get_room_pid_validate(Room, Host, <<"service">>) of
{Pid, _, _} when is_pid(Pid) -> get_room_occupants(Pid);
_ -> throw({error, room_not_found})
end.
@ -1491,7 +1615,7 @@ get_room_occupants(Pid) ->
maps:to_list(S#state.users)).
get_room_occupants_number(Room, Host) ->
case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
case get_room_pid_validate(Room, Host, <<"service">>) of
{Pid, _, _} when is_pid(Pid)->
{ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
N;
@ -1570,7 +1694,7 @@ send_direct_invitation(FromJid, UserJid, Msg) ->
%% For example:
%% `change_room_option(<<"testroom">>, <<"conference.localhost">>, <<"title">>, <<"Test Room">>)'
change_room_option(Name, Service, OptionString, ValueString) ->
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{room_not_found, _, _} ->
throw({error, "Room not found"});
{Pid, _, _} ->
@ -1667,10 +1791,10 @@ parse_nodes([<<"subscribers">> | Rest], Acc) ->
parse_nodes(_, _) ->
throw({error, "Invalid 'subscribers' - unknown node name used"}).
-spec get_room_pid_validate(binary(), binary(), binary(), binary()) ->
-spec get_room_pid_validate(binary(), binary(), binary()) ->
{pid() | room_not_found, binary(), binary()}.
get_room_pid_validate(Name, Service, NameArg, ServiceArg) ->
Name2 = validate_room(Name, NameArg),
get_room_pid_validate(Name, Service, ServiceArg) ->
Name2 = validate_room(Name),
{ServerHost, Service2} = validate_muc2(Service, ServiceArg),
case mod_muc:unhibernate_room(ServerHost, Service2, Name2) of
error ->
@ -1761,7 +1885,7 @@ change_option(Option, Value, Config) ->
%%----------------------------
get_room_options(Name, Service) ->
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) -> get_room_options(Pid);
_ -> []
end.
@ -1784,10 +1908,10 @@ get_options(Config) ->
%%----------------------------
%% @spec(Name::binary(), Service::binary()) ->
%% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
%% [{Username::string(), Domain::string(), Role::string(), Reason::string()}]
%% @doc Get the affiliations of the room Name@Service.
get_room_affiliations(Name, Service) ->
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
%% Get the PID of the online room, then request its state
{ok, StateData} = mod_muc_room:get_state(Pid),
@ -1802,8 +1926,29 @@ get_room_affiliations(Name, Service) ->
throw({error, "The room does not exist."})
end.
%% @spec(Name::binary(), Service::binary()) ->
%% [{JID::string(), Role::string(), Reason::string()}]
%% @doc Get the affiliations of the room Name@Service.
get_room_affiliations_v3(Name, Service) ->
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
%% Get the PID of the online room, then request its state
{ok, StateData} = mod_muc_room:get_state(Pid),
Affiliations = maps:to_list(StateData#state.affiliations),
lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
Jid = makeencode(Uname, Domain),
{Jid, Aff, Reason};
({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
Jid = makeencode(Uname, Domain),
{Jid, Aff, <<>>}
end, Affiliations);
_ ->
throw({error, "The room does not exist."})
end.
get_room_history(Name, Service) ->
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:get_state(Pid) of
{ok, StateData} ->
@ -1829,7 +1974,7 @@ get_room_history(Name, Service) ->
%% @doc Get affiliation of a user in the room Name@Service.
get_room_affiliation(Name, Service, JID) ->
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
%% Get the PID of the online room, then request its state
{ok, StateData} = mod_muc_room:get_state(Pid),
@ -1843,6 +1988,9 @@ get_room_affiliation(Name, Service, JID) ->
%% Change Room Affiliation
%%----------------------------
set_room_affiliation(Name, Service, User, Host, AffiliationString) ->
set_room_affiliation(Name, Service, makeencode(User, Host), AffiliationString).
%% @spec(Name, Service, JID, AffiliationString) -> ok | {error, Error}
%% Name = binary()
%% Service = binary()
@ -1861,7 +2009,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
_ ->
throw({error, "Invalid affiliation"})
end,
case get_room_pid_validate(Name, Service, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Service, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
%% Get the PID for the online room so we can get the state of the room
case mod_muc_room:change_item(Pid, jid:decode(JID), affiliation, Affiliation, <<"">>) of
@ -1880,6 +2028,10 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
%%% MUC Subscription
%%%
subscribe_room(Username, Host, Nick, Name, Service, Nodes) ->
subscribe_room(makeencode(Username, Host), Nick,
makeencode(Name, Service), Nodes).
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
throw({error, "Nickname must be set"});
subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
@ -1891,7 +2043,7 @@ subscribe_room(User, Nick, Room, NodeList) ->
try jid:decode(User) of
UserJID1 ->
UserJID = jid:replace_resource(UserJID1, <<"modmucadmin">>),
case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
case get_room_pid_validate(Name, Host, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:subscribe(
Pid, UserJID, Nick, NodeList) of
@ -1912,6 +2064,10 @@ subscribe_room(User, Nick, Room, NodeList) ->
throw({error, "Malformed room JID"})
end.
subscribe_room_many_v3(List, Name, Service, Nodes) ->
List2 = [{makeencode(User, Host), Nick} || {User, Host, Nick} <- List],
subscribe_room_many(List2, makeencode(Name, Service), Nodes).
subscribe_room_many(Users, Room, Nodes) ->
MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
if
@ -1924,12 +2080,16 @@ subscribe_room_many(Users, Room, Nodes) ->
end, Users)
end.
unsubscribe_room(User, Host, Name, Service) ->
unsubscribe_room(makeencode(User, Host),
makeencode(Name, Service)).
unsubscribe_room(User, Room) ->
try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
try jid:decode(User) of
UserJID ->
case get_room_pid_validate(Name, Host, <<"name">>, <<"room">>) of
case get_room_pid_validate(Name, Host, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
case mod_muc_room:unsubscribe(Pid, UserJID) of
ok ->
@ -1950,7 +2110,7 @@ unsubscribe_room(User, Room) ->
end.
get_subscribers(Name, Host) ->
case get_room_pid_validate(Name, Host, <<"name">>, <<"service">>) of
case get_room_pid_validate(Name, Host, <<"service">>) of
{Pid, _, _} when is_pid(Pid) ->
{ok, JIDList} = mod_muc_room:get_subscribers(Pid),
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
@ -1962,6 +2122,9 @@ get_subscribers(Name, Host) ->
%% Utils
%%----------------------------
makeencode(User, Host) ->
jid:encode(jid:make(User, Host)).
-spec validate_host(Name :: binary(), ArgName::binary()) -> binary().
validate_host(Name, ArgName) ->
case jid:nameprep(Name) of
@ -2017,11 +2180,11 @@ validate_muc2(Name, ArgName) ->
end
end.
-spec validate_room(Name :: binary(), ArgName :: binary()) -> binary().
validate_room(Name, ArgName) ->
-spec validate_room(Name :: binary()) -> binary().
validate_room(Name) ->
case jid:nodeprep(Name) of
error ->
throw({error, <<"Invalid value of '",ArgName/binary,"'">>});
throw({error, <<"Invalid value of room name">>});
Name2 ->
Name2
end.

View file

@ -308,8 +308,7 @@ component_send_packet({#iq{from = From,
[]),
drop;
{_, {error, no_privileged_iq, _Err}} ->
?INFO_MSG("IQ not forwarded: Component tried to send not wrapped IQ stanza.",
[]),
?INFO_MSG("IQ not forwarded: Component tried to send not wrapped IQ stanza.", []),
drop;
{_, {error, roster_query, _Err}} ->
IQ;

View file

@ -259,7 +259,9 @@ set_room_affiliation(Config) ->
RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation",
Headers = [{"X-Admin", "true"}],
ContentType = "application/json",
Body = misc:json_encode(#{name => RoomName, service => RoomService, jid => jid:encode(PeerJID), affiliation => member}),
Body = misc:json_encode(#{room => RoomName, service => RoomService,
user => PeerJID#jid.luser, host => PeerJID#jid.lserver,
affiliation => member}),
{ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []),
#message{id = _, from = RoomJID, to = MyJID, sub_els = [