24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-16 22:05:29 +02:00

Merge branch 'processone:master' into master

This commit is contained in:
Pouriya 2021-09-18 16:09:11 +04:30 committed by GitHub
commit 3eb795357f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 2382 additions and 709 deletions

View File

@ -108,10 +108,7 @@ jobs:
if: matrix.rebar == 3
uses: actions/cache@v2
with:
path: |
~/.cache/rebar3/
_build/default/
!_build/default/*dialyzer_warnings
path: ~/.cache/rebar3/
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
- name: Compile
@ -126,10 +123,7 @@ jobs:
make update
make
# Right now 'make rel' works only with rebar2, not rebar3
- run: make rel
if: matrix.rebar == 2
- run: make install -s
- run: make hooks
- run: make options
@ -214,9 +208,7 @@ jobs:
- name: Cache Rebar3
uses: actions/cache@v2
with:
path: |
~/.cache/rebar3/
_build/default/lib/
path: ~/.cache/rebar3/
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
- name: Prepare libraries
run: |

4
.gitignore vendored
View File

@ -9,6 +9,7 @@
/Makefile
/config.log
/config.status
/config/releases.exs
/configure
/aclocal.m4
/*.cache
@ -17,14 +18,17 @@
/ebin/
/ejabberd.init
/ejabberd.service
/ejabberdctl
/ejabberdctl.example
/rel/ejabberd/
/rel/overlays/
/src/eldap_filter_yecc.erl
/vars.config
/dialyzer/
/test/*.beam
/test/*.ctc
/logs/
/priv/bin/captcha*sh
/priv/sql
/rel/ejabberd
/_build

View File

@ -1,8 +1,127 @@
# Version 21.07
Compilation
- Add rebar3 3.15.2 binary
- Add support for mix to: `./configure --enable-rebar=mix`
- Improved `make rel` to work with rebar3 and mix
- Add `make dev` to build a development release with rebar3 or mix
- Hex: Add `sql/` and `vars.config` to Hex package files
- Hex: Update mix applications list to fix error `p1_utils is listed as both...`
- There are so many targets in Makefile... add `make help`
- Fix extauth.py failure in test suite with Python 3
- Added experimental support for GitHub Codespaces
- Switch test service from TravisCI to GitHub Actions
Commands:
- Display extended error message in ejabberdctl
- Remove SMP option from ejabberdctl.cfg, `-smp` was removed in OTP 21
- `create_room`: After creating room, store in DB if it's persistent
- `help`: Major changes in its usage and output
- `srg_create`: Update to use `label` parameter instead of `name`
Modules:
- ejabberd_listener: New `send_timeout` option
- mod_mix: Improvements to update to 0.14.1
- mod_muc_room: Don't leak owner JIDs
- mod_multicast: Routing for more MUC packets
- mod_multicast: Correctly strip only other bcc addresses
- mod_mqtt: Allow shared roster group placeholder in mqtt topic
- mod_pubsub: Several fixes when using PubSub with RSM
- mod_push: Handle MUC/Sub events correctly
- mod_shared_roster: Delete cache after performing change to be sure that in cache will be up to date data
- mod_shared_roster: Improve database and caching
- mod_shared_roster: Reconfigure cache when options change
- mod_vcard: Fix invalid_encoding error when using extended plane characters in vcard
- mod_vcard: Update econf:vcard() to generate correct vcard_temp record
- WebAdmin: New simple pages to view mnesia tables information and content
- WebSocket: Fix typos
SQL:
- MySQL Backend Patch for scram-sha512
- SQLite: When exporting for SQLite, use its specific escape options
- SQLite: Minor fixes for new_sql_schema support
- mod_privacy: Cast as boolean when exporting privacy_list_data to PostgreSQL
- mod_mqtt: Add mqtt_pub table definition for MSSQL
- mod_shared_roster: Add missing indexes to `sr_group` tables in all SQL databases
# Version 21.04
API Commands:
- `add_rosteritem/...`: Add argument guards to roster commands
- `get_user_subscriptions`: New command for MUC/Sub
- `remove_mam_for_user_with_peer`: Fix when removing room archive
- `send_message`: Fix bug introduced in ejabberd 21.01
- `set_vcard`: Return modules errors
Build and setup:
- Allow ejabberd to be compatible as a dependency for an Erlang project using rebar3
- CAPTCHA: New question/answer-based CAPTCHA script
- `--enable-lua`: new configure option for luerl instead of --enable-tools
- Remove support for HiPE, it was experimental and Erlang/OTP 24 removes it
- Update `sql_query` record to handle the Erlang/OTP 24 compiler reports
- Updated dependencies to fix Dialyzer warnings
Miscellaneous:
- CAPTCHA: Update `FORM_TYPE` from captcha to register
- LDAP: fix eldap certificate verification
- MySQL: Fix for "specified key was too long"
- Translations: updated the Esperanto, Greek, and Japanese translations
- Websocket: Fix PONG responses
Modules:
- `mod_block_strangers`: If stanza is type error, allow it passing
- `mod_caps`: Don't request roster when not needed
- `mod_caps`: Skip reading roster in one more case
- `mod_mam`: Remove `queryid` from MAM fin element
- `mod_mqtt`: When deregistering XMPP account, close its MQTT sessions
- `mod_muc`: Take in account subscriber's affiliation when checking access to moderated room
- `mod_muc`: Use monitors to track online and hard-killed rooms
- `mod_muc`: When occupant is banned, remove his subscriptions too
- `mod_privacy`: Make fetching roster lazy
- `mod_pubsub`: Don't fail on PEP unsubscribe
- `mod_pubsub`: Fix `gen_pubsub_node:get_state` return value
- `mod_vcard`: Obtain and provide photo type in vCard LDAP
# Version 21.01
Miscellaneous changes:
- `log_rotate_size` option: Fix handling of infinity value
- `mod_time`: Fix invalid timezone
- Auth JWT: New `check_decoded_jwt` hook runs the default JWT verifier
- MUC: Allow non-occupant non-subscribed service admin send private MUC message
- MUC: New `max_password` and `max_captcha_whitelist` options
- OAuth: New `oauth_cache_rest_failure_life_time` option
- PEP: Skip reading pep nodes that we know wont be requested due to caps
- SQL: Add sql script to migrate mysql from old schema to new
- SQL: Dont use REPLACE for upsert when there are “-” fields.
- Shared Rosters LDAP: Add multi-domain support (and flexibility)
- Sqlite3: Fix dependency version
- Stun: Block loopback addresses by default
- Several documentation fixes and clarifications
Commands:
- `decide_room`: Use better fallback value for room activity time when skipping room
- `delete_old_message`: Fix when using sqlite spool table
- `module_install`: Make ext_mod compile module with debug_info flags
- `room_unused_*`: Dont fetch subscribers list
- `send_message`: Dont include empty in messages
- `set_room_affiliation`: Validate affiliations
Running:
- Docker: New `Dockerfile` and `devcontainer.json`
- New `ejabberdctl foreground-quiet`
- Systemd: Allow for listening on privileged ports
- Systemd: Integrate nicely with systemd
Translations:
- Moved gettext PO files to a new `ejabberd-po` repository
- Improved several translations: Catalan, Chinese, German, Greek, Indonesian, Norwegian, Portuguese (Brazil), Spanish.
# Version 20.12
- Add support for `SCRAM-SHA-{256,512}-{PLUS}` authentication
- Don't use same value in cache for user don't exist and wrong password
- outgoing_s2s_ipv*_address: New options to set ipv4/ipv6 outbound s2s out interface
- `outgoing_s2s_ipv*_address`: New options to set ipv4/ipv6 outbound s2s out interface
- s2s_send_packet: this hook now filters outgoing s2s stanzas
- start_room: new hook runs when a room process is started
- check_decoded_jwt: new hook to check decoded JWT after success authentication
@ -10,7 +129,7 @@
* Admin
- Docker: Fix DB initialization
- New sql_odbc_driver option: choose the mssql ODBC driver
- Rebar3: Fully supported. Enable with ./configure --with-rebar=/path/to/rebar3
- Rebar3: Fully supported. Enable with `./configure --with-rebar=/path/to/rebar3`
- systemd: start ejabberd in foreground
* Modules:

View File

@ -1,4 +1,5 @@
REBAR = @ESCRIPT@ @rebar@
MIX = @rebar@
INSTALL = @INSTALL@
SED = @SED@
ERL = @ERL@
@ -97,16 +98,38 @@ ifneq ($(INSTALLGROUP),)
G_USER=-g $(INSTALLGROUP)
endif
IS_REBAR3:=$(shell expr `$(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}'` '>=' 3)
ifeq "$(MIX)" "mix"
REBAR_VER:=6
else
REBAR_VER:=$(shell $(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}')
endif
ifeq "$(IS_REBAR3)" "1"
ifeq "$(REBAR_VER)" "6"
REBAR=$(MIX)
SKIPDEPS=
LISTDEPS=deps.tree
UPDATEDEPS=deps.update
DEPSPATTERN="s/.*─ \([a-z0-9_]*\) .*/\1/p;"
DEPSBASE=_build
DEPSDIR=$(DEPSBASE)/dev/lib
GET_DEPS= deps.get
CONFIGURE_DEPS=
EBINDIR=$(DEPSDIR)/ejabberd/ebin
REBARREL=MIX_ENV=prod $(REBAR) release --overwrite
REBARDEV=MIX_ENV=dev $(REBAR) release --overwrite
else
ifeq "$(REBAR_VER)" "3"
SKIPDEPS=
LISTDEPS=tree
UPDATEDEPS=upgrade
DEPSPATTERN="s/ (.*//; /^ / s/.* \([a-z0-9_]*\).*/\1/p;"
DEPSBASE=_build
DEPSDIR=$(DEPSBASE)/default/lib
GET_DEPS= get-deps
CONFIGURE_DEPS=$(REBAR) configure-deps
EBINDIR=$(DEPSDIR)/ejabberd/ebin
REBARREL=$(REBAR) as prod tar
REBARDEV=REBAR_PROFILE=dev $(REBAR) release
else
SKIPDEPS=skip_deps=true
LISTDEPS=-q list-deps
@ -114,7 +137,12 @@ else
DEPSPATTERN="/ TAG / s/ .*// p; / REV / s/ .*// p; / BRANCH / s/ .*// p;"
DEPSBASE=deps
DEPSDIR=$(DEPSBASE)
GET_DEPS= get-deps
CONFIGURE_DEPS=$(REBAR) configure-deps
EBINDIR=ebin
REBARREL=$(REBAR) generate
REBARDEV=
endif
endif
all: deps src
@ -125,10 +153,10 @@ $(DEPSDIR)/.got:
rm -rf $(DEPSDIR)/.got
rm -rf $(DEPSDIR)/.built
mkdir -p $(DEPSDIR)
$(REBAR) get-deps && :> $(DEPSDIR)/.got
$(REBAR) $(GET_DEPS) && :> $(DEPSDIR)/.got
$(DEPSDIR)/.built: $(DEPSDIR)/.got
$(REBAR) configure-deps
$(CONFIGURE_DEPS)
$(REBAR) compile && :> $(DEPSDIR)/.built
src: $(DEPSDIR)/.built
@ -357,15 +385,20 @@ distclean: clean clean-rel
rm -f ejabberdctl.example ejabberd.init ejabberd.service
[ ! -f ../ChangeLog ] || rm -f ../ChangeLog
rel: all
$(REBAR) generate
rel:
$(REBARREL)
DEV_CONFIG = _build/dev/rel/ejabberd/etc/ejabberd/ejabberd.yml
dev $(DEV_CONFIG):
$(REBARDEV)
TAGS:
etags *.erl
Makefile: Makefile.in
ifeq "$(IS_REBAR3)" "1"
ifeq "$(REBAR_VER)" "3"
dialyzer:
$(REBAR) dialyzer
else
@ -419,3 +452,29 @@ test:
.PHONY: src edoc dialyzer Makefile TAGS clean clean-rel distclean rel \
install uninstall uninstall-binary uninstall-all translations deps test \
quicktest erlang_plt deps_plt ejabberd_plt xref hooks options
help:
@echo ""
@echo " [all] Compile dependencies and ejabberd"
@echo " src Compile ejabberd"
@echo " deps Get dependencies"
@echo " update Update dependencies' source code"
@echo " clean Clean binary files"
@echo " distclean Clean completely the development files"
@echo ""
@echo " install Install ejabberd to /usr/local"
@echo " uninstall Uninstall ejabberd (buggy)"
@echo " uninstall-all Uninstall also configuration, logs, mnesia... (buggy)"
@echo ""
@echo " rel Build a production release"
@echo " dev Build a development release"
@echo ""
@echo " edoc Generate edoc documentation (unused)"
@echo " options Generate ejabberd_option.erl"
@echo " translations Extract translation files (requires --enable-tools)"
@echo " tags Generate tags file for text editors"
@echo ""
@echo " dialyzer Run Dialyzer static analyzer"
@echo " hooks Run hooks validator"
@echo " test Run Common Tests suite"
@echo " xref Run cross reference analysis"

View File

@ -118,6 +118,7 @@ To compile ejabberd you need:
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
- ImageMagick's Convert program and Ghostscript fonts. Optional. For CAPTCHA
challenges.
- Elixir ≥ 1.10.3. Optional. Alternative to build ejabberd
If your system splits packages in libraries and development headers, you must
install the development packages also.
@ -155,9 +156,25 @@ start and stop ejabberd. For example:
ejabberdctl start
For detailed information please refer to the ejabberd Installation and
Operation Guide available online and in the `doc` directory of the source
tarball.
For detailed information please refer to the
[ejabberd Documentation](https://docs.ejabberd.im)
### 3. Use ejabberd locally
Alternatively, you can setup ejabberd without installing in your system:
./configure --with-rebar=rebar3
make dev
Or, if you have Elixir available and plan to develop Elixir code:
./configure --with-rebar=mix
make dev
Check the full list of targets:
make help
Development

View File

@ -1,10 +0,0 @@
use Mix.Config
# This is standard path in the context of ejabberd release
config :ejabberd,
file: "config/ejabberd.yml",
log_path: 'log/ejabberd.log'
# Customize Mnesia directory:
config :mnesia,
dir: 'database/'

12
config/runtime.exs Normal file
View File

@ -0,0 +1,12 @@
import Config
rootpath = System.get_env("RELEASE_ROOT", "")
# This is standard path in the context of ejabberd release
config :ejabberd,
file: Path.join(rootpath, "etc/ejabberd/ejabberd.yml"),
log_path: Path.join(rootpath, 'var/log/ejabberd/ejabberd.log')
# Customize Mnesia directory:
config :mnesia,
dir: Path.join(rootpath, 'var/lib/ejabberd/')

View File

@ -30,7 +30,7 @@ fi
AC_ARG_WITH(rebar,
AC_HELP_STRING([--with-rebar=bin],
[use rebar specified]),
[use the rebar/rebar3/mix binary specified]),
[if test "$withval" = "yes" -o "$withval" = "no" -o "X$with_rebar" = "X"; then
rebar="rebar"
else

View File

@ -10,7 +10,8 @@ INET_DIST_INTERFACE=""
ERLANG_NODE=ejabberd@localhost
# define default environment variables
SCRIPT_DIR=$(cd "${0%/*}" && pwd)
[ -z "$SCRIPT" ] && SCRIPT=$0
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd -P)"
ERL="{{erl}}"
IEX="{{bindir}}/iex"
EPMD="{{epmd}}"

View File

@ -59,6 +59,7 @@
policy = restricted :: open | restricted | admin | user,
%% access is: [accessRuleName] or [{Module, AccessOption, DefaultAccessRuleName}]
access = [] :: [{atom(),atom(),atom()}|atom()],
definer = unknown :: atom(),
result = {res, rescode} :: rterm() | '_' | '$2',
args_rename = [] :: [{atom(),atom()}],
args_desc = none :: none | [string()] | '_',

View File

@ -87,6 +87,16 @@
nick = <<>> :: binary(),
nodes = [] :: [binary()]}).
-record(muc_subscribers,
{subscribers = #{} :: subscribers(),
subscriber_nicks = #{} :: subscriber_nicks(),
subscriber_nodes = #{} :: subscriber_nodes()
}).
-type subscribers() :: #{ljid() => #subscriber{}}.
-type subscriber_nicks() :: #{binary() => [ljid()]}.
-type subscriber_nodes() :: #{binary() => subscribers()}.
-record(activity,
{
message_time = 0 :: integer(),
@ -106,8 +116,7 @@
jid = #jid{} :: jid(),
config = #config{} :: config(),
users = #{} :: users(),
subscribers = #{} :: subscribers(),
subscriber_nicks = #{} :: subscriber_nicks(),
muc_subscribers = #muc_subscribers{} :: #muc_subscribers{},
last_voice_request_time = treap:empty() :: treap:treap(),
robots = #{} :: robots(),
nicks = #{} :: nicks(),
@ -126,5 +135,3 @@
-type robots() :: #{jid() => {binary(), stanza()}}.
-type nicks() :: #{binary() => [ljid()]}.
-type affiliations() :: #{ljid() => affiliation() | {affiliation(), binary()}}.
-type subscribers() :: #{ljid() => #subscriber{}}.
-type subscriber_nicks() :: #{binary() => [ljid()]}.

View File

@ -2,12 +2,12 @@
.\" Title: ejabberd.yml
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 03/26/2021
.\" Date: 07/21/2021
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "03/26/2021" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "07/21/2021" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@ -82,7 +82,7 @@ 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/21\&.01/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/21\&.07/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"
@ -378,6 +378,8 @@ that can be installed from the
\fIejabberd\-contrib\fR
Git repository\&. Please refer to that module\(cqs README file for details\&.
.RE
.sp
\fINote\fR about the next option: improved in 20\&.01:
.PP
\fBauth_password_format\fR: \fIplain | scram\fR
.RS 4
@ -996,7 +998,7 @@ minute\&.
Whether to use
\fInew\fR
SQL schema\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/21\&.01/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
https://github\&.com/processone/ejabberd/tree/21\&.07/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
\fInew\fR
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
\fInew\fR
@ -1029,6 +1031,8 @@ Same as
\fIcache_missed\fR
will be used\&.
.RE
.sp
\fINote\fR about the next option: added in 21\&.01:
.PP
\fBoauth_cache_rest_failure_life_time\fR: \fItimeout()\fR
.RS 4
@ -1110,12 +1114,16 @@ Specify which address families to try, in what order\&. The default is
\fI[ipv4, ipv6]\fR
which means it first tries connecting with IPv4, if that fails it tries using IPv6\&.
.RE
.sp
\fINote\fR about the next option: added in 20\&.12:
.PP
\fBoutgoing_s2s_ipv4_address\fR: \fIAddress\fR
.RS 4
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
\fIundefined\fR\&.
.RE
.sp
\fINote\fR about the next option: added in 20\&.12:
.PP
\fBoutgoing_s2s_ipv6_address\fR: \fIAddress\fR
.RS 4
@ -1542,6 +1550,8 @@ An SQL database name\&. For SQLite this must be a full path to a database file\&
.RS 4
An interval to make a dummy SQL request to keep alive the connections to the database\&. There is no default value, so no keepalive requests are made\&.
.RE
.sp
\fINote\fR about the next option: added in 20\&.12:
.PP
\fBsql_odbc_driver\fR: \fIPath\fR
.RS 4
@ -1574,6 +1584,8 @@ for PostgreSQL and
\fI1433\fR
for MS SQL\&. The option has no effect for SQLite\&.
.RE
.sp
\fINote\fR about the next option: added in 20\&.01:
.PP
\fBsql_prepared_statements\fR: \fItrue | false\fR
.RS 4
@ -1605,10 +1617,12 @@ if the latter is not set\&.
A hostname or an IP address of the SQL server\&. The default value is
\fIlocalhost\fR\&.
.RE
.sp
\fINote\fR about the next option: improved in 20\&.03:
.PP
\fBsql_ssl\fR: \fItrue | false\fR
.RS 4
Whether to use SSL encrypted connections to the SQL server\&. The option is only available for PostgreSQL\&. The default value is
Whether to use SSL encrypted connections to the SQL server\&. The option is only available for MySQL and PostgreSQL\&. The default value is
\fIfalse\fR\&.
.RE
.PP
@ -1870,7 +1884,7 @@ ejabberdctl srg\-create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g
.RE
.SS "mod_admin_update_sql"
.sp
This module can be used to update existing SQL database from the default to the new schema\&. Check the section Default and New Schemas for details\&. When the module is loaded use \fIupdate_sql\fR ejabberdctl command\&.
This module can be used to update existing SQL database from the default to the new schema\&. Check the section Default and New Schemas for details\&. Please note that only PostgreSQL is supported\&. When the module is loaded use \fIupdate_sql\fR ejabberdctl command\&.
.sp
The module has no options\&.
.SS "mod_announce"
@ -3749,12 +3763,16 @@ option is not specified, the only Jabber ID will be the hostname of the virtual
\fI@HOST@\fR
is replaced with the real virtual host name\&.
.RE
.sp
\fINote\fR about the next option: added in 21\&.01:
.PP
\fBmax_captcha_whitelist\fR: \fINumber\fR
.RS 4
This option defines the maximum number of characters that Captcha Whitelist can have when configuring the room\&. The default value is
\fIinfinity\fR\&.
.RE
.sp
\fINote\fR about the next option: added in 21\&.01:
.PP
\fBmax_password\fR: \fINumber\fR
.RS 4
@ -3840,10 +3858,11 @@ Same as top\-level
option, but applied to this module only\&.
.RE
.PP
\fBram_db_type\fR: \fImnesia\fR
\fBram_db_type\fR: \fImnesia | sql\fR
.RS 4
Define the type of volatile (in\-memory) storage where the module will store room information\&. The only available value for this module is
\fImnesia\fR\&.
Define the type of volatile (in\-memory) storage where the module will store room information (\fImuc_online_room\fR
and
\fImuc_online_users\fR)\&.
.RE
.PP
\fBregexp_room_id\fR: \fIstring()\fR
@ -5422,7 +5441,7 @@ This option specifies the period of time until the session of a disconnected pus
\fImod_stream_mgmt\fR
module is restored\&. The default value is
\fI72\fR
minutes\&.
hours\&.
.RE
.PP
\fBwake_on_start\fR: \fItrue | false\fR
@ -6556,7 +6575,7 @@ minutes\&.
.RE
.SS "mod_stun_disco"
.sp
This module allows XMPP clients to discover STUN/TURN services and to obtain temporary credentials for using them as per XEP\-0215: External Service Discovery\&.
This module allows XMPP clients to discover STUN/TURN services and to obtain temporary credentials for using them as per XEP\-0215: External Service Discovery\&. This module is included in ejabberd since version 20\&.04\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -7170,13 +7189,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 21\&.01\&.81\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 21\&.04\&.131\&. 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/21\&.01/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/21\&.07/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp

134
mix.exs
View File

@ -1,9 +1,9 @@
defmodule Ejabberd.Mixfile do
defmodule Ejabberd.MixProject do
use Mix.Project
def project do
[app: :ejabberd,
version: "21.4.0",
version: version(),
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
@ -13,10 +13,24 @@ defmodule Ejabberd.Mixfile do
erlc_paths: ["asn1", "src"],
# Elixir tests are starting the part of ejabberd they need
aliases: [test: "test --no-start"],
start_permanent: Mix.env() == :prod,
language: :erlang,
releases: releases(),
package: package(),
deps: deps()]
end
def version do
case config(:vsn) do
:false -> "0.0.0" # ./configure wasn't run: vars.config not created
'0.0' -> "0.0.0" # the full git repository wasn't downloaded
[_, _, ?., _, _] = x ->
head = String.replace(:erlang.list_to_binary(x), ~r/0+([0-9])/, "\\1")
<<head::binary, ".0">>
vsn -> String.replace(:erlang.list_to_binary(vsn), ~r/0+([0-9])/, "\\1")
end
end
def description do
"""
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
@ -25,12 +39,13 @@ defmodule Ejabberd.Mixfile do
def application do
[mod: {:ejabberd_app, []},
applications: [:kernel, :stdlib, :sasl, :ssl],
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml, :xmpp, :mqtree,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2,
:eimp, :base64url, :jose, :pkix, :os_mon, :yconf,
:p1_acme, :idna]
extra_applications: [:mix],
applications: [:idna, :inets, :kernel, :sasl, :ssl, :stdlib,
:base64url, :fast_tls, :fast_xml, :fast_yaml, :jiffy, :jose,
:p1_utils, :stringprep, :stun, :yconf],
included_applications: [:lager, :mnesia, :os_mon,
:cache_tab, :eimp, :esip, :mqtree, :p1_acme,
:p1_oauth2, :pkix, :xmpp]
++ cond_apps()]
end
@ -107,9 +122,9 @@ defmodule Ejabberd.Mixfile do
{:p1_pgsql, "~> 1.1"},
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:stun, "~> 1.0"},
{:xmpp, "~> 1.5"},
{:xmpp, git: "https://github.com/processone/xmpp", ref: "e943c0285aa85e3cbd4bfb9259f6b7de32b00395", override: true},
{:yconf, "~> 1.0"}]
++ cond_deps()
end
@ -140,14 +155,15 @@ defmodule Ejabberd.Mixfile do
{config(:mysql), :p1_mysql},
{config(:odbc), :odbc},
{config(:pgsql), :p1_pgsql},
{config(:sqlite), :sqlite3},
{config(:zlib), :ezlib}], do:
{config(:sqlite), :sqlite3}], do:
app
end
defp package do
[# These are the default files included in the package
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING", "rebar.config", "rebar.config.script"],
files: ["include", "lib", "priv", "sql", "src",
"COPYING", "README.md",
"mix.exs", "rebar.config", "rebar.config.script", "vars.config"],
maintainers: ["ProcessOne"],
licenses: ["GPLv2"],
links: %{"Site" => "https://www.ejabberd.im",
@ -170,6 +186,98 @@ defmodule Ejabberd.Mixfile do
end
end
defp releases do
maybe_tar = case Mix.env() do
:prod -> [:tar]
_ -> []
end
[
ejabberd: [
include_executables_for: [:unix],
# applications: [runtime_tools: :permanent]
steps: [&copy_extra_files/1, :assemble | maybe_tar]
]
]
end
defp copy_extra_files(release) do
assigns = [
version: version(),
rootdir: config(:rootdir),
installuser: config(:installuser),
libdir: config(:libdir),
sysconfdir: config(:sysconfdir),
localstatedir: config(:localstatedir),
docdir: config(:docdir),
erl: config(:erl),
epmd: config(:epmd),
bindir: Path.join([config(:release_dir), "releases", version()]),
release_dir: config(:release_dir),
erts_vsn: "erts-#{release.erts_version}"
]
ro = "rel/overlays"
File.rm_rf(ro)
# Elixir lower than 1.12.0 don't have System.shell
execute = fn(command) ->
case function_exported?(System, :shell, 1) do
true ->
System.shell(command)
false ->
:os.cmd(to_charlist(command))
end
end
# Mix/Elixir lower than 1.11.0 use config/releases.exs instead of runtime.exs
case Version.match?(System.version, ">= 1.11.0") do
true ->
:ok
false ->
execute.("cp config/runtime.exs config/releases.exs")
end
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.template > ejabberdctl.example1")
Mix.Generator.copy_template("ejabberdctl.example1", "ejabberdctl.example2", assigns)
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2 > ejabberdctl.example3")
execute.("sed -e 's|ERLANG_NODE=ejabberd@localhost|ERLANG_NODE=ejabberd|g' ejabberdctl.example3 > ejabberdctl.example4")
execute.("sed -e 's|INSTALLUSER=|ERL_OPTIONS=\"-setcookie \\$\\(cat \"\\${SCRIPT_DIR%/*}/releases/COOKIE\")\"\\nINSTALLUSER=|g' ejabberdctl.example4 > ejabberdctl.example5")
Mix.Generator.copy_template("ejabberdctl.example5", "#{ro}/bin/ejabberdctl", assigns)
File.chmod("#{ro}/bin/ejabberdctl", 0o755)
File.rm("ejabberdctl.example1")
File.rm("ejabberdctl.example2")
File.rm("ejabberdctl.example3")
File.rm("ejabberdctl.example4")
File.rm("ejabberdctl.example5")
suffix = case Mix.env() do
:dev ->
Mix.Generator.copy_file("test/ejabberd_SUITE_data/ca.pem", "#{ro}/etc/ejabberd/ca.pem")
Mix.Generator.copy_file("test/ejabberd_SUITE_data/cert.pem", "#{ro}/etc/ejabberd/cert.pem")
".example"
_ -> ""
end
Mix.Generator.copy_file("ejabberd.yml.example", "#{ro}/etc/ejabberd/ejabberd.yml#{suffix}")
Mix.Generator.copy_file("ejabberdctl.cfg.example", "#{ro}/etc/ejabberd/ejabberdctl.cfg#{suffix}")
Mix.Generator.copy_file("inetrc", "#{ro}/etc/ejabberd/inetrc")
Mix.Generator.copy_template("rel/vm.args.mix", "#{ro}/etc/ejabberd/vm.args", assigns)
Enum.each(File.ls!("sql"),
fn x ->
Mix.Generator.copy_file("sql/#{x}", "#{ro}/lib/ejabberd-#{release.version}/priv/sql/#{x}")
end)
Mix.Generator.create_directory("#{ro}/var/lib/ejabberd")
case Mix.env() do
:dev -> execute.("REL_DIR_TEMP=$PWD/rel/overlays/ rel/setup-dev.sh")
_ -> :ok
end
release
end
end
defmodule Mix.Tasks.Compile.Asn1 do

View File

@ -1,40 +1,36 @@
%{
"artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
"cache_tab": {:hex, :cache_tab, "1.0.28", "551beb14325c34aa7e1e5d65e5fdaa1a87b0325393cfaf8641c0068364d84f69", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "17451084f2cc0a4a6304e12ae5c9ceef13d70ad070c9c406317e80abb7677889"},
"cache_tab": {:hex, :cache_tab, "1.0.29", "6c161988620b788d8df28c8f6af557571609c8e4b671dbadab295a4722cd501b", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "a02a638021cce91ed1a8628dcbb4795bf5c01c9d11db8c613065923142824ce9"},
"distillery": {:hex, :distillery, "2.1.1", "f9332afc2eec8a1a2b86f22429e068ef35f84a93ea1718265e740d90dd367814", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm", "bbc7008b0161a6f130d8d903b5b3232351fccc9c31a991f8fcbf2a12ace22995"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"eimp": {:hex, :eimp, "1.0.20", "4b46fa8623690ba602fc1c509dd5d38bdf7c2188b694064bf144f36be600cf02", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "531a1d07e5df83f6adf29f9bae93faa227ebfdcd92d2408a28630709eac3e979"},
"epam": {:hex, :epam, "1.0.10", "feb9cf1169c8e30609b13bc5f77a0d8213b425997bec1d05dfbccab024fde856", [:rebar3], [], "hexpm", "429a43b876fbef5d1accaadf8177a74933b37e069d6bcc08d74745b8c8f76f71"},
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm", "d9b5abef2c2c8aba8f32aa018203e0b3dc8b1157773b254ab1d4c2002317f1e1"},
"esip": {:hex, :esip, "1.0.42", "ccd1f3aa1e8d7b5ffe51fc5df238b0d36b5f6cde67cef1dab24931ff03a0a836", [:rebar3], [{:fast_tls, "1.1.12", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.43", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "87e28b1344543ef9c1b24a60097ae3ceeedba0ff6487f20cdc7be53f087b9a4e"},
"ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"},
"ezlib": {:hex, :ezlib, "1.0.9", "b17136b48bcf73962446b06d4427b0b6f2be4550bb5190a18a2979640271e244", [:rebar3], [], "hexpm", "fafc60a0de6e982be38f793da7b220b87a0da2969eba8a878351442b35cc2fde"},
"fast_tls": {:hex, :fast_tls, "1.1.12", "b11997d96d4306abcd4cdb9ffa77ccfc0f826f64bed77ecaceb48b2dc46b9202", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "c3d80828f5d985cba4c93660ffbf2c9b9cd831303382b97832dd66429fb16f8a"},
"fast_xml": {:hex, :fast_xml, "1.1.46", "bd563e1cdd8a397ed31a6e11f832ea9e4e90f84695104aa92b46224a887fc6ae", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "2992a0b2fb649879936fd72498e8bf4202e4cc8356295e14ddf17d4220ac8c51"},
"fast_yaml": {:hex, :fast_yaml, "1.0.31", "a66d0e678341dc20680aece8e6fd566205a229981b5b3cfa698c66323b728da2", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8aa129e3384c5aa9f5f1597a1e3321a63a44860f3951f23eeed950bf0ee59643"},
"earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
"eimp": {:hex, :eimp, "1.0.21", "2e918a5dc9a1959ef8713a2360499e3baeee64cfd7881bd9d1f361ca9ddf07e8", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "998f58538f58aa0cff103414994d7ce56dc253e6576cd6fb40c1ead64aa73a28"},
"epam": {:hex, :epam, "1.0.12", "2a5625d4133bca4b3943791a3f723ba764455a461ae9b6ba5debb262efcf4b40", [:rebar3], [], "hexpm", "54c166c4459cef72f2990a3d89a8f0be27180fe0ab0f24b28ddcc3b815f49f7f"},
"esip": {:hex, :esip, "1.0.43", "1cbdc073073f80b9b50e2759f66ca13a353eb4f874bcf92501bd4cd767e34d46", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.44", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "b2c758ae52c4588e0399c0b4ce550bfa56551a5a2f828a28389f2614797e4f4b"},
"ex_doc": {:hex, :ex_doc, "0.25.0", "4070a254664ee5495c2f7cce87c2f43064a8752f7976f2de4937b65871b05223", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2d90883bd4f3d826af0bde7fea733a4c20adba1c79158e2330f7465821c8949b"},
"ezlib": {:hex, :ezlib, "1.0.10", "c1c24eb18944cfde55f0574e9922d5b0392fa864282f769f82b2ea15e54f6003", [:rebar3], [], "hexpm", "1d317f1d85373686199eb3b4164d3477e95033ac68e45a95ba18e7b7a8c23241"},
"fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"},
"fast_xml": {:hex, :fast_xml, "1.1.47", "bd1d6c081b69c7bce0d2f22b013c1b864ed2588d48f34e2156d9428f8f772c66", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dd014c45247498effb9a28cf98cb716db79be635ad1e98c951240763119f24c7"},
"fast_yaml": {:hex, :fast_yaml, "1.0.32", "43f53a2c8572f2e4d66cd4e787fc6761b1c65b9132e42c511d8b9540b0989d65", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "7258e322739ff0824237ebe44cd158e0bf52cd27a15fe731cf92f4b4c70b913e"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jiffy": {:hex, :jiffy, "1.0.5", "a69b58faf7123534c20e1b0b7ae97ac52079ca02ed4b6989b4b380179cd63a54", [:rebar3], [], "hexpm", "b617a53f46ae84f20d0c38951367dc947a2cf8cff922aa5c6ac6b64b8b052289"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"},
"lager": {:hex, :lager, "3.6.10", "6172b43ab720ac33914ccd0aeb21fdbdf88213847707d4b91e6af57b2ae5c4d2", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "5d10499461826b79c5abee18bb594b3949cbdf76d9d9fd7e66d0a558137c21c9"},
"luerl": {:hex, :luerl, "0.3.1", "5412807630aac1aaf59ffe5a1bc09259c447b4faeb1d3fe2d4ef41b87676cb04", [:rebar3], [], "hexpm", "1bc011c7297e43aec762e53b17ecb15b0ff29f9546cd153110b343cf5b043f5f"},
"lager": {:hex, :lager, "3.9.2", "4cab289120eb24964e3886bd22323cb5fefe4510c076992a23ad18cf85413d8c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "7f904d9e87a8cb7e66156ed31768d1c8e26eba1d54f4bc85b1aa4ac1f6340c28"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mqtree": {:hex, :mqtree, "1.0.13", "28c9eaa3a63624829c75f606588d584c3434aec3b615df452f5a539a97467f50", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "2369ecb99b2ae3908e8c5c604ed4b96aab8f930ac1f1225e1c42ec25d6a65cc8"},
"mqtree": {:hex, :mqtree, "1.0.14", "d201a79b51a9232b80e764b4b77a866f7c30a90c7ac6205d71f391eb3ea7eb31", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8626dac5e862b575eaf4836f0fc1be5a7c8435c378c5a309e34ee012d48b6f6e"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"p1_acme": {:hex, :p1_acme, "1.0.12", "e51a9e178441669ac06fc5f335ed8ef3343fda67ec83bbbf9551c49967d5f401", [:rebar3], [{:idna, "~>6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~>1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~>1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~>1.0.11", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "9a98f523c656b159be3222c9834a07a83cd5f9dc83f8dec9233a457fceef99a6"},
"p1_mysql": {:hex, :p1_mysql, "1.0.18", "c636842cc7b56785de383e4ffacc5b74318dae09d9d1447f6eac8b4aac8bd8f4", [:rebar3], [], "hexpm", "4b5e001c554b9a2b9e32885ad43c7cc97d53ef8412d9bbe9491c01997935fbb8"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.9", "07c4527e0af10aa93e4df033f971c64aec01aa0806d6467bb0632f944b3a2309", [:rebar3], [], "hexpm", "924360cd9883cb773ae8166773241e1a9b3a70cad3034e038ab493b40b56d987"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.11", "8769ec7a2c4f480d443b90fdaa02519fbb459aa06c5fa6a99a0fbee683be9f7a", [:rebar3], [], "hexpm", "e4ef71713293717e8ace5fe717ddd63c618179597a6af79156af9be8c3ea851d"},
"p1_utils": {:hex, :p1_utils, "1.0.22", "10a70faa665d1f8f6995fa100d068b4d910ee2a0111fa3567e9d6232cbb86f21", [:rebar3], [], "hexpm", "0e49ff5586515e3c44901d6fb06b1473bda492d749f0b1705cbf9d96d4cd5a38"},
"pkix": {:hex, :pkix, "1.0.7", "a0b8c9011edeba702d7cb73fecef1eabe3ae89b3dcf4b8f33775e4f17a7a1304", [:rebar3], [], "hexpm", "104a9e0ecd9cc0e579d148a028189a6efe6420b241f3d319d8a65d898a078295"},
"sqlite3": {:hex, :sqlite3, "1.1.9", "10090161893843c9839d884855cafbb33c8ee3a2d6597443d66e6f94144dfd79", [:rebar3], [], "hexpm", "148ef74ee796efc13ffc6bb0e7f5feeb050f425e7335ff02c7415ca86819c0d0"},
"stringprep": {:hex, :stringprep, "1.0.25", "15062f6020777a51a96488f29ce9a2d83e3195f92fdf27f25acc462dff7d8d82", [:rebar3], [{:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8e0807d94a6927d1c3f8e76d584a2e3cdd9b4bcb02823eaf96629edf2a65b104"},
"stun": {:hex, :stun, "1.0.43", "5c58af3b87fde5e85552dfc085d565a8d899ef7bc0c3e97dea0ea45e3daf0a30", [:rebar3], [{:fast_tls, "1.1.12", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "6db03005a5f9931dc5c331d923cbe1c06b04f92921b47093dff8e3f0a18d7b36"},
"p1_acme": {:hex, :p1_acme, "1.0.13", "fec71df416004ce49e295f4846fe5ba3478b41fbe4f73a06b4a8fbc967d6e659", [:rebar3], [{:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.12", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "a2ce9d4904304df020c8e92e8577e0fc88f32623540656317c7e25440b4ac8d2"},
"p1_mysql": {:hex, :p1_mysql, "1.0.19", "22f1be58397780a7d580a954e7af66cde32a29dee1a24ab2aa196272fc654a4a", [:rebar3], [], "hexpm", "88f6cdb510e8959c14b6ae84ccda04967e3de239228f859d8341da67949622b1"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.10", "09ba1fbd447b1f480b223903e36d0415f21be592a1b00db964eea01285749028", [:rebar3], [], "hexpm", "c79cb61ababee4a8c85409b7f4932035797c093aeef1f9f53985e512b26f2a64"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.12", "10ae79eeb35ea98c0424a8b6420542fef9e4469eb12ccf41475d10840c291e68", [:rebar3], [], "hexpm", "32203f779e01cf0353270df24833a1d831ad7cb3e3e8e35a7556dfa1f40948d5"},
"p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"},
"pkix": {:hex, :pkix, "1.0.8", "98ea05243847fd4504f7c7a0cd82cecd1010ac327a082e1c674c5384006eae75", [:rebar3], [], "hexpm", "399508819501fab9d2e586dfa601b5ee3ef22b5612d3db58204dd2d089ef45d7"},
"stringprep": {:hex, :stringprep, "1.0.27", "02808c7024bc6285ca6a8a67e7addfc16f35dda55551a582c5181d8ea960e890", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "a5967b1144ca8002a58a03d16dd109fbd0bcdb82616cead2f983944314af6a00"},
"stun": {:hex, :stun, "1.0.44", "30b6b774864b24b05ba901291abe583bff19081e7c4efb3361df50b781ec9d3b", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "e45bba816cbefff01d820e49e66814f450df25a7a468a70d68d1e64218d46520"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"xmpp": {:hex, :xmpp, "1.5.3", "d61d8c90ffacd99bea4915c55dcee1829a053d43e0dc33b8ea66bab845cca8ed", [:rebar3], [{:ezlib, "1.0.9", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.12", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.46", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.22", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.25", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "dca72f926769ed512e524b4369c7812095205f39e00b7e8b11530174a10a9d35"},
"yconf": {:hex, :yconf, "1.0.11", "081fe70e5d73a74d0bbad91c3876a1be0e84bd8539bba4cefef2d61cd3c92f9e", [:rebar3], [{:fast_yaml, "1.0.31", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "6df4253e2d55592ad56b5cc29edb0981778ddb6656034a8625f70ffe24290a98"},
"xmpp": {:hex, :xmpp, "1.5.4", "6cd8144b3fe04745dc2cb3e746d6f2a963bb283db48a61f159b49cbe3fab8623", [:rebar3], [{:ezlib, "1.0.10", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.47", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.27", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "3bc2b5cb24e52964fb11641422ce2b7ba7c261dd50080689a1cbe3d952a9db35"},
"yconf": {:hex, :yconf, "1.0.12", "78c119d39bb805207fcb7671cb884805d75ee89c9ec98632b678f90a597dee2c", [:rebar3], [{:fast_yaml, "1.0.32", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "12faa51c281e95bcb6abf185fd034a242209621a7bb04b6cc411c867b192e207"},
}

View File

@ -13,8 +13,10 @@
{"Accept","Diterima"}.
{"Access denied by service policy","Akses ditolak oleh kebijakan layanan"}.
{"Access model of authorize","Model akses otorisasi"}.
{"Access model of open","Model akses terbuka"}.
{"Access model of presence","Model akses kehadiran"}.
{"Access model of roster","model akses daftar kontak"}.
{"Access model of whitelist","Model akses daftar putih"}.
{"Access model","Model akses"}.
{"Account doesn't exist","Akun tidak ada"}.
{"Action on user","Tindakan pada pengguna"}.
@ -34,7 +36,9 @@
{"Allow users to send invites","Perbolehkan pengguna mengirimkan undangan"}.
{"Allow users to send private messages","perbolehkan pengguna mengirimkan pesan ke pengguna lain secara pribadi"}.
{"Allow visitors to change nickname","Perbolehkan visitor mengganti nama julukan"}.
{"Allow visitors to send private messages to","Izinkan pengunjung mengirimkan pesan privat ke"}.
{"Allow visitors to send status text in presence updates","Izinkan pengunjung untuk mengirim teks status terbaru"}.
{"Allow visitors to send voice requests","Izinkan pengunjung mengirim permintaan suara"}.
{"Announcements","Pengumuman"}.
{"Answer associated with a picture","Jawaban yang berhubungan dengan gambar"}.
{"Answer associated with a video","Jawaban yang berhubungan dengan video"}.
@ -44,6 +48,7 @@
{"Anyone may publish","Siapapun dapat mempublikasi"}.
{"Anyone may subscribe and retrieve items","Siapapun dapat berlangganan dan mengambil item"}.
{"Anyone with Voice","Siapapun dengan fungsi suara"}.
{"Anyone","Siapapun"}.
{"April","April"}.
{"Attribute 'channel' is required for this request","Atribut 'channel' diperlukan untuk permintaan ini"}.
{"Attribute 'id' is mandatory for MIX messages","Atribut 'id' harus ada untuk pesan MIX"}.
@ -112,8 +117,9 @@
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Jangan beritahukan kata sandi Anda ke siapapun, bahkan ke administrator layanan XMPP."}.
{"Dump Backup to Text File at ","Dump Backup menjadi File Teks di "}.
{"Dump to Text File","Dump menjadi File Teks"}.
{"Duplicated groups are not allowed by RFC6121","Grup duplikat tidak diperbolehkan oleh RFC6121"}.
{"Edit Properties","Ganti Properti"}.
{"Either approve or decline the voice request.","Antara terima atau tolak permintaan suara"}.
{"Either approve or decline the voice request.","Antara terima atau tolak permintaan suara."}.
{"ejabberd HTTP Upload service","Layanan HTTP Upload ejabberd"}.
{"ejabberd MUC module","Modul MUC ejabberd"}.
{"ejabberd Multicast service","Layanan Multicast ejabberd"}.
@ -135,8 +141,10 @@
{"Enter path to jabberd14 spool file","Masukkan path ke file jabberd14 spool"}.
{"Enter path to text file","Masukkan path ke file teks"}.
{"Enter the text you see","Masukkan teks yang Anda lihat"}.
{"Erlang XMPP Server","Server Erlang XMPP"}.
{"Error","Kesalahan"}.
{"Exclude Jabber IDs from CAPTCHA challenge","Kecualikan Jabber IDs dari tantangan CAPTCHA"}.
{"Export all tables as SQL queries to a file:","Ekspor semua tabel sebagai kueri SQL ke file:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Ekspor data dari semua pengguna pada layanan ke berkas PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Ekspor data pengguna pada sebuah host ke berkas PIEFXIS (XEP-0227):"}.
{"External component failure","Kegagalan komponen eksternal"}.
@ -149,6 +157,7 @@
{"FAQ Entry","Entri FAQ"}.
{"February","Februari"}.
{"File larger than ~w bytes","File lebih besar dari ~w bytes"}.
{"Fill in the form to search for any matching XMPP User","Isi kolom untuk mencari pengguna XMPP"}.
{"Friday","Jumat"}.
{"From ~ts","Dari ~ts"}.
{"From","Dari"}.
@ -161,8 +170,11 @@
{"Get User Last Login Time","Lihat Waktu Login Terakhir Pengguna"}.
{"Get User Password","Dapatkan User Password"}.
{"Get User Statistics","Dapatkan Statistik Pengguna"}.
{"Given Name","Nama"}.
{"Grant voice to this person?","Ijinkan akses suara kepadanya?"}.
{"Group","Grup"}.
{"Groups that will be displayed to the members","Grup yang akan ditampilkan kepada anggota"}.
{"Groups","Grup"}.
{"has been banned","telah dibanned"}.
{"has been kicked because of a system shutdown","telah dikick karena sistem shutdown"}.
{"has been kicked because of an affiliation change","telah dikick karena perubahan afiliasi"}.
@ -170,17 +182,27 @@
{"has been kicked","telah dikick"}.
{"Host unknown","Host tidak dikenal"}.
{"Host","Host"}.
{"HTTP File Upload","Unggah Berkas HTTP"}.
{"Idle connection","Koneksi menganggur"}.
{"If you don't see the CAPTCHA image here, visit the web page.","Jika Anda tidak melihat gambar CAPTCHA disini, silahkan kunjungi halaman web."}.
{"Import Directory","Impor Direktori"}.
{"Import File","Impor File"}.
{"Import user data from jabberd14 spool file:","Impor data pengguna dari sekumpulan berkas jabberd14:"}.
{"Import User from File at ","Impor Pengguna dari File pada"}.
{"Import User from File at ","Impor Pengguna dari File pada "}.
{"Import users data from a PIEFXIS file (XEP-0227):","impor data-data pengguna dari sebuah PIEFXIS (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","Импорт пользовательских данных из буферной директории jabberd14:"}.
{"Import Users from Dir at ","Impor Pengguna dari Dir di"}.
{"Import Users from Dir at ","Impor Pengguna dari Dir di "}.
{"Import Users From jabberd14 Spool Files","Impor Pengguna Dari jabberd14 Spool File"}.
{"Improper message type","Jenis pesan yang tidak benar"}.
{"Incoming s2s Connections:","Koneksi s2s masuk:"}.
{"Incorrect CAPTCHA submit","Isian CAPTCHA salah"}.
{"Incorrect data form","Formulir data salah"}.
{"Incorrect password","Kata sandi salah"}.
{"Incorrect value of 'action' attribute","Nilai atribut 'aksi' salah"}.
{"Incorrect value of 'action' in data form","Nilai 'tindakan' yang salah dalam formulir data"}.
{"Insufficient privilege","Hak tidak mencukupi"}.
{"Internal server error","Galat server internal"}.
{"Invalid node name","Nama node tidak valid"}.
{"IP addresses","Alamat IP"}.
{"is now known as","sekarang dikenal sebagai"}.
{"It is not allowed to send private messages of type \"groupchat\"","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi jenis \"groupchat \""}.
@ -217,15 +239,17 @@
{"Moderator privileges required","Hak istimewa moderator dibutuhkan"}.
{"Modified modules","Modifikasi modul-modul"}.
{"Monday","Senin"}.
{"Multiple <item/> elements are not allowed by RFC6121","Beberapa elemen <item/> tidak diizinkan oleh RFC6121"}.
{"Name","Nama"}.
{"Name:","Nama:"}.
{"Never","Tidak Pernah"}.
{"New Password:","Password Baru:"}.
{"Nickname Registration at ","Pendaftaran Julukan pada"}.
{"Nickname Registration at ","Pendaftaran Julukan pada "}.
{"Nickname ~s does not exist in the room","Nama Julukan ~s tidak berada di dalam ruangan"}.
{"Nickname","Nama Julukan"}.
{"No body provided for announce message","Tidak ada isi pesan yang disediakan untuk mengirimkan pesan"}.
{"No Data","Tidak Ada Data"}.
{"No <forwarded/> element found","Tidak ada elemen <forwarded/> yang ditemukan"}.
{"No limit","Tidak terbatas"}.
{"Node ID","ID Node"}.
{"Node not found","Node tidak ditemukan"}.
@ -259,28 +283,45 @@
{"Outgoing s2s Connections:","Koneksi s2s yang keluar:"}.
{"Owner privileges required","Hak istimewa owner dibutuhkan"}.
{"Packet","Paket"}.
{"Participant","Partisipan"}.
{"Password Verification:","Verifikasi Kata Sandi:"}.
{"Password Verification","Verifikasi Sandi"}.
{"Password:","Kata Sandi:"}.
{"Password","Sandi"}.
{"Path to Dir","Jalur ke Dir"}.
{"Path to File","Jalur ke File"}.
{"Payload type","Tipe payload"}.
{"Pending","Tertunda"}.
{"Period: ","Periode:"}.
{"Period: ","Periode: "}.
{"Persist items to storage","Pertahankan item ke penyimpanan"}.
{"Persistent","Persisten"}.
{"Ping query is incorrect","Kueri ping salah"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Harap dicatat bahwa pilihan ini hanya akan membuat cadangan builtin Mnesia database. Jika Anda menggunakan modul ODBC, anda juga perlu untuk membuat cadangan database SQL Anda secara terpisah."}.
{"Pong","Pong"}.
{"Present real Jabber IDs to","Tampilkan Jabber ID secara lengkap"}.
{"Previous session not found","Sesi sebelumnya tidak ditemukan"}.
{"Previous session PID has been killed","Sesi PID sebelumnya telah dimatikan"}.
{"Previous session PID has exited","Sesi PID sebelumnya telah keluar"}.
{"Previous session PID is dead","Sesi PID sebelumnya mati"}.
{"Previous session timed out","Sesi sebelumnya habis waktu"}.
{"private, ","pribadi, "}.
{"Public","Publik"}.
{"Publish model","Model penerbitan"}.
{"Publish-Subscribe","Setujui-Pertemanan"}.
{"PubSub subscriber request","Permintaan pertemanan PubSub"}.
{"Purge all items when the relevant publisher goes offline","Bersihkan semua item ketika penerbit yang relevan telah offline"}.
{"Queries to the conference members are not allowed in this room","Permintaan untuk para anggota konferensi tidak diperbolehkan di ruangan ini"}.
{"Query to another users is forbidden","Kueri ke pengguna lain dilarang"}.
{"RAM and disc copy","RAM dan disc salinan"}.
{"RAM copy","Salinan RAM"}.
{"Really delete message of the day?","Benar-benar ingin menghapus pesan harian?"}.
{"Receive notification from all descendent nodes","Terima notifikasi dari semua node turunan"}.
{"Receive notification from direct child nodes only","Terima notifikasi dari child node saja"}.
{"Receive notification of new items only","Terima notifikasi dari item baru saja"}.
{"Receive notification of new nodes only","Terima notifikasi dari node baru saja"}.
{"Recipient is not in the conference room","Penerima tidak berada di ruangan konferensi"}.
{"Register an XMPP account","Daftarkan sebuah akun XMPP"}.
{"Registered Users","Pengguna Terdaftar"}.
{"Registered Users:","Pengguna Terdaftar:"}.
{"Register","Mendaftar"}.
@ -289,28 +330,39 @@
{"Remove User","Hapus Pengguna"}.
{"Remove","Menghapus"}.
{"Replaced by new connection","Diganti dengan koneksi baru"}.
{"Request has timed out","Waktu permintaan telah habis"}.
{"Request is ignored","Permintaan diabaikan"}.
{"Requested role","Peran yang diminta"}.
{"Resources","Sumber daya"}.
{"Restart Service","Restart Layanan"}.
{"Restart","Jalankan Ulang"}.
{"Restore Backup from File at ","Kembalikan Backup dari File pada"}.
{"Restore Backup from File at ","Kembalikan Backup dari File pada "}.
{"Restore binary backup after next ejabberd restart (requires less memory):","Mengembalikan cadangan yang berpasanagn setelah ejabberd berikutnya dijalankan ulang (memerlukan memori lebih sedikit):"}.
{"Restore binary backup immediately:","Segera mengembalikan cadangan yang berpasangan:"}.
{"Restore plain text backup immediately:","Segera mengembalikan cadangan teks biasa:"}.
{"Restore","Mengembalikan"}.
{"Roles that May Send Private Messages","Peran yang Dapat Mengirim Pesan Pribadi"}.
{"Room Configuration","Konfigurasi Ruangan"}.
{"Room creation is denied by service policy","Pembuatan Ruangan ditolak oleh kebijakan layanan"}.
{"Room description","Keterangan ruangan"}.
{"Room Occupants","Penghuni Ruangan"}.
{"Room terminates","Ruang dihentikan"}.
{"Room title","Nama Ruangan"}.
{"Roster groups allowed to subscribe","Kelompok kontak yang diizinkan untuk berlangganan"}.
{"Roster of ~ts","Daftar ~ts"}.
{"Roster size","Ukuran Daftar Kontak"}.
{"Roster:","Daftar:"}.
{"RPC Call Error","Panggilan Kesalahan RPC"}.
{"Running Nodes","Menjalankan Node"}.
{"~s invites you to the room ~s","~s mengundang anda masuk kamar ~s"}.
{"Saturday","Sabtu"}.
{"Script check","Periksa naskah"}.
{"Search Results for ","Hasil Pencarian untuk"}.
{"Search users in ","Pencarian pengguna dalam"}.
{"Search from the date","Cari dari tanggal"}.
{"Search Results for ","Hasil Pencarian untuk "}.
{"Search the text","Cari teks"}.
{"Search until the date","Cari sampai tanggal"}.
{"Search users in ","Pencarian pengguna dalam "}.
{"Select All","Pilih Semua"}.
{"Send announcement to all online users on all hosts","Kirim pengumuman untuk semua pengguna yang online pada semua host"}.
{"Send announcement to all online users","Kirim pengumuman untuk semua pengguna yang online"}.
{"Send announcement to all users on all hosts","Kirim pengumuman untuk semua pengguna pada semua host"}.
@ -323,9 +375,11 @@
{"Show Integral Table","Tampilkan Tabel Terpisah"}.
{"Show Ordinary Table","Tampilkan Tabel Normal"}.
{"Shut Down Service","Shut Down Layanan"}.
{"SOCKS5 Bytestreams","SOCKS5 Bytestreams"}.
{"Specify the access model","Tentukan model akses"}.
{"Specify the event message type","Tentukan jenis acara pesan"}.
{"Specify the publisher model","Tentukan model penerbitan"}.
{"Stanza ID","ID Stanza"}.
{"Statistics of ~p","statistik dari ~p"}.
{"Statistics","Statistik"}.
{"Stop","Hentikan"}.
@ -333,18 +387,35 @@
{"Storage Type","Jenis Penyimpanan"}.
{"Store binary backup:","Penyimpanan cadangan yang berpasangan:"}.
{"Store plain text backup:","Simpan cadangan teks biasa:"}.
{"Stream management is already enabled","Manajemen stream sudah diaktifkan"}.
{"Stream management is not enabled","Manajemen stream tidak diaktifkan"}.
{"Subject","Subyek"}.
{"Submit","Serahkan"}.
{"Submitted","Ulangi masukan"}.
{"Subscriber Address","Alamat Pertemanan"}.
{"Subscribers may publish","Pelanggan dapat mempublikasikan"}.
{"Subscription","Berlangganan"}.
{"Subscriptions are not allowed","Langganan tidak diperbolehkan"}.
{"Sunday","Minggu"}.
{"Text associated with a picture","Teks yang terkait dengan gambar"}.
{"Text associated with a sound","Teks yang terkait dengan suara"}.
{"Text associated with a video","Teks yang terkait dengan video"}.
{"Text associated with speech","Teks yang terkait dengan ucapan"}.
{"That nickname is already in use by another occupant","Julukan itu sudah digunakan oleh penghuni lain"}.
{"That nickname is registered by another person","Julukan tersebut telah didaftarkan oleh orang lain"}.
{"The account was not unregistered","Akun tidak terdaftar"}.
{"The CAPTCHA is valid.","Captcha ini benar."}.
{"The CAPTCHA verification has failed","Verifikasi CAPTCHA telah gagal"}.
{"The captcha you entered is wrong","Isian captcha yang anda masukkan salah"}.
{"The collections with which a node is affiliated","Koleksi dengan yang berafiliasi dengan sebuah node"}.
{"The JID of the node creator","JID dari pembuat node"}.
{"The JIDs of those to contact with questions","JID dari mereka untuk dihubungi dengan pertanyaan"}.
{"The JIDs of those with an affiliation of owner","JID dari mereka yang memiliki afiliasi pemilik"}.
{"The JIDs of those with an affiliation of publisher","JID dari mereka yang memiliki afiliasi penerbit"}.
{"The name of the node","Nama node"}.
{"The node is a collection node","Node adalah node koleksi"}.
{"The node is a leaf node (default)","Node adalah leaf node (default)"}.
{"The NodeID of the relevant node","NodeID dari node yang relevan"}.
{"The number of subscribers to the node","Jumlah pendaftar di node"}.
{"The number of unread or undelivered messages","Jumlah pesan yang belum dibaca atau tidak terkirim"}.
{"The password contains unacceptable characters","Kata sandi mengandung karakter yang tidak dapat diterima"}.
@ -352,10 +423,12 @@
{"the password is","kata sandinya"}.
{"The password was not changed","Kata sandi belum berubah"}.
{"The passwords are different","Kata sandi berbeda"}.
{"The username is not valid","Nama pengguna tidak valid"}.
{"There was an error changing the password: ","Ada kesalahan saat merubah kata kunci: "}.
{"There was an error creating the account: ","Ada kesalahan saat membuat akun: "}.
{"There was an error deleting the account: ","Ada kesalahan saat menghapus akun:"}.
{"There was an error deleting the account: ","Ada kesalahan saat menghapus akun: "}.
{"This room is not anonymous","Ruangan ini tidak dikenal"}.
{"This service can not process the address: ~s","Layanan ini tidak dapat memproses alamat: ~s"}.
{"Thursday","Kamis"}.
{"Time delay","Waktu tunda"}.
{"Time","Waktu"}.
@ -363,10 +436,13 @@
{"To ~ts","Kepada ~ts"}.
{"Token TTL","TTL Token"}.
{"To","Kepada"}.
{"Too many active bytestreams","Terlalu banyak bytestream aktif"}.
{"Too many CAPTCHA requests","Terlalu banyak permintaan CAPTCHA"}.
{"Too many child elements","Terlalu banyak elemen turunan"}.
{"Too many <item/> elements","Terlalu banyak <item/> elemen"}.
{"Too many <list/> elements","Terlalu banyak <list/> elemen"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Terlalu banyak (~p) percobaan otentifikasi yang gagal dari alamat IP (~s). Alamat akan di unblok pada ~s UTC"}.
{"Too many receiver fields were specified","Terlalu banyak bidang penerima yang ditentukan"}.
{"Too many users in this conference","Terlalu banyak pengguna di grup ini"}.
{"Total rooms","Total kamar"}.
{"Traffic rate limit is exceeded","Batas tingkat lalu lintas terlampaui"}.
@ -377,6 +453,7 @@
{"~ts's Offline Messages Queue","~ts's antrian Pesan Offline"}.
{"Tuesday","Selasa"}.
{"Unable to generate a CAPTCHA","Tidak dapat menghasilkan CAPTCHA"}.
{"Unable to register route on existing local domain","Tidak dapat mendaftarkan rute di domain lokal yang ada"}.
{"Unauthorized","Ditolak"}.
{"Unexpected action","Aksi yang tidak diharapkan"}.
{"Unexpected error condition: ~p","Kondisi kerusakan yang tidak diduga: ~p"}.
@ -414,7 +491,7 @@
{"vCard User Search","vCard Pencarian Pengguna"}.
{"View Queue","Lihat antrian"}.
{"View Roster","Lihat daftar kontak"}.
{"Virtual Hosts","Virtual Hosts"}.
{"Virtual Hosts","Host Virtual"}.
{"Visitors are not allowed to change their nicknames in this room","Tamu tidak diperbolehkan untuk mengubah nama panggilan di ruangan ini"}.
{"Visitors are not allowed to send messages to all occupants","Tamu tidak diperbolehkan untuk mengirim pesan ke semua penghuni"}.
{"Visitor","Tamu"}.
@ -428,11 +505,14 @@
{"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually","Apakah entitas ingin menerima ringkasan(agregasi) pemberitahuan atau semua pemberitahuan satu per satu"}.
{"Whether an entity wants to receive or disable notifications","Apakah entitas ingin menerima atau menonaktifkan pemberitahuan"}.
{"Whether owners or publisher should receive replies to items","Apakah pemilik atau penerbit harus menerima balasan dari item"}.
{"Whether the node is a leaf (default) or a collection","Apakah node adalah leaf (default) atau koleksi"}.
{"Whether to allow subscriptions","Apakah diperbolehkan untuk berlangganan"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Apakah akan menjadikan semua langganan sementara, berdasarkan keberadaan pelanggan"}.
{"Whether to notify owners about new subscribers and unsubscribes","Apakah akan memberi tahu pemilik tentang pelanggan baru dan berhenti berlangganan"}.
{"Who may associate leaf nodes with a collection","Siapa yang dapat mengaitkan leaf node dengan koleksi"}.
{"Wrong parameters in the web formulary","Parameter yang salah di formula web"}.
{"Wrong xmlns","xmlns salah"}.
{"XMPP Account Registration","Pendaftaran Akun XMPP"}.
{"XMPP Domains","Domain XMPP"}.
{"XMPP Show Value of Away","XMPP menunjukkan status Away"}.
{"XMPP Show Value of Chat","XMPP menunjukkan status Chat"}.

View File

@ -3,6 +3,7 @@
%% To improve translations please read:
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," Riempire il modulo per la ricerca di utenti Jabber corrispondenti ai criteri (Aggiungere * alla fine del campo per la ricerca di una sottostringa"}.
{" has set the subject to: "," ha modificato l'oggetto in: "}.
{"A friendly name for the node","Un nome comodo per il nodo"}.
{"A password is required to enter this room","Per entrare in questa stanza è prevista una password"}.

View File

@ -3,103 +3,662 @@
%% To improve translations please read:
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," (Adicione * no final do campo para combinar com a substring)"}.
{" has set the subject to: "," colocou o tópico: "}.
{"# participants","# participantes"}.
{"A description of the node","Uma descrição do nó"}.
{"A friendly name for the node","Um nome familiar para o nó"}.
{"A password is required to enter this room","Se necessita palavra-passe para entrar nesta sala"}.
{"A Web Page","Uma página da web"}.
{"Accept","Aceito"}.
{"Access denied by service policy","Acesso negado pela política de serviço"}.
{"Access model of authorize","Modelo de acesso da autorização"}.
{"Access model of open","Modelo para acesso aberto"}.
{"Access model of presence","Modelo para acesso presença"}.
{"Access model of roster","Modelo para acesso lista"}.
{"Access model of whitelist","Modelo de acesso da lista branca"}.
{"Access model","Modelo de acesso"}.
{"Account doesn't exist","A conta não existe"}.
{"Action on user","Acção no utilizador"}.
{"Add Jabber ID","Adicionar ID jabber"}.
{"Add New","Adicionar novo"}.
{"Add User","Adicionar utilizador"}.
{"Administration of ","Administração de "}.
{"Administration","Administração"}.
{"Administrator privileges required","São necessários privilégios de administrador"}.
{"All activity","Todas atividades"}.
{"All Users","Todos os utilizadores"}.
{"Allow subscription","Permitir a assinatura"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Autorizar este Jabber ID para a inscrição neste tópico pubsub?"}.
{"Allow this person to register with the room?","Permita que esta pessoa se registe na sala?"}.
{"Allow users to change the subject","Permitir a utilizadores modificar o assunto"}.
{"Allow users to query other users","Permitir a utilizadores pesquisar informações sobre os demais"}.
{"Allow users to send invites","Permitir a utilizadores envio de convites"}.
{"Allow users to send private messages","Permitir a utilizadores enviarem mensagens privadas"}.
{"Allow visitors to change nickname","Permitir mudança de apelido aos visitantes"}.
{"Allow visitors to send private messages to","Permitir visitantes enviar mensagem privada para"}.
{"Allow visitors to send status text in presence updates","Permitir atualizações de estado aos visitantes"}.
{"Allow visitors to send voice requests","Permitir aos visitantes o envio de requisições de voz"}.
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Um grupo LDAP associado que define a adesão à sala; este deve ser um Nome Distinto LDAP de acordo com uma definição específica da implementação ou da implantação específica de um grupo."}.
{"Announcements","Anúncios"}.
{"Answer associated with a picture","Resposta associada com uma foto"}.
{"Answer associated with a video","Resposta associada com um vídeo"}.
{"Answer associated with speech","Resposta associada com a fala"}.
{"Answer to a question","Resposta para uma pergunta"}.
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Qualquer pessoa do(s) grupo(s) informado(s) podem se inscrever e recuperar itens"}.
{"Anyone may associate leaf nodes with the collection","Qualquer pessoa pode associar nós das páginas à coleção"}.
{"Anyone may publish","Qualquer pessoa pode publicar"}.
{"Anyone may subscribe and retrieve items","Qualquer pessoa pode se inscrever e recuperar os itens"}.
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Qualquer pessoa com uma assinatura presente dos dois ou de ambos pode se inscrever e recuperar os itens"}.
{"Anyone with Voice","Qualquer pessoa com voz"}.
{"Anyone","Qualquer pessoa"}.
{"April","Abril"}.
{"Attribute 'channel' is required for this request","O atributo 'canal' é necessário para esta solicitação"}.
{"Attribute 'id' is mandatory for MIX messages","O atributo 'id' é obrigatório para mensagens MIX"}.
{"Attribute 'jid' is not allowed here","O atributo 'jid' não é permitido aqui"}.
{"Attribute 'node' is not allowed here","O Atributo 'nó' não é permitido aqui"}.
{"Attribute 'to' of stanza that triggered challenge","O atributo 'para' da estrofe que desencadeou o desafio"}.
{"August","Agosto"}.
{"Automatic node creation is not enabled","Criação automatizada de nós está desativada"}.
{"Backup Management","Gestão de cópias de segurança"}.
{"Backup of ~p","Backup de ~p"}.
{"Backup to File at ","Guardar cópia de segurança para ficheiro em "}.
{"Backup","Guardar cópia de segurança"}.
{"Bad format","Formato incorreto"}.
{"Birthday","Data de nascimento"}.
{"Both the username and the resource are required","Nome de utilizador e recurso são necessários"}.
{"Bytestream already activated","Bytestream já foi ativado"}.
{"Cannot remove active list","Não é possível remover uma lista ativa"}.
{"Cannot remove default list","Não é possível remover uma lista padrão"}.
{"CAPTCHA web page","CAPTCHA web page"}.
{"Challenge ID","ID do desafio"}.
{"Change Password","Mudar palavra-chave"}.
{"Change User Password","Alterar Palavra-passe do Utilizador"}.
{"Changing password is not allowed","Não é permitida a alteração da palavra-passe"}.
{"Changing role/affiliation is not allowed","Não é permitida a alteração da função/afiliação"}.
{"Channel already exists","O canal já existe"}.
{"Channel does not exist","O canal não existe"}.
{"Channels","Canais"}.
{"Characters not allowed:","Caracteres não aceitos:"}.
{"Chatroom configuration modified","Configuração da sala de bate-papo modificada"}.
{"Chatroom is created","A sala de chat está criada"}.
{"Chatroom is destroyed","A sala de chat está destruída"}.
{"Chatroom is started","A sala de chat está iniciada"}.
{"Chatroom is stopped","A sala de chat está parada"}.
{"Chatrooms","Salas de Chat"}.
{"Choose a username and password to register with this server","Escolha um nome de utilizador e palavra-chave para se registar neste servidor"}.
{"Choose storage type of tables","Seleccione o tipo de armazenagem das tabelas"}.
{"Choose whether to approve this entity's subscription.","Aprovar esta assinatura."}.
{"City","Cidade"}.
{"Client acknowledged more stanzas than sent by server","O cliente reconheceu mais estrofes do que as enviadas pelo servidor"}.
{"Commands","Comandos"}.
{"Conference room does not exist","A sala não existe"}.
{"Configuration of room ~s","Configuração para ~s"}.
{"Configuration","Configuração"}.
{"Connected Resources:","Recursos conectados:"}.
{"Contact Addresses (normally, room owner or owners)","Endereços de contato (normalmente, o proprietário ou os proprietários da sala)"}.
{"Country","País"}.
{"CPU Time:","Tempo da CPU:"}.
{"Current Discussion Topic","Assunto em discussão"}.
{"Database failure","Falha no banco de dados"}.
{"Database Tables at ~p","Tabelas da Base de dados em ~p"}.
{"Database Tables Configuration at ","Configuração de Tabelas de Base de dados em "}.
{"Database","Base de dados"}.
{"December","Dezembro"}.
{"Default users as participants","Utilizadores padrões como participantes"}.
{"Delete content","Apagar o conteúdo"}.
{"Delete message of the day on all hosts","Apagar a mensagem do dia em todos os hosts"}.
{"Delete message of the day","Apagar mensagem do dia"}.
{"Delete Selected","Eliminar os seleccionados"}.
{"Delete table","Apagar a tabela"}.
{"Delete User","Deletar Utilizador"}.
{"Deliver event notifications","Entregar as notificações de evento"}.
{"Deliver payloads with event notifications","Enviar payloads junto com as notificações de eventos"}.
{"Description:","Descrição:"}.
{"Disc only copy","Cópia apenas em disco"}.
{"'Displayed groups' not added (they do not exist!): ","Os 'Grupos exibidos' não foi adicionado (eles não existem!): "}.
{"Displayed:","Exibido:"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Não revele a sua palavra-passe a ninguém, nem mesmo para o administrador deste servidor XMPP."}.
{"Dump Backup to Text File at ","Exporta cópia de segurança para ficheiro de texto em "}.
{"Dump to Text File","Exportar para ficheiro de texto"}.
{"Duplicated groups are not allowed by RFC6121","Os grupos duplicados não são permitidos pela RFC6121"}.
{"Dynamically specify a replyto of the item publisher","Definir de forma dinâmica uma resposta da editora do item"}.
{"Edit Properties","Editar propriedades"}.
{"Either approve or decline the voice request.","Deve aprovar/desaprovar a requisição de voz."}.
{"ejabberd HTTP Upload service","serviço HTTP de upload ejabberd"}.
{"ejabberd MUC module","Módulo MUC de ejabberd"}.
{"ejabberd Multicast service","Serviço multicast ejabberd"}.
{"ejabberd Publish-Subscribe module","Módulo para Publicar Tópicos do ejabberd"}.
{"ejabberd SOCKS5 Bytestreams module","Modulo ejabberd SOCKS5 Bytestreams"}.
{"ejabberd vCard module","Módulo vCard de ejabberd"}.
{"ejabberd Web Admin","ejabberd Web Admin"}.
{"ejabberd","ejabberd"}.
{"Elements","Elementos"}.
{"Email Address","Endereço de e-mail"}.
{"Email","Email"}.
{"Enable logging","Permitir criação de logs"}.
{"Enable message archiving","Ativar arquivamento de mensagens"}.
{"Enabling push without 'node' attribute is not supported","Abilitar push sem o atributo 'node' não é suportado"}.
{"End User Session","Terminar Sessão do Utilizador"}.
{"Enter nickname you want to register","Introduza a alcunha que quer registar"}.
{"Enter path to backup file","Introduza o caminho do ficheiro de cópia de segurança"}.
{"Enter path to jabberd14 spool dir","Introduza o caminho para o directório de spools do jabberd14"}.
{"Enter path to jabberd14 spool file","Introduza o caminho para o ficheiro de spool do jabberd14"}.
{"Enter path to text file","Introduza caminho para o ficheiro de texto"}.
{"Enter the text you see","Insira o texto que vê"}.
{"Erlang XMPP Server","Servidor XMPP Erlang"}.
{"Error","Erro"}.
{"Exclude Jabber IDs from CAPTCHA challenge","Excluir IDs Jabber de serem submetidos ao CAPTCHA"}.
{"Export all tables as SQL queries to a file:","Exportar todas as tabelas como SQL para um ficheiro:"}.
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Exportar todos os dados de todos os utilizadores no servidor, para ficheiros de formato PIEFXIS (XEP-0227):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Exportar dados dos utilizadores num host, para ficheiros de PIEFXIS (XEP-0227):"}.
{"External component failure","Falha de componente externo"}.
{"External component timeout","Tempo esgotado à espera de componente externo"}.
{"Failed to activate bytestream","Falha ao ativar bytestream"}.
{"Failed to extract JID from your voice request approval","Não foi possível extrair o JID (Jabber ID) da requisição de voz"}.
{"Failed to map delegated namespace to external component","Falha ao mapear namespace delegado ao componente externo"}.
{"Failed to parse HTTP response","Falha ao analisar resposta HTTP"}.
{"Failed to process option '~s'","Falha ao processar opção '~s'"}.
{"Family Name","Apelido"}.
{"FAQ Entry","Registo das perguntas frequentes"}.
{"February","Fevereiro"}.
{"File larger than ~w bytes","Ficheiro é maior que ~w bytes"}.
{"Fill in the form to search for any matching XMPP User","Preencha campos para procurar por quaisquer utilizadores XMPP"}.
{"Friday","Sexta"}.
{"From ~ts","De ~s"}.
{"From","De"}.
{"Full List of Room Admins","Lista completa dos administradores das salas"}.
{"Full List of Room Owners","Lista completa dos proprietários das salas"}.
{"Full Name","Nome completo"}.
{"Get Number of Online Users","Obter quantidade de utilizadores online"}.
{"Get Number of Registered Users","Obter quantidade de utilizadores registados"}.
{"Get Pending","Obter os pendentes"}.
{"Get User Last Login Time","Obter a data do último login"}.
{"Get User Password","Obter palavra-passe do utilizador"}.
{"Get User Statistics","Obter estatísticas do utilizador"}.
{"Given Name","Sobrenome"}.
{"Grant voice to this person?","Dar voz a esta pessoa?"}.
{"Group","Grupo"}.
{"Groups that will be displayed to the members","Os grupos que serão exibidos para os membros"}.
{"Groups","Grupos"}.
{"has been banned","foi banido"}.
{"has been kicked because of a system shutdown","foi desconectado porque o sistema foi desligado"}.
{"has been kicked because of an affiliation change","foi desconectado porque por afiliação inválida"}.
{"has been kicked because the room has been changed to members-only","foi desconectado porque a política da sala mudou, só membros são permitidos"}.
{"has been kicked","foi removido"}.
{"Host unknown","Máquina desconhecida"}.
{"Host","Máquina"}.
{"HTTP File Upload","Upload de ficheiros por HTTP"}.
{"Idle connection","Conexão inativa"}.
{"If you don't see the CAPTCHA image here, visit the web page.","Se não conseguir ver o CAPTCHA aqui, visite a web page."}.
{"Import Directory","Importar directório"}.
{"Import File","Importar ficheiro"}.
{"Import user data from jabberd14 spool file:","Importar dados dos utilizadores de uma fila jabberd14:"}.
{"Import User from File at ","Importar utilizador a partir do ficheiro em "}.
{"Import users data from a PIEFXIS file (XEP-0227):","Importe os utilizadores de um ficheiro PIEFXIS (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","Importar dados dos utilizadores de um diretório-fila jabberd14:"}.
{"Import Users from Dir at ","Importar utilizadores a partir do directório em "}.
{"Import Users From jabberd14 Spool Files","Importar utilizadores de ficheiros de jabberd14 (spool files)"}.
{"Improper domain part of 'from' attribute","Atributo 'from' contém domínio incorreto"}.
{"Improper message type","Tipo de mensagem incorrecto"}.
{"Incoming s2s Connections:","Conexões s2s de Entrada:"}.
{"Incorrect CAPTCHA submit","CAPTCHA submetido incorretamente"}.
{"Incorrect data form","Formulário dos dados incorreto"}.
{"Incorrect password","Palavra-chave incorrecta"}.
{"Incorrect value of 'action' attribute","Valor incorreto do atributo 'action'"}.
{"Incorrect value of 'action' in data form","Valor incorreto de 'action' no formulário de dados"}.
{"Incorrect value of 'path' in data form","Valor incorreto de 'path' no formulário de dados"}.
{"Insufficient privilege","Privilégio insuficiente"}.
{"Internal server error","Erro interno do servidor"}.
{"Invalid 'from' attribute in forwarded message","Atributo 'from' inválido na mensagem reenviada"}.
{"Invalid node name","Nome do nó inválido"}.
{"Invalid 'previd' value","Valor 'previd' inválido"}.
{"Invitations are not allowed in this conference","Os convites não são permitidos nesta conferência"}.
{"IP addresses","Endereços IP"}.
{"is now known as","é agora conhecido como"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro para a sala. O membro (~s) enviou uma mensagem de erro (~s) e foi expulso da sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir o envio de mensagens privadas para a sala"}.
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
{"Jabber ID","ID Jabber"}.
{"January","Janeiro"}.
{"JID normalization denied by service policy","Normalização JID negada por causa da política de serviços"}.
{"JID normalization failed","A normalização JID falhou"}.
{"joins the room","Entrar na sala"}.
{"July","Julho"}.
{"June","Junho"}.
{"Just created","Acabou de ser criado"}.
{"Label:","Rótulo:"}.
{"Last Activity","Última actividade"}.
{"Last login","Último login"}.
{"Last message","Última mensagem"}.
{"Last month","Último mês"}.
{"Last year","Último ano"}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Bits menos significativos do hash sha-256 do texto devem ser iguais ao rótulo hexadecimal"}.
{"leaves the room","Sair da sala"}.
{"List of rooms","Lista de salas"}.
{"Logging","Registando no log"}.
{"Low level update script","Script de atualização low level"}.
{"Make participants list public","Tornar pública a lista de participantes"}.
{"Make room CAPTCHA protected","Tornar protegida a palavra-passe da sala"}.
{"Make room members-only","Tornar sala apenas para membros"}.
{"Make room moderated","Tornar a sala moderada"}.
{"Make room password protected","Tornar sala protegida à palavra-passe"}.
{"Make room persistent","Tornar sala persistente"}.
{"Make room public searchable","Tornar sala pública possível de ser encontrada"}.
{"Malformed username","Nome de utilizador inválido"}.
{"MAM preference modification denied by service policy","Modificação de preferência MAM negada por causa da política de serviços"}.
{"March","Março"}.
{"Max # of items to persist","Máximo # de elementos que persistem"}.
{"Max payload size in bytes","Máximo tamanho do payload em bytes"}.
{"Maximum file size","Tamanho máximo do ficheiro"}.
{"Maximum Number of History Messages Returned by Room","Quantidade máxima das mensagens do histórico que foram devolvidas por sala"}.
{"Maximum number of items to persist","Quantidade máxima dos itens para manter"}.
{"Maximum Number of Occupants","Quantidate máxima de participantes"}.
{"May","Maio"}.
{"Members not added (inexistent vhost!): ","Membros que não foram adicionados (o vhost não existe!): "}.
{"Membership is required to enter this room","É necessário ser membro desta sala para poder entrar"}.
{"Members:","Membros:"}.
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","Memorize a sua palavra-passe ou anote-a num papel guardado num local seguro. No XMPP, não há uma maneira automatizada de recuperar a sua palavra-passe caso a esqueça."}.
{"Memory","Memória"}.
{"Mere Availability in XMPP (No Show Value)","Mera disponibilidade no XMPP (Sem valor para ser exibido)"}.
{"Message body","Corpo da mensagem"}.
{"Message not found in forwarded payload","Mensagem não encontrada em conteúdo encaminhado"}.
{"Messages from strangers are rejected","As mensagens vindas de estranhos são rejeitadas"}.
{"Messages of type headline","Mensagens do tipo do título"}.
{"Messages of type normal","Mensagens do tipo normal"}.
{"Middle Name","Segundo nome"}.
{"Minimum interval between voice requests (in seconds)","O intervalo mínimo entre requisições de voz (em segundos)"}.
{"Moderator privileges required","São necessários privilégios de moderador"}.
{"Moderator","Moderador"}.
{"Moderators Only","Somente moderadores"}.
{"Modified modules","Módulos atualizados"}.
{"Module failed to handle the query","Módulo falhou ao processar a consulta"}.
{"Monday","Segunda"}.
{"Multicast","Multicast"}.
{"Multiple <item/> elements are not allowed by RFC6121","Vários elementos <item/> não são permitidos pela RFC6121"}.
{"Multi-User Chat","Chat multi-utilizador"}.
{"Name in the rosters where this group will be displayed","O nome nas listas onde este grupo será exibido"}.
{"Name","Nome"}.
{"Name:","Nome:"}.
{"Natural Language for Room Discussions","Idioma nativo para as discussões na sala"}.
{"Natural-Language Room Name","Nome da sala no idioma nativo"}.
{"Neither 'jid' nor 'nick' attribute found","Nem o atributo 'jid' nem 'nick' foram encontrados"}.
{"Neither 'role' nor 'affiliation' attribute found","Nem o atributo 'role' nem 'affiliation' foram encontrados"}.
{"Never","Nunca"}.
{"New Password:","Nova Palavra-passe:"}.
{"Nickname can't be empty","O apelido não pode ser vazio"}.
{"Nickname Registration at ","Registo da alcunha em "}.
{"Nickname ~s does not exist in the room","A alcunha ~s não existe na sala"}.
{"Nickname","Alcunha"}.
{"No address elements found","Nenhum elemento endereço foi encontrado"}.
{"No addresses element found","Nenhum elemento endereços foi encontrado"}.
{"No 'affiliation' attribute found","Atributo 'affiliation' não foi encontrado"}.
{"No available resource found","Nenhum recurso disponível foi encontrado"}.
{"No body provided for announce message","Nenhum corpo de texto fornecido para anunciar mensagem"}.
{"No child elements found","Nenhum elemento filho foi encontrado"}.
{"No data form found","Nenhum formulário de dados foi encontrado"}.
{"No Data","Nenhum dado"}.
{"No features available","Nenhuma funcionalidade disponível"}.
{"No <forwarded/> element found","Nenhum elemento <forwarded/> foi encontrado"}.
{"No hook has processed this command","Nenhum hook processou este comando"}.
{"No info about last activity found","Não foi encontrada informação sobre última atividade"}.
{"No 'item' element found","O elemento 'item' não foi encontrado"}.
{"No items found in this query","Nenhum item encontrado nesta consulta"}.
{"No limit","Ilimitado"}.
{"No module is handling this query","Nenhum módulo está processando esta consulta"}.
{"No node specified","Nenhum nó especificado"}.
{"No 'password' found in data form","'password' não foi encontrado em formulário de dados"}.
{"No 'password' found in this query","Nenhuma 'palavra-passe' foi encontrado nesta consulta"}.
{"No 'path' found in data form","'path' não foi encontrado em formulário de dados"}.
{"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 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"}.
{"No 'to' attribute found in the invitation","Atributo 'to' não foi encontrado no convite"}.
{"Nobody","Ninguém"}.
{"Node already exists","Nó já existe"}.
{"Node ID","ID do Tópico"}.
{"Node index not found","O índice do nó não foi encontrado"}.
{"Node not found","Nodo não encontrado"}.
{"Node ~p","Nó ~p"}.
{"Node","Nó"}.
{"Nodeprep has failed","Processo de identificação de nó falhou (nodeprep)"}.
{"Nodes","Nodos"}.
{"None","Nenhum"}.
{"Not allowed","Não é permitido"}.
{"Not Found","Não encontrado"}.
{"Not subscribed","Não subscrito"}.
{"Notify subscribers when items are removed from the node","Notificar assinantes quando itens forem eliminados do nó"}.
{"Notify subscribers when the node configuration changes","Notificar assinantes a configuração do nó mudar"}.
{"Notify subscribers when the node is deleted","Notificar assinantes quando o nó for eliminado se elimine"}.
{"November","Novembro"}.
{"Number of answers required","Quantidade de respostas necessárias"}.
{"Number of occupants","Quantidade de participantes"}.
{"Number of Offline Messages","Quantidade das mensagens offline"}.
{"Number of online users","Quantidade de utilizadores online"}.
{"Number of registered users","Quantidade de utilizadores registados"}.
{"Number of seconds after which to automatically purge items","Quantidade de segundos para excluir os itens automaticamente"}.
{"Occupants are allowed to invite others","As pessoas estão autorizadas a convidar outras pessoas"}.
{"Occupants May Change the Subject","As pessoas talvez possam alterar o assunto"}.
{"October","Outubro"}.
{"Offline Messages","Mensagens offline"}.
{"Offline Messages:","Mensagens offline:"}.
{"OK","OK"}.
{"Old Password:","Palavra-passe Antiga:"}.
{"Online Users","Utilizadores ligados"}.
{"Online Users:","Utilizadores online:"}.
{"Online","Ligado"}.
{"Only admins can see this","Apenas administradores podem ver isso"}.
{"Only collection node owners may associate leaf nodes with the collection","Apenas um grupo dos proprietários dos nós podem associar as páginas na coleção"}.
{"Only deliver notifications to available users","Somente enviar notificações aos utilizadores disponíveis"}.
{"Only <enable/> or <disable/> tags are allowed","Apenas tags <enable/> ou <disable/> são permitidas"}.
{"Only <list/> element is allowed in this query","Apenas elemento <list/> é permitido nesta consulta"}.
{"Only members may query archives of this room","Somente os membros podem procurar nos arquivos desta sala"}.
{"Only moderators and participants are allowed to change the subject in this room","Somente os moderadores e os participamentes podem alterar o assunto desta sala"}.
{"Only moderators are allowed to change the subject in this room","Somente os moderadores podem alterar o assunto desta sala"}.
{"Only moderators can approve voice requests","Somente moderadores podem aprovar requisições de voz"}.
{"Only occupants are allowed to send messages to the conference","Só os ocupantes podem enviar mensagens para a sala"}.
{"Only occupants are allowed to send queries to the conference","Só os ocupantes podem enviar consultas para a sala"}.
{"Only publishers may publish","Apenas os editores podem publicar"}.
{"Only service administrators are allowed to send service messages","Só os administradores do serviço têm permissão para enviar mensagens de serviço"}.
{"Only those on a whitelist may associate leaf nodes with the collection","Apenas aqueles presentes numa lista branca podem associar páginas na coleção"}.
{"Only those on a whitelist may subscribe and retrieve items","Apenas aqueles presentes numa lista branca podem se inscrever e recuperar os itens"}.
{"Organization Name","Nome da organização"}.
{"Organization Unit","Unidade da organização"}.
{"Outgoing s2s Connections","Conexões s2s de Saída"}.
{"Outgoing s2s Connections:","Saída das conexões s2s:"}.
{"Owner privileges required","São necessários privilégios de dono"}.
{"Packet relay is denied by service policy","A retransmissão de pacote é negada por causa da política de serviço"}.
{"Packet","Pacote"}.
{"Participant","Participante"}.
{"Password Verification:","Verificação da Palavra-passe:"}.
{"Password Verification","Verificação de Palavra-passe"}.
{"Password","Palavra-chave"}.
{"Password:","Palavra-chave:"}.
{"Path to Dir","Caminho para o directório"}.
{"Path to File","Caminho do ficheiro"}.
{"Payload type","Tipo da carga útil"}.
{"Pending","Pendente"}.
{"private, ","privado"}.
{"Period: ","Período: "}.
{"Persist items to storage","Persistir elementos ao armazenar"}.
{"Persistent","Persistente"}.
{"Ping query is incorrect","A consulta ping está incorreta"}.
{"Ping","Ping"}.
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Observe que tais opções farão backup apenas da base de dados Mnesia. Caso esteja a utilizar o modulo ODBC, precisará fazer backup da sua base de dados SQL separadamente."}.
{"Please, wait for a while before sending new voice request","Por favor, espere antes de enviar uma nova requisição de voz"}.
{"Pong","Pong"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Possuir o atributo 'ask' não é permitido pela RFC6121"}.
{"Present real Jabber IDs to","Tornar o Jabber ID real visível por"}.
{"Previous session not found","A sessão anterior não foi encontrada"}.
{"Previous session PID has been killed","O PID da sessão anterior foi excluído"}.
{"Previous session PID has exited","O PID da sessão anterior foi encerrado"}.
{"Previous session PID is dead","O PID da sessão anterior está morto"}.
{"Previous session timed out","A sessão anterior expirou"}.
{"private, ","privado, "}.
{"Public","Público"}.
{"Publish model","Publicar o modelo"}.
{"Publish-Subscribe","Publicação de Tópico"}.
{"PubSub subscriber request","PubSub requisição de assinante"}.
{"Purge all items when the relevant publisher goes offline","Descartar todos os itens quando o publicante principal estiver offline"}.
{"Push record not found","O registo push não foi encontrado"}.
{"Queries to the conference members are not allowed in this room","Nesta sala não são permitidas consultas aos seus membros"}.
{"Query to another users is forbidden","Consultar a outro utilizador é proibido"}.
{"RAM and disc copy","Cópia em RAM e em disco"}.
{"RAM copy","Cópia em RAM"}.
{"Really delete message of the day?","Deletar realmente a mensagem do dia?"}.
{"Receive notification from all descendent nodes","Receba a notificação de todos os nós descendentes"}.
{"Receive notification from direct child nodes only","Receba apenas as notificações dos nós relacionados"}.
{"Receive notification of new items only","Receba apenas as notificações dos itens novos"}.
{"Receive notification of new nodes only","Receba apenas as notificações dos nós novos"}.
{"Recipient is not in the conference room","O destinatário não está na sala"}.
{"Register an XMPP account","Registe uma conta XMPP"}.
{"Registered Users","Utilizadores registados"}.
{"Registered Users:","Utilizadores registados:"}.
{"Register","Registar"}.
{"Remote copy","Cópia remota"}.
{"Remove All Offline Messages","Remover Todas as Mensagens Offline"}.
{"Remove User","Eliminar utilizador"}.
{"Remove","Remover"}.
{"Replaced by new connection","Substituído por nova conexão"}.
{"Request has timed out","O pedido expirou"}.
{"Request is ignored","O pedido foi ignorado"}.
{"Requested role","Função solicitada"}.
{"Resources","Recursos"}.
{"Restart Service","Reiniciar Serviço"}.
{"Restart","Reiniciar"}.
{"Restore Backup from File at ","Restaura cópia de segurança a partir do ficheiro em "}.
{"Restore binary backup after next ejabberd restart (requires less memory):","Restaurar backup binário após reinicialização do ejabberd (requer menos memória):"}.
{"Restore binary backup immediately:","Restaurar imediatamente o backup binário:"}.
{"Restore plain text backup immediately:","Restaurar backup formato texto imediatamente:"}.
{"Restore","Restaurar"}.
{"Roles and Affiliations that May Retrieve Member List","As funções e as afiliações que podem recuperar a lista dos membros"}.
{"Roles for which Presence is Broadcasted","Para quem a presença será notificada"}.
{"Roles that May Send Private Messages","Atribuições que talvez possam enviar mensagens privadas"}.
{"Room Configuration","Configuração de salas"}.
{"Room creation is denied by service policy","Sala não pode ser criada devido à política do serviço"}.
{"Room description","Descrição da Sala"}.
{"Room Occupants","Quantidade de participantes"}.
{"Room terminates","Terminação da sala"}.
{"Room title","Título da sala"}.
{"Roster groups allowed to subscribe","Listar grupos autorizados"}.
{"Roster of ~ts","Lista de ~ts"}.
{"Roster size","Tamanho da Lista"}.
{"Roster:","Lista:"}.
{"RPC Call Error","Erro de chamada RPC"}.
{"Running Nodes","Nodos a correr"}.
{"~s invites you to the room ~s","~s convidaram-o à sala ~s"}.
{"Saturday","Sábado"}.
{"Script check","Verificação de Script"}.
{"Search from the date","Pesquise a partir da data"}.
{"Search Results for ","Resultados de pesquisa para "}.
{"Search the text","Pesquise o texto"}.
{"Search until the date","Pesquise até a data"}.
{"Search users in ","Procurar utilizadores em "}.
{"Select All","Selecione tudo"}.
{"Send announcement to all online users on all hosts","Enviar anúncio a todos utilizadores online em todas as máquinas"}.
{"Send announcement to all online users","Enviar anúncio a todos os utilizadorns online"}.
{"Send announcement to all users on all hosts","Enviar aviso para todos os utilizadores em todos os hosts"}.
{"Send announcement to all users","Enviar anúncio a todos os utilizadores"}.
{"September","Setembro"}.
{"Server:","Servidor:"}.
{"Service list retrieval timed out","A recuperação da lista dos serviços expirou"}.
{"Session state copying timed out","A cópia do estado da sessão expirou"}.
{"Set message of the day and send to online users","Definir mensagem do dia e enviar a todos utilizadores online"}.
{"Set message of the day on all hosts and send to online users","Definir mensagem do dia em todos os hosts e enviar para os utilizadores online"}.
{"Shared Roster Groups","Grupos Shared Roster"}.
{"Show Integral Table","Mostrar Tabela Integral"}.
{"Show Ordinary Table","Mostrar Tabela Ordinária"}.
{"Shut Down Service","Parar Serviço"}.
{"SOCKS5 Bytestreams","Bytestreams SOCKS5"}.
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","Alguns clientes XMPP podem armazenar a sua palavra-passe no seu computador, só faça isso no seu computador particular por questões de segurança."}.
{"Specify the access model","Especificar os modelos de acesso"}.
{"Specify the event message type","Especificar o tipo de mensagem para o evento"}.
{"Specify the publisher model","Especificar o modelo do publicante"}.
{"Stanza ID","ID da estrofe"}.
{"Statically specify a replyto of the node owner(s)","Defina uma resposta fixa do(s) proprietário(s) do nó"}.
{"Statistics of ~p","Estatísticas de ~p"}.
{"Statistics","Estatísticas"}.
{"Stop","Parar"}.
{"Stopped Nodes","Nodos parados"}.
{"Storage Type","Tipo de armazenagem"}.
{"Store binary backup:","Armazenar backup binário:"}.
{"Store plain text backup:","Armazenar backup em texto:"}.
{"Stream management is already enabled","A gestão do fluxo já está ativada"}.
{"Stream management is not enabled","A gestão do fluxo não está ativada"}.
{"Subject","Assunto"}.
{"Submit","Enviar"}.
{"Submitted","Submetido"}.
{"Subscriber Address","Endereço dos Assinantes"}.
{"Subscribers may publish","Os assinantes podem publicar"}.
{"Subscription requests must be approved and only subscribers may retrieve items","Os pedidos de assinatura devem ser aprovados e apenas os assinantes podem recuperar os itens"}.
{"Subscriptions are not allowed","Subscrições não estão permitidas"}.
{"Subscription","Subscrição"}.
{"Sunday","Domingo"}.
{"Text associated with a picture","Um texto associado a uma imagem"}.
{"Text associated with a sound","Um texto associado a um som"}.
{"Text associated with a video","Um texto associado a um vídeo"}.
{"Text associated with speech","Um texto associado à fala"}.
{"That nickname is already in use by another occupant","O apelido (nick) já está a ser utilizado"}.
{"That nickname is registered by another person","O apelido já está registado por outra pessoa"}.
{"The account already exists","A conta já existe"}.
{"The account was not unregistered","A conta não estava não registada"}.
{"The body text of the last received message","O corpo do texto da última mensagem que foi recebida"}.
{"The CAPTCHA is valid.","O CAPTCHA é inválido."}.
{"The CAPTCHA verification has failed","A verificação do CAPTCHA falhou"}.
{"The captcha you entered is wrong","O captcha que digitou está errado"}.
{"The child nodes (leaf or collection) associated with a collection","Os nós relacionados (página ou coleção) associados com uma coleção"}.
{"The collections with which a node is affiliated","As coleções com as quais o nó está relacionado"}.
{"The DateTime at which a leased subscription will end or has ended","A data e a hora que uma assinatura alugada terminará ou terá terminado"}.
{"The datetime when the node was created","A data em que o nó foi criado"}.
{"The default language of the node","O idioma padrão do nó"}.
{"The feature requested is not supported by the conference","A funcionalidade solicitada não é suportada pela sala de conferência"}.
{"The JID of the node creator","O JID do criador do nó"}.
{"The JIDs of those to contact with questions","Os JIDs daqueles para entrar em contato com perguntas"}.
{"The JIDs of those with an affiliation of owner","Os JIDs daqueles com uma afiliação de proprietário"}.
{"The JIDs of those with an affiliation of publisher","Os JIDs daqueles com uma afiliação de editor"}.
{"The list of JIDs that may associate leaf nodes with a collection","A lista dos JIDs que podem associar as páginas dos nós numa coleção"}.
{"The maximum number of child nodes that can be associated with a collection","A quantidade máxima dos nós relacionados que podem ser associados com uma coleção"}.
{"The minimum number of milliseconds between sending any two notification digests","A quantidade mínima de milissegundos entre o envio do resumo das duas notificações"}.
{"The name of the node","O nome do nó"}.
{"The node is a collection node","O nó é um nó da coleção"}.
{"The node is a leaf node (default)","O nó é uma página do nó (padrão)"}.
{"The NodeID of the relevant node","O NodeID do nó relevante"}.
{"The number of pending incoming presence subscription requests","A quantidade pendente dos pedidos da presença da assinatura"}.
{"The number of subscribers to the node","A quantidade dos assinantes para o nó"}.
{"The number of unread or undelivered messages","A quantidade das mensagens que não foram lidas ou não foram entregues"}.
{"The password contains unacceptable characters","A palavra-passe contém caracteres proibidos"}.
{"The password is too weak","Palavra-passe considerada muito fraca"}.
{"the password is","a palavra-passe é"}.
{"The password of your XMPP account was successfully changed.","A palavra-passe da sua conta XMPP foi alterada com sucesso."}.
{"The password was not changed","A palavra-passe não foi alterada"}.
{"The passwords are different","As palavras-passe não batem"}.
{"The presence states for which an entity wants to receive notifications","As condições da presença para os quais uma entidade queira receber as notificações"}.
{"The query is only allowed from local users","Esta consulta só é permitida a partir de utilizadores locais"}.
{"The query must not contain <item/> elements","A consulta não pode conter elementos <item/>"}.
{"The room subject can be modified by participants","O tema da sala pode ser alterada pelos próprios participantes"}.
{"The sender of the last received message","O remetente da última mensagem que foi recebida"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","A instância DEVE conter apenas um elemento <active/>, um elemento <default/>, ou um elemento <list/>"}.
{"The subscription identifier associated with the subscription request","O identificador da assinatura associado à solicitação da assinatura"}.
{"The type of node data, usually specified by the namespace of the payload (if any)","O tipo dos dados do nó, normalmente definido pelo espaço dos nomes da carga útil (caso haja)"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","O URL da transformação XSL que pode ser aplicada nas cargas úteis para gerar um elemento apropriado no corpo da mensagem."}.
{"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","A URL de uma transformação XSL que pode ser aplicada ao formato de carga útil para gerar um Formulário de Dados válido onde o cliente possa exibir usando um mecanismo genérico de renderização do Formulários de Dados"}.
{"The username is not valid","O nome do utilizador não é válido"}.
{"There was an error changing the password: ","Houve um erro ao alterar a palavra-passe: "}.
{"There was an error creating the account: ","Houve um erro ao criar esta conta: "}.
{"There was an error deleting the account: ","Houve um erro ao deletar esta conta: "}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","O tamanho da caixa não importa: macbeth é o mesmo que MacBeth e Macbeth."}.
{"This page allows to register an XMPP account in this XMPP server. Your JID (Jabber ID) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Esta pagina permite a criação de novas contas XMPP neste servidor. O seu JID (Identificador Jabber) será da seguinte maneira: utilizador@servidor. Por favor, leia cuidadosamente as instruções para preencher todos os campos corretamente."}.
{"This page allows to unregister an XMPP account in this XMPP server.","Esta página permite a exclusão de uma conta XMPP neste servidor."}.
{"This room is not anonymous","Essa sala não é anônima"}.
{"This service can not process the address: ~s","Este serviço não pode processar o endereço: ~s"}.
{"Thursday","Quinta"}.
{"Time delay","Intervalo (Tempo)"}.
{"Timed out waiting for stream resumption","Tempo limite expirou durante à espera da retomada da transmissão"}.
{"Time","Data"}.
{"To register, visit ~s","Para registar, visite ~s"}.
{"To ~ts","Para ~s"}.
{"Token TTL","Token TTL"}.
{"Too many active bytestreams","Quantidade excessiva de bytestreams ativos"}.
{"Too many CAPTCHA requests","Quantidade excessiva de requisições para o CAPTCHA"}.
{"Too many child elements","Quantidade excessiva de elementos filho"}.
{"Too many <item/> elements","Quantidade excessiva de elementos <item/>"}.
{"Too many <list/> elements","Quantidade excessiva de elementos <list/>"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Tentativas excessivas (~p) com falha de autenticação (~s). O endereço será desbloqueado às ~s UTC"}.
{"Too many receiver fields were specified","Foram definidos receptores demais nos campos"}.
{"Too many unacked stanzas","Quantidade excessiva de instâncias sem confirmação"}.
{"Too many users in this conference","Há uma quantidade excessiva de utilizadores nesta conferência"}.
{"To","Para"}.
{"Total rooms","Salas no total"}.
{"Traffic rate limit is exceeded","Limite de banda excedido"}.
{"Transactions Aborted:","Transações abortadas:"}.
{"Transactions Committed:","Transações salvas:"}.
{"Transactions Logged:","Transações de log:"}.
{"Transactions Restarted:","Transações reiniciadas:"}.
{"~ts's Offline Messages Queue","~s's Fila de Mensagens Offline"}.
{"Tuesday","Terça"}.
{"Unable to generate a CAPTCHA","Impossível gerar um CAPTCHA"}.
{"Unable to register route on existing local domain","Não foi possível registar rota no domínio local existente"}.
{"Unauthorized","Não Autorizado"}.
{"Unexpected action","Ação inesperada"}.
{"Unexpected error condition: ~p","Condição de erro inesperada: ~p"}.
{"Unregister an XMPP account","Excluir uma conta XMPP"}.
{"Unregister","Deletar registo"}.
{"Unselect All","Desmarcar todos"}.
{"Unsupported <index/> element","Elemento <index/> não suportado"}.
{"Unsupported version","Versão sem suporte"}.
{"Update message of the day (don't send)","Atualizar mensagem do dia (não enviar)"}.
{"Update message of the day on all hosts (don't send)","Atualizar a mensagem do dia em todos os host (não enviar)"}.
{"Update ~p","Atualizar ~p"}.
{"Update plan","Plano de atualização"}.
{"Update script","Script de atualização"}.
{"Update","Actualizar"}.
{"Uptime:","Tempo de atividade:"}.
{"URL for Archived Discussion Logs","A URL para o arquivamento dos registos da discussão"}.
{"User already exists","Utilizador já existe"}.
{"User (jid)","Utilizador (jid)"}.
{"User JID","Utilizador JID"}.
{"User Management","Gestão de utilizadores"}.
{"User removed","O utilizador foi removido"}.
{"User session not found","A sessão do utilizador não foi encontrada"}.
{"User session terminated","Sessão de utilizador terminada"}.
{"User ~ts","Utilizador ~s"}.
{"Username:","Utilizador:"}.
{"Users are not allowed to register accounts so quickly","Utilizadores não estão autorizados a registar contas imediatamente"}.
{"Users Last Activity","Últimas atividades dos utilizadores"}.
{"Users","Utilizadores"}.
{"User","Utilizador"}.
{"Validate","Validar"}.
{"Value 'get' of 'type' attribute is not allowed","Valor 'get' não permitido para atributo 'type'"}.
{"Value of '~s' should be boolean","Value de '~s' deveria ser um booleano"}.
{"Value of '~s' should be datetime string","Valor de '~s' deveria ser data e hora"}.
{"Value of '~s' should be integer","Valor de '~s' deveria ser um inteiro"}.
{"Value 'set' of 'type' attribute is not allowed","Valor 'set' não permitido para atributo 'type'"}.
{"vCard User Search","Busca de Utilizador vCard"}.
{"View Queue","Exibir a fila"}.
{"View Roster","Ver a lista"}.
{"Virtual Hosts","Hosts virtuais"}.
{"Visitors are not allowed to change their nicknames in this room","Nesta sala, os visitantes não podem mudar os apelidos deles"}.
{"Visitors are not allowed to send messages to all occupants","Os visitantes não podem enviar mensagens para todos os ocupantes"}.
{"Visitor","Visitante"}.
{"Voice request","Requisição de voz"}.
{"Voice requests are disabled in this conference","Requisições de voz estão desativadas nesta sala de conferência"}.
{"Wednesday","Quarta"}.
{"When a new subscription is processed and whenever a subscriber comes online","Quando uma nova assinatura é processada e sempre que um assinante fica online"}.
{"When a new subscription is processed","Quando uma nova assinatura é processada"}.
{"When to send the last published item","Quando enviar o último tópico publicado"}.
{"Whether an entity wants to receive an XMPP message body in addition to the payload format","Caso uma entidade queira receber o corpo de uma mensagem XMPP além do formato de carga útil"}.
{"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually","Caso uma entidade queira receber os resumos (as agregações) das notificações ou todas as notificações individualmente"}.
{"Whether an entity wants to receive or disable notifications","Caso uma entidade queira receber ou desativar as notificações"}.
{"Whether owners or publisher should receive replies to items","Caso os proprietários ou a editora devam receber as respostas nos itens"}.
{"Whether the node is a leaf (default) or a collection","Caso o nó seja uma folha (padrão) ou uma coleção"}.
{"Whether to allow subscriptions","Permitir subscrições"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Caso todas as assinaturas devam ser temporárias, com base na presença do assinante"}.
{"Whether to notify owners about new subscribers and unsubscribes","Caso deva notificar os proprietários sobre os novos assinantes e aqueles que cancelaram a assinatura"}.
{"Who may associate leaf nodes with a collection","Quem pode associar as folhas dos nós numa coleção"}.
{"Wrong parameters in the web formulary","O formulário web está com os parâmetros errados"}.
{"Wrong xmlns","Xmlns errado"}.
{"XMPP Account Registration","Registo da Conta XMPP"}.
{"XMPP Domains","Domínios XMPP"}.
{"XMPP Show Value of Away","XMPP Exiba o valor da ausência"}.
{"XMPP Show Value of Chat","XMPP Exiba o valor do chat"}.
{"XMPP Show Value of DND (Do Not Disturb)","XMPP Exiba o valor do DND (Não Perturbe)"}.
{"XMPP Show Value of XA (Extended Away)","XMPP Exiba o valor do XA (Ausência Estendida)"}.
{"XMPP URI of Associated Publish-Subscribe Node","XMPP URI da publicação do nó associado da assinatura"}.
{"You are being removed from the room because of a system shutdown","Está a ser removido da sala devido ao desligamento do sistema"}.
{"You are not joined to the channel","Não está inscrito no canal"}.
{"You can later change your password using an XMPP client.","Pode alterar a sua palavra-passe mais tarde usando um cliente XMPP."}.
{"You have been banned from this room","Foi banido desta sala"}.
{"You have joined too many conferences","Entrou em demais salas de conferência"}.
{"You must fill in field \"Nickname\" in the form","Deve completar o campo \"Apelido\" no formulário"}.
{"You need a client that supports x:data and CAPTCHA to register","Precisa de um cliente com suporte de x:data para poder registar o apelido"}.
{"You need a client that supports x:data to register the nickname","Precisa de um cliente com suporte a x:data para registar o seu apelido"}.
{"You need an x:data capable client to search","É necessário um cliente com suporte de x:data para poder procurar"}.
{"Your active privacy list has denied the routing of this stanza.","A sua lista de privacidade ativa negou o roteamento desta instância."}.
{"Your contact offline message queue is full. The message has been discarded.","A fila de contatos offline esta cheia. A sua mensagem foi descartada."}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","As suas mensagens para ~s estão bloqueadas. Para desbloqueá-las, visite: ~s"}.
{"Your XMPP account was successfully registered.","A sua conta XMPP foi registada com sucesso."}.
{"Your XMPP account was successfully unregistered.","A sua conta XMPP foi excluída com sucesso."}.
{"You're not allowed to create nodes","Não tem autorização para criar nós"}.

View File

@ -19,48 +19,48 @@
%%%----------------------------------------------------------------------
{deps, [{base64url, ".*", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.28"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.20"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.29"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.21"}}},
{if_var_true, tools,
{ejabberd_po, ".*", {git, "https://github.com/processone/ejabberd-po", {branch, "main"}}}},
{if_var_true, elixir,
{elixir, ".*", {git, "https://github.com/elixir-lang/elixir", {tag, "v1.4.4"}}}},
{if_var_true, pam,
{epam, ".*", {git, "https://github.com/processone/epam", {tag, "1.0.10"}}}},
{epam, ".*", {git, "https://github.com/processone/epam", {tag, "1.0.12"}}}},
{if_var_true, redis,
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
{if_var_true, sip,
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.42"}}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.43"}}}},
{if_var_true, zlib,
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.9"}}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.12"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.46"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.31"}}},
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.10"}}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.13"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.47"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.32"}}},
{idna, ".*", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "1.0.5"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.9.0"}}},
{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}},
{if_var_true, lua,
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.3"}}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.13"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.12"}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.14"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.13"}}},
{if_var_true, mysql,
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.18"}}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.9"}}},
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.19"}}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.10"}}},
{if_var_true, pgsql,
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.11"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.22"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.7"}}},
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.12"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.23"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.8"}}},
{if_not_rebar3, %% Needed because modules are not fully migrated to new structure and mix
{if_var_true, elixir,
{rebar_elixir_plugin, ".*", {git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
{if_var_true, sqlite,
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.12"}}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", "6d143ebcacaafe3a8adc669300a8ad001402b3d8"}},
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.13"}}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.27"}}},
{if_var_true, stun,
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.43"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "1a4874ebcec2984a5a7f33c73695a2b4aad75fb6"}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.11"}}}
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.44"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e943c0285aa85e3cbd4bfb9259f6b7de32b00395"}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.12"}}}
]}.
{gitonly_deps, [elixir, luerl]}.
@ -172,6 +172,41 @@
{"fast_yaml", []},
{"stringprep", []}]}.
{relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}},
[ejabberd]},
{sys_config, "./rel/sys.config"},
{vm_args, "./rel/vm.args"},
{overlay_vars, "vars.config"},
{extended_start_script, true},
{overlay, [{mkdir, "var/log/ejabberd"},
{mkdir, "var/lock"},
{mkdir, "var/lib/ejabberd"},
{mkdir, "etc/ejabberd"},
{copy, "rel/files/erl", "\{\{erts_vsn\}\}/bin/erl"}, % in rebar2 this prepends erts-
{template, "ejabberdctl.template", "bin/ejabberdctl"},
{copy, "inetrc", "etc/ejabberd/inetrc"},
{copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"},
{copy, "rel/files/install_upgrade.escript", "bin/install_upgrade.escript"}]}
]}.
{profiles, [{prod, [{relx, [{debug_info, strip},
{dev_mode, false},
{include_erts, true},
{include_src, true},
{overlay, [{copy, "sql/*", "lib/ejabberd-\{\{release_version\}\}/priv/sql/"},
{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg"},
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml"}]}]}]},
{dev, [{post_hooks, [{release, "rel/setup-dev.sh"}]},
{relx, [{debug_info, keep},
{dev_mode, true},
{include_erts, true},
{include_src, false},
{overlay, [{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg.example"},
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml.example"},
{copy, "test/ejabberd_SUITE_data/ca.pem", "etc/ejabberd/"},
{copy, "test/ejabberd_SUITE_data/cert.pem", "etc/ejabberd/"}]}]}]},
{test, [{erl_opts, [nowarn_export_all]}]}]}.
%% Local Variables:
%% mode: erlang
%% End:

View File

@ -339,6 +339,29 @@ fun(Deps, FDeps) ->
end, Deps)
end,
VarsApps = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])) of
{ok, TermsV} ->
case proplists:get_bool(odbc, TermsV) of
true -> [odbc];
false -> []
end;
_->
[]
end,
ProcessRelx = fun(Relx, Deps) ->
{value, {release, NameVersion, DefaultApps}, RelxTail} = lists:keytake(release, 1, Relx),
ProfileApps = case os:getenv("REBAR_PROFILE") of
"dev" -> [observer, runtime_tools, wx, debugger];
_ -> []
end,
DepApps = lists:map(fun({DepName, _, _}) -> DepName;
({DepName, _}) -> DepName
end, Deps),
[{release, NameVersion, DefaultApps ++ VarsApps ++ ProfileApps ++ DepApps} | RelxTail]
end,
GithubConfig = case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of
{"true", Token} when is_list(Token) ->
CONFIG1 = [{coveralls_repo_token, Token},
@ -374,6 +397,8 @@ Rules = [
ProcessErlOpt, []},
{[xref_queries], [xref_exclusions], true,
AppendList2(ProcssXrefExclusions), [], []},
{[relx], [deps], IsRebar3,
ProcessRelx, [], []},
{[deps], [floating_deps], true,
ProcessFloatingDeps, [], []},
{[deps], [gitonly_deps], IsRebar3,

BIN
rebar3 Executable file

Binary file not shown.

28
rel/setup-dev.sh Executable file
View File

@ -0,0 +1,28 @@
echo -n "===> Preparing dev configuration files: "
PWD_DIR=`pwd`
REL_DIR=$PWD_DIR/_build/dev/rel/ejabberd/
CON_DIR=$REL_DIR/etc/ejabberd/
[ -z "$REL_DIR_TEMP" ] && REL_DIR_TEMP=$REL_DIR
CON_DIR_TEMP=$REL_DIR_TEMP/etc/ejabberd/
BIN_DIR_TEMP=$REL_DIR_TEMP/bin/
cd $CON_DIR_TEMP
sed -i "s|# certfiles:|certfiles:\n - $CON_DIR/cert.pem|g" ejabberd.yml.example
sed -i "s|certfiles:|ca_file: $CON_DIR/ca.pem\ncertfiles:|g" ejabberd.yml.example
sed -i 's|^acl:$|acl:\n admin: [user: admin]|g' ejabberd.yml.example
[ ! -f "$CON_DIR/ejabberd.yml" ] \
&& echo -n "ejabberd.yml " \
&& mv ejabberd.yml.example ejabberd.yml
sed -i "s|#' POLL|EJABBERD_BYPASS_WARNINGS=true\n\n#' POLL|g" ejabberdctl.cfg.example
[ ! -f "$CON_DIR/ejabberdctl.cfg" ] \
&& echo -n "ejabberdctl.cfg " \
&& mv ejabberdctl.cfg.example ejabberdctl.cfg
echo ""
echo "===> Some example ways to start this ejabberd dev:"
echo " _build/dev/rel/ejabberd/bin/ejabberd console"
echo " _build/dev/rel/ejabberd/bin/ejabberdctl live"

2
rel/sys.config Normal file
View File

@ -0,0 +1,2 @@
[{ejabberd, [{config, "etc/ejabberd/ejabberd.yml"},
{log_path, "var/log/ejabberd/ejabberd.log"}]}].

32
rel/vm.args Normal file
View File

@ -0,0 +1,32 @@
## Name of the node
-sname ejabberd@localhost
## Cookie for distributed erlang
#-setcookie ejabberd
-mnesia dir \"var/lib/ejabberd\"
## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
## (Disabled by default..use with caution!)
##-heart
## Enable kernel poll and a few async threads
##+K true
##+A 5
## Increase number of concurrent ports/sockets
##-env ERL_MAX_PORTS 4096
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
# +B [c | d | i]
# Option c makes Ctrl-C interrupt the current shell instead of invoking the emulator break
# handler. Option d (same as specifying +B without an extra option) disables the break handler. # Option i makes the emulator ignore any break signal.
# If option c is used with oldshell on Unix, Ctrl-C will restart the shell process rather than
# interrupt it.
# Disable the emulator break handler
# it easy to accidentally type ctrl-c when trying
# to reach for ctrl-d. ctrl-c on a live node can
# have very undesirable results
##+Bi

14
rel/vm.args.mix Normal file
View File

@ -0,0 +1,14 @@
## Customize flags given to the VM: https://erlang.org/doc/man/erl.html
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
-boot ../releases/<%= @version %>/start_clean
-boot_var RELEASE_LIB ../lib
## Number of dirty schedulers doing IO work (file, sockets, and others)
##+SDio 5
## Increase number of concurrent ports/sockets
##+Q 65536
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10

View File

@ -86,7 +86,7 @@ CREATE TABLE sr_user (
PRIMARY KEY (server_host(191), jid, grp)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE UNIQUE INDEX i_sr_user_sh_jid_group ON sr_group(server_host(191), jid, grp);
CREATE UNIQUE INDEX i_sr_user_sh_jid_group ON sr_user(server_host(191), jid, grp);
CREATE INDEX i_sr_user_sh_jid ON sr_user(server_host(191), jid);
CREATE INDEX i_sr_user_sh_grp ON sr_user(server_host(191), grp);

View File

@ -156,6 +156,12 @@
-- CREATE INDEX i_sm_sh_username ON sm USING btree (server_host, username);
-- ALTER TABLE sm ALTER COLUMN server_host DROP DEFAULT;
-- ALTER TABLE push_session ADD COLUMN server_host text NOT NULL DEFAULT '<HOST>';
-- DROP INDEX i_push_usn;
-- DROP INDEX i_push_ut;
-- ALTER TABLE push_session ADD PRIMARY KEY (server_host, username, timestamp);
-- CREATE UNIQUE INDEX i_push_session_susn ON push_session USING btree (server_host, username, service, node);
CREATE TABLE users (
username text NOT NULL,

View File

@ -25,6 +25,7 @@
-export([match_rules/4, match_acls/3]).
-export([access_rules_validator/0, access_validator/0]).
-export([validator/1, validators/0]).
-export([loaded_shared_roster_module/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).

View File

@ -20,10 +20,14 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT), "..", "vars.co
cache_tab,
eimp,
fast_tls,
fast_xml,
fast_yaml,
lager,
p1_acme,
p1_utils,
pkix,
stringprep,
yconf,
xmpp]},
{mod, {ejabberd_app, []}}]}.

View File

@ -46,6 +46,7 @@
code_change/3]).
-define(SERVER, ?MODULE).
-define(CACHE_TAB, access_permissions_cache).
-record(state,
{definitions = none :: none | [definition()]}).
@ -71,17 +72,45 @@
%%%===================================================================
-spec can_access(atom(), caller_info()) -> allow | deny.
can_access(Cmd, CallerInfo) ->
gen_server:call(?MODULE, {can_access, Cmd, CallerInfo}).
Defs0 = show_current_definitions(),
CallerModule = maps:get(caller_module, CallerInfo, none),
Host = maps:get(caller_host, CallerInfo, global),
Tag = maps:get(tag, CallerInfo, none),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl(
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule "
"'~ts' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
case Res of
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied "
"(CallerInfo=~p)", [Cmd, CallerInfo]),
deny
end.
-spec invalidate() -> ok.
invalidate() ->
gen_server:cast(?MODULE, invalidate).
gen_server:cast(?MODULE, invalidate),
ets_cache:delete(?CACHE_TAB, definitions).
-spec show_current_definitions() -> [definition()].
show_current_definitions() ->
gen_server:call(?MODULE, show_current_definitions).
ets_cache:lookup(?CACHE_TAB, definitions,
fun() ->
{cache, gen_server:call(?MODULE, show_current_definitions)}
end).
start_link() ->
ets_cache:new(?CACHE_TAB, [{max_size, 2}]),
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%%===================================================================
@ -90,38 +119,11 @@ start_link() ->
-spec init([]) -> {ok, state()}.
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, invalidate, 90),
ets_cache:new(access_permissions),
{ok, #state{}}.
-spec handle_call({can_access, atom(), caller_info()} |
show_current_definitions | term(),
-spec handle_call(show_current_definitions | term(),
term(), state()) -> {reply, term(), state()}.
handle_call({can_access, Cmd, CallerInfo}, _From, State) ->
CallerModule = maps:get(caller_module, CallerInfo, none),
Host = maps:get(caller_host, CallerInfo, global),
Tag = maps:get(tag, CallerInfo, none),
{State2, Defs0} = get_definitions(State),
Defs = maps:get(extra_permissions, CallerInfo, []) ++ Defs0,
Res = lists:foldl(
fun({Name, _} = Def, none) ->
case matches_definition(Def, Cmd, CallerModule, Tag, Host, CallerInfo) of
true ->
?DEBUG("Command '~p' execution allowed by rule "
"'~ts' (CallerInfo=~p)", [Cmd, Name, CallerInfo]),
allow;
_ ->
none
end;
(_, Val) ->
Val
end, none, Defs),
Res2 = case Res of
allow -> allow;
_ ->
?DEBUG("Command '~p' execution denied "
"(CallerInfo=~p)", [Cmd, CallerInfo]),
deny
end,
{reply, Res2, State2};
handle_call(show_current_definitions, _From, State) ->
{State2, Defs} = get_definitions(State),
{reply, Defs, State2};

View File

@ -27,7 +27,8 @@
%% Hooks
-export([ejabberd_started/0, register_certfiles/0, cert_expired/2]).
%% ejabberd commands
-export([request_certificate/1, revoke_certificate/1, list_certificates/0]).
-export([get_commands_spec/0, request_certificate/1,
revoke_certificate/1, list_certificates/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).

View File

@ -119,12 +119,12 @@ get_commands_spec() ->
desc = "Restart ejabberd gracefully",
module = init, function = restart,
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs, server],
#ejabberd_commands{name = reopen_log, tags = [logs],
desc = "Reopen the log files",
policy = admin,
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
#ejabberd_commands{name = rotate_log, tags = [logs, server],
#ejabberd_commands{name = rotate_log, tags = [logs],
desc = "Rotate the log files",
module = ?MODULE, function = rotate_log,
args = [], result = {res, rescode}},
@ -139,14 +139,14 @@ get_commands_spec() ->
args_example = [60, <<"Server will stop now.">>],
args = [{delay, integer}, {announcement, string}],
result = {res, rescode}},
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
#ejabberd_commands{name = get_loglevel, tags = [logs],
desc = "Get the current loglevel",
module = ejabberd_logger, function = get,
result_desc = "Tuple with the log level number, its keyword and description",
result_example = warning,
args = [],
result = {levelatom, atom}},
#ejabberd_commands{name = set_loglevel, tags = [logs, server],
#ejabberd_commands{name = set_loglevel, tags = [logs],
desc = "Set the loglevel",
module = ?MODULE, function = set_loglevel,
args_desc = ["Desired logging level: none | emergency | alert | critical "
@ -200,7 +200,7 @@ get_commands_spec() ->
result_example = [<<"example.com">>, <<"anon.example.com">>],
args = [],
result = {vhosts, {list, {vhost, string}}}},
#ejabberd_commands{name = reload_config, tags = [server, config],
#ejabberd_commands{name = reload_config, tags = [config],
desc = "Reload config file in memory",
module = ?MODULE, function = reload_config,
args = [],
@ -254,21 +254,21 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = import_file,
args_desc = ["Full path to the PIEFXIS file"],
args_example = ["/var/lib/ejabberd/example.com.xml"],
args = [{file, string}], result = {res, rescode}},
args = [{file, binary}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_server,
args_desc = ["Full path to a directory"],
args_example = ["/var/lib/ejabberd/"],
args = [{dir, string}], result = {res, rescode}},
args = [{dir, binary}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_host,
args_desc = ["Full path to a directory", "Vhost to export"],
args_example = ["/var/lib/ejabberd/", "example.com"],
args = [{dir, string}, {host, string}], result = {res, rescode}},
args = [{dir, binary}, {host, binary}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
#ejabberd_commands{name = delete_mnesia, tags = [mnesia],
desc = "Delete elements in Mnesia database for a given vhost",
module = ejd2sql, function = delete,
args_desc = ["Vhost which content will be deleted in Mnesia database"],
@ -326,7 +326,7 @@ get_commands_spec() ->
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
#ejabberd_commands{name = set_master, tags = [cluster],
desc = "Set master node of the clustered Mnesia tables",
longdesc = "If you provide as nodename \"self\", this "
"node will be set as its own master.",

View File

@ -880,7 +880,7 @@ get_priority_from_presence(#presence{priority = Prio}) ->
-spec route_multiple(state(), [jid()], stanza()) -> ok.
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
From = xmpp:get_from(Pkt),
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt, false).
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
{Subscription, _, _} = ejabberd_hooks:run_fold(

View File

@ -41,6 +41,7 @@
get_tags_commands/0,
get_tags_commands/1,
register_commands/1,
register_commands/2,
unregister_commands/1,
get_commands_spec/0,
get_commands_definition/0,
@ -90,6 +91,16 @@ get_commands_spec() ->
"that will have example invocation include in markdown document"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok},
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation],
desc = "Generates markdown documentation for ejabberd_commands",
module = ejabberd_commands_doc, function = generate_tags_md,
args = [{file, binary}],
result = {res, rescode},
args_desc = ["Path to file where generated "
"documentation should be stored"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/tags.md"],
result_example = ok}].
start_link() ->
@ -129,10 +140,13 @@ code_change(_OldVsn, State, _Extra) ->
-spec register_commands([ejabberd_commands()]) -> ok.
register_commands(Commands) ->
register_commands(unknown, Commands).
register_commands(Definer, Commands) ->
lists:foreach(
fun(Command) ->
%% XXX check if command exists
mnesia:dirty_write(Command)
mnesia:dirty_write(Command#ejabberd_commands{definer = Definer})
%% ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands),

View File

@ -28,6 +28,7 @@
-export([generate_html_output/3]).
-export([generate_md_output/3]).
-export([generate_tags_md/1]).
-include("ejabberd_commands.hrl").
@ -360,8 +361,16 @@ gen_param(Name, Type, Desc, HTMLOutput) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))].
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc, note=Note,
make_tags(HTMLOutput) ->
TagsList = ejabberd_commands:get_tags_commands(1000000),
lists:map(fun(T) -> gen_tags(T, HTMLOutput) end, TagsList).
-dialyzer({no_match, gen_tags/2}).
gen_tags({TagName, Commands}, HTMLOutput) ->
[?TAG(h1, TagName) | [?TAG(p, ?RAW("* *`"++C++"`*")) || C <- Commands]].
gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc, note=Note, definer=Definer,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
try
ArgsText = case ArgsDesc of
@ -389,6 +398,17 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
end,
TagsText = [?RAW("*`"++atom_to_list(Tag)++"`* ") || Tag <- Tags],
IsDefinerMod = case Definer of
unknown -> true;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
end,
ModuleText = case IsDefinerMod of
true ->
[?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("*`"++atom_to_list(Definer)++"`*"))];
false ->
[]
end,
NoteEl = case Note of
"" -> [];
_ -> ?TAG('div', "note-down", ?RAW(Note))
@ -403,6 +423,8 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
end,
?TAG(h2, <<"Arguments:">>), ArgsText,
?TAG(h2, <<"Result:">>), ResultText,
?TAG(h2, <<"Tags:">>), ?TAG(p, TagsText)]
++ ModuleText ++ [
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
catch
_:Ex ->
@ -421,12 +443,13 @@ find_commands_definitions() ->
lists:flatmap(fun(P) ->
Mod = list_to_atom(filename:rootname(P)),
code:ensure_loaded(Mod),
case erlang:function_exported(Mod, get_commands_spec, 0) of
Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of
true ->
apply(Mod, get_commands_spec, []);
_ ->
[]
end
end,
[C#ejabberd_commands{definer = Mod} || C <- Cs]
end, filelib:wildcard("*.beam", Path))
end.
@ -466,15 +489,25 @@ generate_md_output(File, RegExp, Languages) ->
end, Cmds2),
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
Langs = binary:split(Languages, <<",">>, [global]),
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: Administration API\norder: 40\n"
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: API Reference\norder: 1\n"
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_commands'\n---\n\n"
"This section describes API of ejabberd.">>,
"This section describes API of ejabberd.\n">>,
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write]),
io:format(Fh, "~ts~ts", [Header, Out]),
file:close(Fh),
ok.
generate_tags_md(File) ->
Header = <<"---\ntitle: API Tags\ntoc: true\nmenu: API Tags\norder: 2\n"
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_tags'\n---\n\n"
"This section enumerates the tags and their associated API.\n">>,
Tags = make_tags(false),
{ok, Fh} = file:open(File, [write]),
io:format(Fh, "~ts~ts", [Header, Tags]),
file:close(Fh),
ok.
html_pre() ->
"<!DOCTYPE>
<html>

View File

@ -827,6 +827,7 @@ print_usage_command(Cmd, MaxC, ShCode, Version) ->
print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
tags = TagsAtoms,
definer = Definer,
desc = Desc,
args = ArgsDef,
longdesc = LongDesc,
@ -851,6 +852,15 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
TagsFmt = [" ",?B("Tags"),":", prepare_long_line(8, MaxC, [?G(atom_to_list(TagA)) || TagA <- TagsAtoms])],
IsDefinerMod = case Definer of
unknown -> true;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
end,
ModuleFmt = case IsDefinerMod of
true -> [" ",?B("Module"),": ", atom_to_list(Definer), "\n\n"];
false -> []
end,
DescFmt = [" ",?B("Description"),":", prepare_description(15, MaxC, Desc)],
LongDescFmt = case LongDesc of
@ -866,7 +876,7 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
case Cmd of
"help" -> ok;
_ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt,
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n"], [])
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", ModuleFmt, DescFmt, "\n\n"], [])
end,
print([LongDescFmt, NoteEjabberdctl], []).

View File

@ -113,10 +113,11 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
_ ->
{Port, SockOpts}
end,
ExtraOpts2 = lists:keydelete(send_timeout, 1, ExtraOpts),
case gen_udp:open(Port2, [binary,
{active, false},
{reuseaddr, true} |
ExtraOpts]) of
ExtraOpts2]) of
{ok, Socket} ->
case inet:sockname(Socket) of
{ok, {Addr, Port1}} ->
@ -195,7 +196,6 @@ listen_tcp(Port, SockOpts) ->
{active, false},
{reuseaddr, true},
{nodelay, true},
{send_timeout, ?TCP_SEND_TIMEOUT},
{send_timeout_close, true},
{keepalive, true} | ExtraOpts]),
case Res of
@ -682,6 +682,8 @@ listen_opt_type(max_stanza_size) ->
econf:pos_int(infinity);
listen_opt_type(max_fsm_queue) ->
econf:pos_int();
listen_opt_type(send_timeout) ->
econf:timeout(second, infinity);
listen_opt_type(shaper) ->
econf:shaper();
listen_opt_type(access) ->
@ -694,6 +696,7 @@ listen_options() ->
{transport, tcp},
{ip, {0,0,0,0}},
{accept_interval, 0},
{send_timeout, 15000},
{backlog, 5},
{use_proxy_protocol, false},
{supervisor, true}].

View File

@ -288,10 +288,11 @@ start(Level) ->
ConsoleFmtConfig = FmtConfig#{template => console_template()},
try
ok = logger:set_primary_config(level, Level),
ok = logger:update_formatter_config(default, ConsoleFmtConfig),
DefaultHandlerId = get_default_handlerid(),
ok = logger:update_formatter_config(DefaultHandlerId, ConsoleFmtConfig),
case quiet_mode() of
true ->
ok = logger:set_handler_config(default, level, critical);
ok = logger:set_handler_config(DefaultHandlerId, level, critical);
_ ->
ok
end,
@ -319,6 +320,13 @@ start(Level) ->
Err
end.
get_default_handlerid() ->
Ids = logger:get_handler_ids(),
case lists:member(default, Ids) of
true -> default;
false -> hd(Ids)
end.
-spec restart() -> ok.
restart() ->
ok.

View File

@ -61,11 +61,11 @@ doc() ->
desc =>
?T("The time of a cached item to keep in cache. "
"Once it's expired, the corresponding item is "
"erased from cache. The default value is 'one hour'. "
"erased from cache. The default value is '1 hour'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_life_time', 'oauth_cache_life_time', "
"'router_cache_life_time', and 'sm_cache_life_time'.")}},
"_`auth_cache_life_time`_, _`oauth_cache_life_time`_, "
"_`router_cache_life_time`_, and _`sm_cache_life_time`_.")}},
{cache_missed,
#{value => "true | false",
desc =>
@ -73,12 +73,12 @@ doc() ->
"an attempt to lookup for a value in a database and "
"this value is not found and the option is set to 'true', "
"this attempt will be cached and no attempts will be "
"performed until the cache expires (see 'cache_life_time'). "
"performed until the cache expires (see _`cache_life_time`_). "
"Usually you don't want to change it. Default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_missed', 'oauth_cache_missed', "
"'router_cache_missed', and 'sm_cache_missed'.")}},
"_`auth_cache_missed`_, _`oauth_cache_missed`_, "
"_`router_cache_missed`_, and _`sm_cache_missed`_.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
@ -93,16 +93,16 @@ doc() ->
"performance. The default value is '1000'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_size', 'oauth_cache_size', "
"'router_cache_size', and 'sm_cache_size'.")}},
"_`auth_cache_size`_, _`oauth_cache_size`_, "
"_`router_cache_size`_, and _`sm_cache_size`_.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Enable or disable cache. The default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_use_cache', 'oauth_use_cache', 'router_use_cache', "
"and 'sm_use_cache'.")}},
"_`auth_use_cache`_, _`oauth_use_cache`_, _`router_use_cache`_, "
"and _`sm_use_cache`_.")}},
{default_db,
#{value => "mnesia | sql",
desc =>
@ -122,14 +122,14 @@ doc() ->
"Modules may have its own value of the option. "
"The value of 'ram' means that queues will be kept in memory. "
"If value 'file' is set, you may also specify directory "
"in 'queue_dir' option where file queues will be placed. "
"in _`queue_dir`_ option where file queues will be placed. "
"The default value is 'ram'.")}},
{version,
#{value => "string()",
desc =>
?T("The option can be used to set custom ejabberd version, "
"that will be used by different parts of ejabberd, for "
"example by 'mod_version' module. The default value is "
"example by _`mod_version`_ module. The default value is "
"obtained at compile time from the underlying version "
"control system.")}},
{acl,
@ -141,7 +141,7 @@ doc() ->
"has name 'ACLName': it can be any string except 'all' or 'none' "
"(those are predefined names for the rules that match all or nothing "
"respectively). The name 'ACLName' can be referenced from other "
"parts of the configuration file, for example in 'access_rules' "
"parts of the configuration file, for example in _`access_rules`_ "
"option. The rules of 'ACLName' are represented by mapping "
"'pass:[{ACLType: ACLValue}]'. These can be one of the following:")},
[{user,
@ -225,7 +225,7 @@ doc() ->
"of the configuration file (mostly from 'access' options of "
"ejabberd modules). Each rule definition may contain "
"arbitrary number of 'allow' or 'deny' sections, and each "
"section may contain any number of ACL rules (see 'acl' option). "
"section may contain any number of ACL rules (see _`acl`_ option). "
"There are no access rules defined by default."),
example =>
["access_rules:",
@ -313,10 +313,12 @@ doc() ->
{anonymous_protocol,
#{value => "login_anon | sasl_anon | both",
desc =>
?T("'login_anon' means that the anonymous login method will be used. "
"'sasl_anon' means that the SASL Anonymous method will be used. "
"'both' means that SASL Anonymous and login anonymous are both "
"enabled. The default value is 'sasl_anon'.")}},
[?T("Define what anonymous protocol will be used: "), "",
?T("* 'login_anon' means that the anonymous login method will be used. "), "",
?T("* 'sasl_anon' means that the SASL Anonymous method will be used. "), "",
?T("* 'both' means that SASL Anonymous and login anonymous are both "
"enabled."), "",
?T("The default value is 'sasl_anon'."), ""]}},
{api_permissions,
#{value => "[Permission, ...]",
desc =>
@ -334,18 +336,18 @@ doc() ->
{auth_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to authentication cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to authentication cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{auth_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to authentication cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to authentication cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{auth_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to authentication cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to authentication cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{auth_method,
#{value => "[mnesia | sql | anonymous | external | jwt | ldap | pam, ...]",
desc =>
@ -359,38 +361,41 @@ doc() ->
desc =>
?T("This is used by the contributed module "
"'ejabberd_auth_http' that can be installed from the "
"'ejabberd-contrib' Git repository. Please refer to that "
"https://github.com/processone/ejabberd-contrib[ejabberd-contrib] "
"Git repository. Please refer to that "
"module's README file for details.")}},
{auth_password_format,
#{value => "plain | scram",
note => "improved in 20.01",
desc =>
?T("The option defines in what format the users passwords "
"are stored. 'plain': The password is stored as plain text "
[?T("The option defines in what format the users passwords "
"are stored:"), "",
?T("* 'plain': The password is stored as plain text "
"in the database. This is risky because the passwords "
"can be read if your database gets compromised. "
"This is the default value. This format allows clients to "
"authenticate using: the old Jabber Non-SASL (XEP-0078), "
"SASL PLAIN, SASL DIGEST-MD5, and SASL SCRAM-SHA-1. "
"'scram': The password is not stored, only some information "
"SASL PLAIN, SASL DIGEST-MD5, and SASL SCRAM-SHA-1. "), "",
?T("* 'scram': The password is not stored, only some information "
"that allows to verify the hash provided by the client. "
"It is impossible to obtain the original plain password "
"from the stored information; for this reason, when this "
"value is configured it cannot be changed to plain anymore. "
"This format allows clients to authenticate using: "
"SASL PLAIN and SASL SCRAM-SHA-1.")}},
"SASL PLAIN and SASL SCRAM-SHA-1."),
?T("The default value is 'plain'.")]}},
{auth_scram_hash,
#{value => "sha | sha256 | sha512",
desc =>
?T("Hash algorith that should be used to store password in SCRAM format. "
"You shouldn't change this if you already have passwords generated with "
"a different algorithm - users that have such passwords will not be able "
"to authenticate.")}},
"to authenticate. The default value is 'sha'.")}},
{auth_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to authentication cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to authentication cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{c2s_cafile,
#{value => ?T("Path"),
desc =>
@ -449,22 +454,22 @@ doc() ->
{captcha_cmd,
#{value => ?T("Path"),
desc =>
?T("Full path to a script that generates CAPTCHA images. "
?T("Full path to a script that generates http://../basic/#captcha[CAPTCHA] images. "
"There is no default value: when this option is not "
"set, CAPTCHA functionality is completely disabled.")}},
{captcha_limit,
#{value => "pos_integer() | infinity",
desc =>
?T("Maximum number of CAPTCHA generated images per minute for "
?T("Maximum number of http://../basic/#captcha[CAPTCHA] generated images per minute for "
"any given JID. The option is intended to protect the server "
"from CAPTCHA DoS. The default value is 'infinity'.")}},
{captcha_host,
#{value => "String",
desc => ?T("Deprecated. Use 'captcha_url' instead.")}},
desc => ?T("Deprecated. Use _`captcha_url`_ instead.")}},
{captcha_url,
#{value => ?T("URL"),
desc =>
?T("An URL where CAPTCHA requests should be sent. NOTE: you need "
?T("An URL where http://../basic/#captcha[CAPTCHA] requests should be sent. NOTE: you need "
"to configure 'request_handlers' for 'ejabberd_http' listener "
"as well. There is no default value.")}},
{certfiles,
@ -674,7 +679,8 @@ doc() ->
desc =>
?T("The option defines the default language of server strings "
"that can be seen by XMPP clients. If an XMPP client does not "
"possess 'xml:lang' attribute, the specified language is used.")}},
"possess 'xml:lang' attribute, the specified language is used. "
"The default value is '\"en\"'.")}},
{ldap_servers,
#{value => "[Host, ...]",
desc =>
@ -684,7 +690,7 @@ doc() ->
#{value => "[Host, ...]",
desc =>
?T("A list of IP addresses or DNS names of LDAP backup servers. "
"When no servers listed in 'ldap_servers' option are reachable, "
"When no servers listed in _`ldap_servers`_ option are reachable, "
"ejabberd will try to connect to these backup servers. "
"The default is an empty list, i.e. no backup servers specified. "
"WARNING: ejabberd doesn't try to reconnect back to the main "
@ -791,7 +797,7 @@ doc() ->
"the result set. There is no default value, which means the "
"result is not filtered. WARNING: Since this filter makes "
"additional LDAP lookups, use it only as the last resort: "
"try to define all filter rules in 'ldap_filter' option if possible."),
"try to define all filter rules in _`ldap_filter`_ option if possible."),
example =>
["ldap_dn_filter:",
" \"(&(name=%s)(owner=%D)(user=%u@%d))\": [sn]"]}},
@ -799,7 +805,8 @@ doc() ->
#{value => ?T("Number"),
desc =>
?T("The number of rotated log files to keep. "
"The default value is '1'.")}},
"The default value is '1', which means that only keeps "
"`ejabberd.log.0`, `error.log.0` and `crash.log.0`.")}},
{log_rotate_size,
#{value => "pos_integer() | infinity",
desc =>
@ -835,7 +842,7 @@ doc() ->
"must have identical value on all nodes, or it will lead to subtle "
"bugs. Usually leaving default value of this is option is best, "
"tweak it only if you know what you are doing. "
"The default value is '1' minute.")}},
"The default value is '1 minute'.")}},
{new_sql_schema,
#{value => "true | false",
desc =>
@ -861,13 +868,13 @@ doc() ->
{oauth_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to OAuth cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{oauth_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to OAuth cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{oauth_cache_rest_failure_life_time,
#{value => "timeout()",
note => "added in 21.01",
@ -877,8 +884,8 @@ doc() ->
{oauth_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to OAuth cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{oauth_client_id_check,
#{value => "allow | db | deny",
desc =>
@ -888,13 +895,13 @@ doc() ->
{oauth_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to OAuth cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to OAuth cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{oauth_db_type,
#{value => "mnesia | sql",
desc =>
?T("Database backend to use for OAuth authentication. "
"The default value is picked from 'default_db' option, or "
"The default value is picked from _`default_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{oauth_expire,
#{value => "timeout()",
@ -908,7 +915,7 @@ doc() ->
desc =>
?T("Enable or disable OOM (out-of-memory) killer. "
"When system memory raises above the limit defined in "
"'oom_watermark' option, ejabberd triggers OOM killer "
"_`oom_watermark`_ option, ejabberd triggers OOM killer "
"to terminate most memory consuming Erlang processes. "
"Note that in order to maintain functionality, ejabberd only "
"attempts to kill transient processes, such as those managing "
@ -919,14 +926,14 @@ doc() ->
desc =>
?T("Trigger OOM killer when some of the running Erlang processes "
"have messages queue above this 'Size'. Note that "
"such processes won't be killed if 'oom_killer' option is set "
"such processes won't be killed if _`oom_killer`_ option is set "
"to 'false' or if 'oom_watermark' is not reached yet.")}},
{oom_watermark,
#{value => ?T("Percent"),
desc =>
?T("A percent of total system memory consumed at which "
"OOM killer should be activated with some of the processes "
"possibly be killed (see 'oom_killer' option). Later, when "
"possibly be killed (see _`oom_killer`_ option). Later, when "
"memory drops below this 'Percent', OOM killer is deactivated. "
"The default value is '80' percents.")}},
{outgoing_s2s_families,
@ -979,7 +986,7 @@ doc() ->
{queue_dir,
#{value => ?T("Directory"),
desc =>
?T("If 'queue_type' option is set to 'file', use this 'Directory' "
?T("If _`queue_type`_ option is set to 'file', use this 'Directory' "
"to store file queues. The default is to keep queues inside "
"Mnesia directory.")}},
{redis_connect_timeout,
@ -1009,8 +1016,8 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of request queue for the Redis server. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{redis_server,
#{value => ?T("Hostname"),
@ -1020,7 +1027,7 @@ doc() ->
{registration_timeout,
#{value => "timeout()",
desc =>
?T("This is a global option for module 'mod_register'. "
?T("This is a global option for module _`mod_register`_. "
"It limits the frequency of registrations from a given "
"IP or username. So, a user that tries to register a "
"new account from the same IP address or JID during "
@ -1043,29 +1050,29 @@ doc() ->
{router_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to routing table cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to routing table cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{router_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to routing table cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to routing table cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{router_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to routing table cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to routing table cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{router_db_type,
#{value => "mnesia | redis | sql",
desc =>
?T("Database backend to use for routing information. "
"The default value is picked from 'default_ram_db' option, or "
"The default value is picked from _`default_ram_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{router_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to routing table cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to routing table cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{rpc_timeout,
#{value => "timeout()",
desc =>
@ -1142,14 +1149,14 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of a queue for s2s packets. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{s2s_timeout,
#{value => "timeout()",
desc =>
?T("A time to wait before closing an idle s2s connection. "
"The default value is '10' minutes.")}},
"The default value is '10 minutes'.")}},
{s2s_use_starttls,
#{value => "true | false | optional | required",
desc =>
@ -1171,7 +1178,7 @@ doc() ->
desc =>
?T("The option defines a set of shapers. Every shaper is assigned "
"a name 'ShaperName' that can be used in other parts of the "
"configuration file, such as 'shaper_rules' option. The shaper "
"configuration file, such as _`shaper_rules`_ option. The shaper "
"itself is defined by its 'Rate', where 'Rate' stands for the "
"maximum allowed incoming rate in **bytes** per second. "
"When a connection exceeds this limit, ejabberd stops reading "
@ -1187,9 +1194,9 @@ doc() ->
#{value => "{ShaperRuleName: {Number|ShaperName: ACLRule|ACLName}}",
desc =>
?T("An entry allowing to declaring shaper to use for matching user/hosts. "
"Semantics is similar to 'access_rules' option, the only difference is "
"Semantics is similar to _`access_rules`_ option, the only difference is "
"that instead using 'allow' or 'deny', a name of a shaper (defined in "
"'shaper' option) or a positive number should be used."),
"_`shaper`_ option) or a positive number should be used."),
example =>
["shaper_rules:",
" connections_limit:",
@ -1205,29 +1212,29 @@ doc() ->
{sm_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to client sessions table cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{sm_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to client sessions table cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{sm_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to client sessions table cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{sm_db_type,
#{value => "mnesia | redis | sql",
desc =>
?T("Database backend to use for client sessions information. "
"The default value is picked from 'default_ram_db' option, or "
"The default value is picked from _`default_ram_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{sm_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to client sessions table cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to client sessions table cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{sql_type,
#{value => "mssql | mysql | odbc | pgsql | sqlite",
desc =>
@ -1253,7 +1260,7 @@ doc() ->
note => "added in 20.12",
desc =>
?T("Path to the ODBC driver to use to connect to a Microsoft SQL "
"Server database. This option is only valid if the 'sql_type' "
"Server database. This option is only valid if the _`sql_type`_ "
"option is set to 'mssql'. "
"The default value is: 'libtdsodbc.so'")}},
{sql_password,
@ -1288,8 +1295,8 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of a request queue for the SQL server. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{sql_server,
#{value => ?T("Host"),
@ -1307,15 +1314,15 @@ doc() ->
#{value => ?T("Path"),
desc =>
?T("A path to a file with CA root certificates that will "
"be used to verify SQL connections. Implies 'sql_ssl' "
"and 'sql_ssl_verify' options are set to 'true'. "
"be used to verify SQL connections. Implies _`sql_ssl`_ "
"and _`sql_ssl_verify`_ options are set to 'true'. "
"There is no default which means "
"certificate verification is disabled.")}},
{sql_ssl_certfile,
#{value => ?T("Path"),
desc =>
?T("A path to a certificate file that will be used "
"for SSL connections to the SQL server. Implies 'sql_ssl' "
"for SSL connections to the SQL server. Implies _`sql_ssl`_ "
"option is set to 'true'. There is no default which means "
"ejabberd won't provide a client certificate to the SQL "
"server.")}},
@ -1323,8 +1330,8 @@ doc() ->
#{value => "true | false",
desc =>
?T("Whether to verify SSL connection to the SQL server against "
"CA root certificates defined in 'sql_ssl_cafile' option. "
"Implies 'sql_ssl' option is set to 'true'. "
"CA root certificates defined in _`sql_ssl_cafile`_ option. "
"Implies _`sql_ssl`_ option is set to 'true'. "
"The default value is 'false'.")}},
{sql_start_interval,
#{value => "timeout()",

View File

@ -424,15 +424,15 @@ balancing_route(From, To, Packet, Rs) ->
Value = erlang:system_time(),
case [R || R <- Rs, node(R#route.pid) == node()] of
[] ->
R = lists:nth(erlang:phash(Value, length(Rs)), Rs),
R = lists:nth(erlang:phash2(Value, length(Rs))+1, Rs),
do_route(Packet, R);
LRs ->
R = lists:nth(erlang:phash(Value, length(LRs)), LRs),
R = lists:nth(erlang:phash2(Value, length(LRs))+1, LRs),
do_route(Packet, R)
end;
Value ->
SRs = lists:ukeysort(#route.local_hint, Rs),
R = lists:nth(erlang:phash(Value, length(SRs)), SRs),
R = lists:nth(erlang:phash2(Value, length(SRs))+1, SRs),
do_route(Packet, R)
end.

View File

@ -30,7 +30,7 @@
-behaviour(gen_server).
%% API
-export([route_multicast/4,
-export([route_multicast/5,
register_route/1,
unregister_route/1
]).
@ -39,7 +39,7 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
terminate/2, code_change/3, update_to_in_wrapped/2]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
@ -58,9 +58,11 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route_multicast(jid(), binary(), [jid()], stanza()) -> ok.
route_multicast(From, Domain, Destinations, Packet) ->
case catch do_route(Domain, Destinations, xmpp:set_from(Packet, From)) of
-spec route_multicast(jid(), binary(), [jid()], stanza(), boolean()) -> ok.
route_multicast(From0, Domain0, Destinations0, Packet0, Wrapped0) ->
{From, Domain, Destinations, Packet, Wrapped} =
ejabberd_hooks:run_fold(multicast_route, Domain0, {From0, Domain0, Destinations0, Packet0, Wrapped0}, []),
case catch do_route(Domain, Destinations, xmpp:set_from(Packet, From), Wrapped) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, Domain, Destinations, Packet}]);
@ -157,7 +159,7 @@ handle_cast(Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route_multicast, Domain, Destinations, Packet}, State) ->
case catch do_route(Domain, Destinations, Packet) of
case catch do_route(Domain, Destinations, Packet, false) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {Domain, Destinations, Packet}]);
@ -204,13 +206,41 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-spec update_to_in_wrapped(stanza(), jid()) -> stanza().
update_to_in_wrapped(Packet, To) ->
case Packet of
#message{sub_els = [#ps_event{
items = #ps_items{
items = [#ps_item{
sub_els = [Internal]
} = PSItem]
} = PSItems
} = PSEvent]} ->
Internal2 = xmpp:set_to(Internal, To),
PSItem2 = PSItem#ps_item{sub_els = [Internal2]},
PSItems2 = PSItems#ps_items{items = [PSItem2]},
PSEvent2 = PSEvent#ps_event{items = PSItems2},
xmpp:set_to(Packet#message{sub_els = [PSEvent2]}, To);
_ ->
xmpp:set_to(Packet, To)
end.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%% From = #jid
%% Destinations = [#jid]
-spec do_route(binary(), [jid()], stanza()) -> any().
do_route(Domain, Destinations, Packet) ->
-spec do_route(binary(), [jid()], stanza(), boolean()) -> any().
do_route(Domain, Destinations, Packet, true) ->
?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n",
[xmpp:pp(Packet), Domain,
str:join([jid:encode(To) || To <- Destinations], <<", ">>)]),
lists:foreach(
fun(To) ->
Packet2 = update_to_in_wrapped(Packet, To),
ejabberd_router:route(Packet2)
end, Destinations);
do_route(Domain, Destinations, Packet, false) ->
?DEBUG("Route multicast:~n~ts~nDomain: ~ts~nDestinations: ~ts~n",
[xmpp:pp(Packet), Domain,
str:join([jid:encode(To) || To <- Destinations], <<", ">>)]),
@ -236,4 +266,7 @@ pick_multicast_pid(Rs) ->
-spec do_route_normal([jid()], stanza()) -> any().
do_route_normal(Destinations, Packet) ->
[ejabberd_router:route(xmpp:set_to(Packet, To)) || To <- Destinations].
lists:foreach(
fun(To) ->
ejabberd_router:route(xmpp:set_to(Packet, To))
end, Destinations).

View File

@ -429,8 +429,8 @@ choose_pid(From, Pids) ->
Ps -> Ps
end,
Pid =
lists:nth(erlang:phash(jid:remove_resource(From),
length(Pids1)),
lists:nth(erlang:phash2(jid:remove_resource(From),
length(Pids1))+1,
Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
Pid.

View File

@ -602,12 +602,14 @@ list_vhosts2(Lang, Hosts) ->
[?AC(<<"../server/", Host/binary,
"/">>,
Host)]),
?XAC(<<"td">>,
?XAE(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(RegisteredUsers))),
?XAC(<<"td">>,
[?AC(<<"../server/", Host/binary, "/users/">>,
pretty_string_int(RegisteredUsers))]),
?XAE(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(OnlineUsers)))])
[?AC(<<"../server/", Host/binary, "/online-users/">>,
pretty_string_int(OnlineUsers))])])
end,
SHosts)))])].

View File

@ -84,14 +84,14 @@ code_change(_OldVsn, State, _Extra) ->
%% -- ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{name = modules_update_specs,
tags = [admin,modules],
tags = [modules],
desc = "Update the module source code from Git",
longdesc = "A connection to Internet is required",
module = ?MODULE, function = update,
args = [],
result = {res, rescode}},
#ejabberd_commands{name = modules_available,
tags = [admin,modules],
tags = [modules],
desc = "List the contributed modules available to install",
module = ?MODULE, function = available_command,
result_desc = "List of tuples with module name and description",
@ -103,7 +103,7 @@ get_commands_spec() ->
[{name, atom},
{summary, string}]}}}}},
#ejabberd_commands{name = modules_installed,
tags = [admin,modules],
tags = [modules],
desc = "List the contributed modules already installed",
module = ?MODULE, function = installed_command,
result_desc = "List of tuples with module name and description",
@ -115,7 +115,7 @@ get_commands_spec() ->
[{name, atom},
{summary, string}]}}}}},
#ejabberd_commands{name = module_install,
tags = [admin,modules],
tags = [modules],
desc = "Compile, install and start an available contributed module",
module = ?MODULE, function = install,
args_desc = ["Module name"],
@ -123,7 +123,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_uninstall,
tags = [admin,modules],
tags = [modules],
desc = "Uninstall a contributed module",
module = ?MODULE, function = uninstall,
args_desc = ["Module name"],
@ -131,7 +131,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_upgrade,
tags = [admin,modules],
tags = [modules],
desc = "Upgrade the running code of an installed module",
longdesc = "In practice, this uninstalls and installs the module",
module = ?MODULE, function = upgrade,
@ -140,7 +140,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_check,
tags = [admin,modules],
tags = [modules],
desc = "Check the contributed module repository compliance",
module = ?MODULE, function = check,
args_desc = ["Module name"],
@ -638,6 +638,7 @@ install(Module, Spec, SrcDir, LibDir) ->
Errors = lists:dropwhile(fun({_, ok}) -> true;
(_) -> false
end, Files1++Files2),
inform_module_configuration(Module, LibDir, Files1),
Result = case Errors of
[{F, {error, E}}|_] ->
{error, {F, E}};
@ -649,6 +650,24 @@ install(Module, Spec, SrcDir, LibDir) ->
file:set_cwd(CurDir),
Result.
inform_module_configuration(Module, LibDir, Files1) ->
Res = lists:filter(fun({[$c, $o, $n, $f |_], ok}) -> true;
(_) -> false
end, Files1),
case Res of
[{ConfigPath, ok}] ->
FullConfigPath = filename:join(LibDir, ConfigPath),
io:format("Module ~p has been installed and started.~n"
"It's configured in the file:~n ~s~n"
"Configure the module in that file, or remove it~n"
"and configure in your main ejabberd.yml~n",
[Module, FullConfigPath]);
[] ->
io:format("Module ~p has been installed.~n"
"Now you can configure it in your ejabberd.yml~n",
[Module])
end.
%% -- minimalist rebar spec parser, only support git
fetch_rebar_deps(SrcDir) ->

View File

@ -122,6 +122,11 @@
{result, {default, broadcast}} |
{error, stanza_error()}.
-callback remove_extra_items(NodeIdx :: nodeIdx(),
Max_Items :: unlimited | non_neg_integer()) ->
{result, {[itemId()], [itemId()]}
}.
-callback remove_extra_items(NodeIdx :: nodeIdx(),
Max_Items :: unlimited | non_neg_integer(),
ItemIds :: [itemId()]) ->

View File

@ -67,6 +67,9 @@
-callback get_nodes(Host :: host())->
[pubsubNode()].
-callback get_all_nodes(Host :: host()) ->
[pubsubNode()].
-callback get_parentnodes(Host :: host(),
NodeId :: nodeId(),
From :: jid:jid()) ->

View File

@ -92,7 +92,7 @@
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
@ -662,6 +662,7 @@ get_commands_spec() ->
"For example:\n"
" ejabberdctl srg_create group3 myserver.com "
"name desc \\\"group1\\\\ngroup2\\\"",
note = "changed in 21.07",
module = ?MODULE, function = srg_create,
args = [{group, binary}, {host, binary},
{label, binary}, {description, binary}, {display, binary}],
@ -1651,14 +1652,6 @@ mod_doc() ->
?T("If you want to put a group Name with blankspaces, use the "
"characters \"\' and \'\" to define when the Name starts and "
"ends. See an example below.")],
opts =>
[{module_resource,
#{value => ?T("Resource"),
desc =>
?T("Indicate the resource that the XMPP stanzas must use "
"in the FROM or TO JIDs. This is only useful in the "
"'get_vcard*' and 'set_vcard*' commands. The default "
"value is 'mod_admin_extra'.")}}],
example =>
[{?T("With this configuration, vCards can only be modified with "
"mod_admin_extra commands:"),
@ -1669,8 +1662,7 @@ mod_doc() ->
" vcard_set:",
" - allow: adminextraresource",
"modules:",
" mod_admin_extra:",
" module_resource: \"modadminextraf8x,31ad\"",
" mod_admin_extra: {}",
" mod_vcard:",
" access_set: vcard_set"]},
{?T("Content of roster file for 'pushroster' command:"),

View File

@ -46,7 +46,7 @@
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(_Host) ->
ejabberd_commands:unregister_commands(get_commands_spec()).
@ -364,6 +364,6 @@ mod_doc() ->
#{desc =>
?T("This module can be used to update existing SQL database "
"from the default to the new schema. Check the section "
"http://../database-ldap/#default-and-new-schemas[Default and New Schemas] for details. "
"http://../database/#default-and-new-schemas[Default and New Schemas] for details. "
"Please note that only PostgreSQL is supported. "
"When the module is loaded use 'update_sql' ejabberdctl command.")}.
"When the module is loaded use _`update_sql`_ API.")}.

View File

@ -930,7 +930,7 @@ mod_doc() ->
"should be disabled for instances of ejabberd with hundreds of "
"thousands users."), "",
?T("The Ad-hoc Commands are listed in the Server Discovery. "
"For this feature to work, 'mod_adhoc' must be enabled."), "",
"For this feature to work, _`mod_adhoc`_ must be enabled."), "",
?T("The specific JIDs where messages can be sent are listed below. "
"The first JID in each entry will apply only to the specified "
"virtual host example.org, while the JID between brackets "
@ -940,7 +940,7 @@ mod_doc() ->
"online and connected to several resources, only the resource "
"with the highest priority will receive the message. "
"If the registered user is not connected, the message will be "
"stored offline in assumption that offline storage (see 'mod_offline') "
"stored offline in assumption that offline storage (see _`mod_offline`_) "
"is enabled."),
"- example.org/announce/online (example.org/announce/all-hosts/online)::",
?T("The message is sent to all connected users. If the user is "
@ -965,20 +965,20 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.

View File

@ -469,8 +469,8 @@ mod_doc() ->
"[XEP-0398: User Avatar to vCard-Based Avatars Conversion]."), "",
?T("Also, the module supports conversion between avatar "
"image formats on the fly."), "",
?T("The module depends on 'mod_vcard', 'mod_vcard_xupdate' and "
"'mod_pubsub'.")],
?T("The module depends on _`mod_vcard`_, _`mod_vcard_xupdate`_ and "
"_`mod_pubsub`_.")],
opts =>
[{convert,
#{value => "{From: To}",

View File

@ -240,27 +240,27 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{ram_db_type,
#{value => "mnesia | sql | redis",
desc =>
?T("Same as 'default_ram_db' but applied to this module only.")}},
?T("Same as _`default_ram_db`_ but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
["listen:",
" -",

View File

@ -595,25 +595,25 @@ mod_doc() ->
"https://xmpp.org/extensions/xep-0115.html"
"[XEP-0115: Entity Capabilities]."),
?T("The main purpose of the module is to provide "
"PEP functionality (see 'mod_pubsub').")],
"PEP functionality (see _`mod_pubsub`_).")],
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.

View File

@ -1564,4 +1564,4 @@ mod_doc() ->
?T("The module provides server configuration functionality via "
"https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. This module requires "
"'mod_adhoc' to be loaded.")}.
"_`mod_adhoc`_ to be loaded.")}.

View File

@ -95,7 +95,7 @@ mod_doc() ->
?T("WARNING: Security issue: Namespace delegation gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust the component."), "",
?T("NOTE: This module is complementary to 'mod_privilege' but can "
?T("NOTE: This module is complementary to _`mod_privilege`_ but can "
"also be used separately.")],
opts =>
[{namespaces,

View File

@ -464,11 +464,11 @@ mod_doc() ->
" -",
" modules: all",
" name: abuse-addresses",
" urls: [mailto:abuse@shakespeare.lit]",
" urls: [\"mailto:abuse@shakespeare.lit\"]",
" -",
" modules: [mod_muc]",
" name: \"Web chatroom logs\"",
" urls: [http://www.example.org/muc-logs]",
" urls: [\"http://www.example.org/muc-logs\"]",
" -",
" modules: [mod_disco]",
" name: feedback-addresses",

View File

@ -107,7 +107,7 @@ c2s_stream_started(#{ip := {Addr, _}} = State, _) ->
start(Host, Opts) ->
catch ets:new(failed_auth, [named_table, public,
{heir, erlang:group_leader(), none}]),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->

View File

@ -527,10 +527,20 @@ mod_doc() ->
[?T("This module provides a ReST API to call ejabberd commands "
"using JSON data."), "",
?T("To use this module, in addition to adding it to the 'modules' "
"section, you must also add it to 'request_handlers' of some "
"listener."), "",
"section, you must also enable it in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers]."), "",
?T("To use a specific API version N, when defining the URL path "
"in the request_handlers, add a 'vN'. "
"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>'")]}.
"URL: 'http://localhost:5280/api/<command_name>'")],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /api: mod_http_api",
"",
"modules:",
" mod_http_api: {}"]}.

View File

@ -232,8 +232,9 @@ mod_doc() ->
"[XEP-0363: HTTP File Upload]. If the request is accepted, "
"the client receives a URL for uploading the file and "
"another URL from which that file can later be downloaded."), "",
?T("In order to use this module, it must be configured as "
"a 'request_handler' for 'ejabberd_http' listener.")],
?T("In order to use this module, it must be enabled "
"in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers].")],
opts =>
[{host,
#{desc => ?T("Deprecated. Use 'hosts' instead.")}},
@ -320,17 +321,18 @@ mod_doc() ->
"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\".")}},
"The default value is \"https://@HOST@:5443/upload\".")}},
{get_url,
#{value => ?T("URL"),
desc =>
?T("This option specifies the initial part of the GET URLs "
"used for downloading the files. By default, it is set "
"used for downloading the files. The default value is 'undefined'. "
"When this option is 'undefined', this option is set "
"to the same value as 'put_url'. The keyword @HOST@ is "
"replaced with the virtual host name. NOTE: if GET requests "
"are handled by 'mod_http_upload', the 'get_url' must match the "
"'put_url'. Setting it to a different value only makes "
"sense if an external web server or 'mod_http_fileserver' "
"sense if an external web server or _`mod_http_fileserver`_ "
"is used to serve the uploaded files.")}},
{service_url,
#{desc => ?T("Deprecated.")}},

View File

@ -27,7 +27,6 @@
-author('holger@zedat.fu-berlin.de').
-define(TIMEOUT, timer:hours(24)).
-define(INITIAL_TIMEOUT, timer:minutes(10)).
-define(FORMAT(Error), file:format_error(Error)).
-behaviour(gen_server).
@ -64,7 +63,7 @@
max_days :: pos_integer() | infinity,
docroot :: binary(),
disk_usage = #{} :: disk_usage(),
timers :: [timer:tref()]}).
timer :: reference() | undefined}).
-type disk_usage() :: #{{binary(), binary()} => non_neg_integer()}.
-type state() :: #state{}.
@ -166,12 +165,11 @@ init([ServerHost|_]) ->
DocRoot1 = mod_http_upload_opt:docroot(ServerHost),
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
Timers = if MaxDays == infinity -> [];
true ->
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
{ok, T2} = timer:send_interval(?TIMEOUT, sweep),
[T1, T2]
end,
Timer = if MaxDays == infinity -> undefined;
true ->
Timeout = p1_rand:uniform(?TIMEOUT div 2),
erlang:send_after(Timeout, self(), sweep)
end,
ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
handle_slot_request, 50),
{ok, #state{server_host = ServerHost,
@ -179,7 +177,7 @@ init([ServerHost|_]) ->
access_hard_quota = AccessHardQuota,
max_days = MaxDays,
docroot = DocRoot3,
timers = Timers}}.
timer = Timer}}.
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
handle_call(Request, From, State) ->
@ -249,6 +247,7 @@ handle_info(sweep, #state{server_host = ServerHost,
max_days = MaxDays} = State)
when is_integer(MaxDays), MaxDays > 0 ->
?DEBUG("Got 'sweep' message for ~ts", [ServerHost]),
Timer = erlang:send_after(?TIMEOUT, self(), sweep),
case file:list_dir(DocRoot) of
{ok, Entries} ->
BackThen = secs_since_epoch() - (MaxDays * 86400),
@ -264,17 +263,17 @@ handle_info(sweep, #state{server_host = ServerHost,
?ERROR_MSG("Cannot open document root ~ts: ~ts",
[DocRoot, ?FORMAT(Error)])
end,
{noreply, State};
{noreply, State#state{timer = Timer}};
handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
terminate(Reason, #state{server_host = ServerHost, timer = Timer}) ->
?DEBUG("Stopping upload quota process for ~ts: ~p", [ServerHost, Reason]),
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
handle_slot_request, 50),
lists:foreach(fun timer:cancel/1, Timers).
misc:cancel_timer(Timer).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->

View File

@ -344,20 +344,20 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.

View File

@ -148,7 +148,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(check_create_room, Host, ?MODULE,
check_create_room, 50)
end,
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
ok;
Err ->
Err
@ -1456,7 +1456,7 @@ mod_doc() ->
#{value => "true | false",
desc =>
?T("This option determines how ejabberd's "
"stream management code (see 'mod_stream_mgmt') "
"stream management code (see _`mod_stream_mgmt`_) "
"handles unacknowledged messages when the "
"connection is lost. Usually, such messages are "
"either bounced or resent. However, neither is "
@ -1495,28 +1495,28 @@ mod_doc() ->
#{value => "true | false",
desc =>
?T("Whether to destroy message archive of a room "
"(see 'mod_muc') when it gets destroyed. "
"(see _`mod_muc`_) when it gets destroyed. "
"The default value is 'true'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}},
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}},
{user_mucsub_from_muc_archive,
#{value => "true | false",
desc =>

View File

@ -72,7 +72,7 @@ remove_from_archive(LUser, LServer, WithJid) ->
end.
delete_old_messages(ServerHost, TimeStamp, Type) ->
TS = now_to_usec(TimeStamp),
TS = misc:now_to_usec(TimeStamp),
case Type of
all ->
ejabberd_sql:sql_query(
@ -315,7 +315,7 @@ export(_Server) ->
id = _ID, timestamp = TS, peer = Peer,
type = Type, nick = Nick, packet = Pkt})
when LServer == Host ->
TStmp = now_to_usec(TS),
TStmp = misc:now_to_usec(TS),
SUser = case Type of
chat -> LUser;
groupchat -> jid:encode({LUser, LServer, <<>>})
@ -372,16 +372,6 @@ is_empty_for_room(LServer, LName, LHost) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
usec_to_now(Int) ->
Secs = Int div 1000000,
USec = Int rem 1000000,
MSec = Secs div 1000000,
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
Start = proplists:get_value(start, MAMQuery),
End = proplists:get_value('end', MAMQuery),
@ -432,14 +422,14 @@ make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
StartClause = case Start of
{_, _, _} ->
[<<" and timestamp >= ">>,
integer_to_binary(now_to_usec(Start))];
integer_to_binary(misc:now_to_usec(Start))];
_ ->
[]
end,
EndClause = case End of
{_, _, _} ->
[<<" and timestamp <= ">>,
integer_to_binary(now_to_usec(End))];
integer_to_binary(misc:now_to_usec(End))];
_ ->
[]
end,
@ -526,7 +516,7 @@ make_archive_el(User, TS, XML, Peer, Kind, Nick, MsgType, JidRequestor, JidArchi
TSInt ->
try jid:decode(Peer) of
PeerJID ->
Now = usec_to_now(TSInt),
Now = misc:usec_to_now(TSInt),
PeerLJID = jid:tolower(PeerJID),
T = case Kind of
<<"">> -> chat;

View File

@ -24,7 +24,7 @@
-module(mod_mix).
-behaviour(gen_mod).
-behaviour(gen_server).
-protocol({xep, 369, '0.13.0'}).
-protocol({xep, 369, '0.14.1'}).
%% API
-export([route/1]).
@ -106,12 +106,12 @@ mod_doc() ->
"experimental feature, updated in 19.02, and is not "
"yet ready to use in production. It's asserted that "
"the MIX protocol is going to replace the MUC protocol "
"in the future (see 'mod_muc')."), "",
"in the future (see _`mod_muc`_)."), "",
?T("To learn more about how to use that feature, you can refer to "
"our tutorial: https://docs.ejabberd.im/tutorials/mix-010/"
"[Getting started with XEP-0369: Mediated Information "
"eXchange (MIX) v0.1]."), "",
?T("The module depends on 'mod_mam'.")],
?T("The module depends on _`mod_mam`_.")],
opts =>
[{access_create,
#{value => ?T("AccessName"),
@ -136,7 +136,7 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}}]}.
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}}]}.
-spec route(stanza()) -> ok.
route(#iq{} = IQ) ->
@ -166,7 +166,7 @@ process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To,
[ServerHost, ?MODULE, <<"">>, Lang]),
Name = mod_mix_opt:name(ServerHost),
Identity = #identity{category = <<"conference">>,
type = <<"text">>,
type = <<"mix">>,
name = translate:translate(Lang, Name)},
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_MIX_CORE_0, ?NS_MIX_CORE_SEARCHABLE_0,

View File

@ -120,23 +120,23 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
-spec bounce_sm_packet({term(), stanza()}) -> {term(), stanza()}.
bounce_sm_packet({_, #message{to = #jid{lresource = <<>>} = To,

View File

@ -278,8 +278,9 @@ listen_options() ->
%%%===================================================================
mod_doc() ->
#{desc =>
?T("This module adds support for the MQTT protocol "
"version '3.1.1' and '5.0'. Remember to configure "
?T("This module adds "
"https://docs.ejabberd.im/admin/guide/mqtt/[support for the MQTT] "
"protocol version '3.1.1' and '5.0'. Remember to configure "
"'mod_mqtt' in 'modules' and 'listen' sections."),
opts =>
[{access_subscribe,
@ -326,37 +327,37 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, "
?T("Same as top-level _`queue_type`_ option, "
"but applied to this module only.")}},
{ram_db_type,
#{value => "mnesia",
desc =>
?T("Same as top-level 'default_ram_db' option, "
?T("Same as top-level _`default_ram_db`_ option, "
"but applied to this module only.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, "
?T("Same as top-level _`default_db`_ option, "
"but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, "
?T("Same as top-level _`use_cache`_ option, "
"but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, "
?T("Same as top-level _`cache_size`_ option, "
"but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, "
?T("Same as top-level _`cache_missed`_ option, "
"but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, "
?T("Same as top-level _`cache_life_time`_ option, "
"but applied to this module only.")}}]}.
%%%===================================================================
@ -600,6 +601,23 @@ match([H|T1], [<<"%c">>|T2], U, S, R) ->
R -> match(T1, T2, U, S, R);
_ -> false
end;
match([H|T1], [<<"%g">>|T2], U, S, R) ->
case jid:resourceprep(H) of
H ->
case acl:loaded_shared_roster_module(S) of
undefined -> false;
Mod ->
case Mod:get_group_opts(S, H) of
error -> false;
_ ->
case Mod:is_user_in_group({U, S}, H, S) of
true -> match(T1, T2, U, S, R);
_ -> false
end
end
end;
_ -> false
end;
match([H|T1], [H|T2], U, S, R) ->
match(T1, T2, U, S, R);
match([], [], _, _, _) ->

View File

@ -40,6 +40,7 @@
room_destroyed/4,
store_room/4,
store_room/5,
store_changes/4,
restore_room/3,
forget_room/3,
create_room/3,
@ -91,6 +92,7 @@
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok.
-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
-callback store_changes(binary(), binary(), binary(), list()) -> {atomic, any()}.
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
@ -111,7 +113,8 @@
-callback get_subscribed_rooms(binary(), binary(), jid()) ->
{ok, [{jid(), binary(), [binary()]}]} | {error, db_failure}.
-optional_callbacks([get_subscribed_rooms/3]).
-optional_callbacks([get_subscribed_rooms/3,
store_changes/4]).
%%====================================================================
%% API
@ -313,6 +316,11 @@ store_room(ServerHost, Host, Name, Opts, ChangesHints) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_room(LServer, Host, Name, Opts, ChangesHints).
store_changes(ServerHost, Host, Name, ChangesHints) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_changes(LServer, Host, Name, ChangesHints).
restore_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
@ -570,7 +578,7 @@ unhibernate_room(ServerHost, Host, Room) ->
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
Proc = procname(ServerHost, {Room, Host}),
case ?GEN_SERVER:call(Proc, {unhibernate, Room, Host}) of
case ?GEN_SERVER:call(Proc, {unhibernate, Room, Host}, 20000) of
{ok, _} = R -> R;
_ -> error
end;
@ -1358,19 +1366,19 @@ mod_doc() ->
desc =>
?T("To configure who is allowed to create new rooms at the "
"Multi-User Chat service, this option can be used. "
"By default any account in the local ejabberd server is "
"The default value is 'all', which means everyone is "
"allowed to create rooms.")}},
{access_persistent,
#{value => ?T("AccessName"),
desc =>
?T("To configure who is allowed to modify the 'persistent' room option. "
"By default any account in the local ejabberd server is allowed to "
"The default value is 'all', which means everyone is allowed to "
"modify that option.")}},
{access_mam,
#{value => ?T("AccessName"),
desc =>
?T("To configure who is allowed to modify the 'mam' room option. "
"By default any account in the local ejabberd server is allowed to "
"The default value is 'all', which means everyone is allowed to "
"modify that option.")}},
{access_register,
#{value => ?T("AccessName"),
@ -1386,11 +1394,10 @@ mod_doc() ->
"store room information. The default is the storage defined "
"by the global option 'default_db', or 'mnesia' if omitted.")}},
{ram_db_type,
#{value => "mnesia",
#{value => "mnesia | sql",
desc =>
?T("Define the type of volatile (in-memory) storage where the module "
"will store room information. The only available value for this "
"module is 'mnesia'.")}},
"will store room information ('muc_online_room' and 'muc_online_users').")}},
{hibernation_timeout,
#{value => "infinity | Seconds",
desc =>
@ -1487,7 +1494,8 @@ mod_doc() ->
?T("This option defines after how many users in the room, "
"it is considered overcrowded. When a MUC room is considered "
"overcrowed, presence broadcasts are limited to reduce load, "
"traffic and excessive presence \"storm\" received by participants.")}},
"traffic and excessive presence \"storm\" received by participants. "
"The default value is '1000'.")}},
{min_message_interval,
#{value => ?T("Number"),
desc =>
@ -1518,7 +1526,7 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{regexp_room_id,
#{value => "string()",
desc =>
@ -1635,7 +1643,7 @@ mod_doc() ->
{logging,
#{value => "true | false",
desc =>
?T("The public messages are logged using 'mod_muc_log'. "
?T("The public messages are logged using _`mod_muc_log`_. "
"The default value is 'false'.")}},
{members_by_default,
#{value => "true | false",

View File

@ -57,7 +57,7 @@
%%----------------------------
start(Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
@ -696,15 +696,26 @@ create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
lists:keysort(1, DefRoomOpts)),
case mod_muc:create_room(Host, Name, RoomOpts) of
ok ->
ok;
maybe_store_room(ServerHost, Host, Name, RoomOpts);
{error, _} ->
throw({error, "Unable to start room"})
end;
invalid_service ->
throw({error, "Invalid 'service'"});
_ ->
throw({error, "Room already exists"})
end
end.
maybe_store_room(ServerHost, Host, Name, RoomOpts) ->
case proplists:get_bool(persistent, RoomOpts) of
true ->
{atomic, ok} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
ok;
false ->
ok
end.
%% Create the room only in the database.
%% It is required to restart the MUC service for the room to appear.
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
@ -1155,6 +1166,7 @@ change_option(Option, Value, Config) ->
anonymous -> Config#config{anonymous = Value};
captcha_protected -> Config#config{captcha_protected = Value};
description -> Config#config{description = Value};
lang -> Config#config{lang = Value};
logging -> Config#config{logging = Value};
mam -> Config#config{mam = Value};
max_users -> Config#config{max_users = Value};
@ -1167,8 +1179,10 @@ change_option(Option, Value, Config) ->
presence_broadcast -> Config#config{presence_broadcast = Value};
public -> Config#config{public = Value};
public_list -> Config#config{public_list = Value};
pubsub -> Config#config{pubsub = Value};
title -> Config#config{title = Value};
vcard -> Config#config{vcard = Value};
vcard_xupdate -> Config#config{vcard_xupdate = Value};
voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
end.
@ -1399,4 +1413,4 @@ mod_doc() ->
[?T("This module provides commands to administer local MUC "
"services and their MUC rooms. It also provides simple "
"WebAdmin pages to view the existing rooms."), "",
?T("This module depends on 'mod_muc'.")]}.
?T("This module depends on _`mod_muc`_.")]}.

View File

@ -1021,7 +1021,7 @@ mod_doc() ->
?T("- URLs on messages and subjects are converted to hyperlinks."), "",
?T("- Timezone used on timestamps is shown on the log files."), "",
?T("- A custom link can be added on top of each page."), "",
?T("The module depends on 'mod_muc'.")],
?T("The module depends on _`mod_muc`_.")],
opts =>
[{access_log,
#{value => ?T("AccessName"),

View File

@ -641,7 +641,7 @@ handle_event({service_message, Msg}, _StateName,
MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
send_wrapped_multiple(
StateData#state.jid,
get_users_and_subscribers(StateData),
get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
MessagePkt,
?NS_MUCSUB_NODES_MESSAGES,
StateData),
@ -705,7 +705,7 @@ handle_sync_event({change_state, NewStateData}, _From,
true ->
ok;
_ ->
erlang:put(muc_subscribers, NewStateData#state.subscribers)
erlang:put(muc_subscribers, NewStateData#state.muc_subscribers#muc_subscribers.subscribers)
end,
{reply, {ok, NewStateData}, StateName, NewStateData};
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
@ -717,8 +717,10 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
{reply, {ok, NSD}, StateName, NSD}
end;
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
JIDs = lists:map(fun jid:make/1,
maps:keys(StateData#state.subscribers)),
JIDs = muc_subscribers_fold(
fun(_LBareJID, #subscriber{jid = JID}, Acc) ->
[JID | Acc]
end, [], StateData#state.muc_subscribers),
{reply, {ok, JIDs}, StateName, StateData};
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
StateName, StateData) ->
@ -762,7 +764,8 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName,
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({is_subscribed, From}, _From, StateName, StateData) ->
IsSubs = try maps:get(jid:split(From), StateData#state.subscribers) of
IsSubs = try muc_subscribers_get(
jid:split(From), StateData#state.muc_subscribers) of
#subscriber{nick = Nick, nodes = Nodes} -> {true, Nick, Nodes}
catch _:{badkey, _} -> false
end,
@ -899,7 +902,8 @@ terminate(Reason, _StateName,
_ -> ok
end,
tab_remove_online_user(JID, StateData)
end, [], get_users_and_subscribers(StateData)),
end, [], get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
disable_hibernate_timer(StateData),
case StateData#state.hibernate_timer of
@ -991,7 +995,7 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
end,
send_wrapped_multiple(
jid:replace_resource(StateData#state.jid, FromNick),
get_users_and_subscribers(StateData),
get_users_and_subscribers_with_node(Node, StateData),
NewPacket, Node, NewStateData1),
NewStateData2 = case has_body_or_subject(NewPacket) of
true ->
@ -1197,8 +1201,8 @@ get_participant_data(From, StateData) ->
#user{nick = FromNick, role = Role} ->
{FromNick, Role}
catch _:{badkey, _} ->
try maps:get(jid:tolower(jid:remove_resource(From)),
StateData#state.subscribers) of
try muc_subscribers_get(jid:tolower(jid:remove_resource(From)),
StateData#state.muc_subscribers) of
#subscriber{nick = FromNick} ->
{FromNick, none}
catch _:{badkey, _} ->
@ -1329,7 +1333,7 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
andalso maps:size(StateData1#state.users) == 0
andalso maps:size(StateData1#state.subscribers) == 0 of
andalso muc_subscribers_size(StateData1#state.muc_subscribers) == 0 of
true ->
?INFO_MSG("Destroyed MUC room ~ts because it's temporary "
"and empty",
@ -1342,6 +1346,17 @@ close_room_if_temporary_and_empty(StateData1) ->
-spec get_users_and_subscribers(state()) -> users().
get_users_and_subscribers(StateData) ->
get_users_and_subscribers_aux(
StateData#state.muc_subscribers#muc_subscribers.subscribers,
StateData).
-spec get_users_and_subscribers_with_node(binary(), state()) -> users().
get_users_and_subscribers_with_node(Node, StateData) ->
get_users_and_subscribers_aux(
muc_subscribers_get_by_node(Node, StateData#state.muc_subscribers),
StateData).
get_users_and_subscribers_aux(Subscribers, StateData) ->
OnlineSubscribers = maps:fold(
fun(LJID, _, Acc) ->
LBareJID = jid:remove_resource(LJID),
@ -1365,7 +1380,7 @@ get_users_and_subscribers(StateData) ->
true ->
Acc
end
end, StateData#state.users, StateData#state.subscribers).
end, StateData#state.users, Subscribers).
-spec is_user_online(jid(), state()) -> boolean().
is_user_online(JID, StateData) ->
@ -1375,7 +1390,7 @@ is_user_online(JID, StateData) ->
-spec is_subscriber(jid(), state()) -> boolean().
is_subscriber(JID, StateData) ->
LJID = jid:tolower(jid:remove_resource(JID)),
maps:is_key(LJID, StateData#state.subscribers).
muc_subscribers_is_key(LJID, StateData#state.muc_subscribers).
%% Check if the user is occupant of the room, or at least is an admin or owner.
-spec is_occupant_or_admin(jid(), state()) -> boolean().
@ -1869,16 +1884,15 @@ set_subscriber(JID, Nick, Nodes,
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = jid:remove_resource(JID),
LBareJID = jid:tolower(BareJID),
Subscribers = maps:put(LBareJID,
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
StateData#state.subscribers),
Nicks = maps:put(Nick, [LBareJID], StateData#state.subscriber_nicks),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
MUCSubscribers =
muc_subscribers_put(
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
StateData#state.muc_subscribers),
NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
store_room(NewStateData, [{add_subscription, BareJID, Nick, Nodes}]),
case not maps:is_key(LBareJID, StateData#state.subscribers) of
case not muc_subscribers_is_key(LBareJID, StateData#state.muc_subscribers) of
true ->
send_subscriptions_change_notifications(BareJID, Nick, subscribe, NewStateData),
ejabberd_hooks:run(muc_subscribed, ServerHost, [ServerHost, Room, Host, BareJID]);
@ -1956,7 +1970,8 @@ add_user_presence_un(JID, Presence, StateData) ->
-spec find_jids_by_nick(binary(), state()) -> [jid()].
find_jids_by_nick(Nick, StateData) ->
Users = case maps:get(Nick, StateData#state.nicks, []) of
[] -> maps:get(Nick, StateData#state.subscriber_nicks, []);
[] -> muc_subscribers_get_by_nick(
Nick, StateData#state.muc_subscribers);
Us -> Us
end,
[jid:make(LJID) || LJID <- Users].
@ -2020,10 +2035,10 @@ is_nick_change(JID, Nick, StateData) ->
nick_collision(User, Nick, StateData) ->
UserOfNick = case find_jid_by_nick(Nick, StateData) of
false ->
try maps:get(Nick, StateData#state.subscriber_nicks) of
[J] -> J
catch _:{badkey, _} -> false
end;
case muc_subscribers_get_by_nick(Nick, StateData#state.muc_subscribers) of
[J] -> J;
[] -> false
end;
J -> J
end,
(UserOfNick /= false andalso
@ -2433,6 +2448,11 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
false -> {none, #presence{type = unavailable}}
end,
Affiliation = get_affiliation(LJID, StateData),
Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
false -> ?NS_MUCSUB_NODES_PRESENCE
end,
Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
UserMap =
case is_room_overcrowded(StateData) orelse
(not (presence_broadcast_allowed(NJID, StateData) orelse
@ -2440,7 +2460,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
true ->
#{LNJID => UserInfo};
false ->
get_users_and_subscribers(StateData)
%% TODO: optimize further
UM1 = get_users_and_subscribers_with_node(Node1, StateData),
UM2 = get_users_and_subscribers_with_node(Node2, StateData),
maps:merge(UM1, UM2)
end,
maps:fold(
fun(LUJID, Info, _) ->
@ -2465,10 +2488,6 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
Packet = xmpp:set_subtag(
Pres, #muc_user{items = [Item],
status_codes = StatusCodes}),
Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
false -> ?NS_MUCSUB_NODES_PRESENCE
end,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
Type = xmpp:get_type(Packet),
@ -2476,7 +2495,6 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) and
(IsInitialPresence or (Type == unavailable)) ->
Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node2, StateData);
true ->
@ -2607,11 +2625,13 @@ send_nick_changing(JID, OldNick, StateData,
end;
(_, _, _) ->
ok
end, ok, get_users_and_subscribers(StateData)).
end, ok, get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_PRESENCE, StateData)).
-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
%% TODO: there should be a better way to check IsOccupant
Users = get_users_and_subscribers(StateData),
IsOccupant = case LJID of
{LUser, LServer, <<"">>} ->
@ -2637,7 +2657,8 @@ send_affiliation(JID, Affiliation, StateData) ->
role = none},
Message = #message{id = p1_rand:get_string(),
sub_els = [#muc_user{items = [Item]}]},
Users = get_users_and_subscribers(StateData),
Users = get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
Recipients = case (StateData#state.config)#config.anonymous of
true ->
maps:filter(fun(_, #user{role = moderator}) ->
@ -3271,6 +3292,13 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
#user{jid = RealJID, nick = Nick} = maps:get(jid:tolower(UJID), StateData#state.users),
ActorNick = get_actor_nick(MJID, StateData),
%% TODO: optimize further
UserMap =
maps:merge(
get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
maps:fold(
fun(LJID, Info, _) ->
IsSelfPresence = jid:tolower(UJID) == LJID,
@ -3304,7 +3332,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
true ->
ok
end
end, ok, get_users_and_subscribers(StateData)).
end, ok, UserMap).
-spec get_actor_nick(undefined | jid(), state()) -> binary().
get_actor_nick(undefined, _StateData) ->
@ -3720,7 +3748,8 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
id = p1_rand:get_string(),
sub_els = [#muc_user{status_codes = Codes}]},
send_wrapped_multiple(StateData#state.jid,
get_users_and_subscribers(StateData),
get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_CONFIG, StateData),
Message,
?NS_MUCSUB_NODES_CONFIG,
StateData);
@ -3872,26 +3901,23 @@ set_opts([{Opt, Val} | Opts], StateData) ->
StateData#state{config =
(StateData#state.config)#config{lang = Val}};
subscribers ->
{Subscribers, Nicks} =
lists:foldl(
fun({JID, Nick, Nodes}, {SubAcc, NickAcc}) ->
BareJID = case JID of
#jid{} -> jid:remove_resource(JID);
_ ->
?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
jid:remove_resource(jid:make(JID))
end,
LBareJID = jid:tolower(BareJID),
{maps:put(
LBareJID,
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
SubAcc),
maps:put(Nick, [LBareJID], NickAcc)}
end, {#{}, #{}}, Val),
StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks};
MUCSubscribers =
lists:foldl(
fun({JID, Nick, Nodes}, MUCSubs) ->
BareJID =
case JID of
#jid{} -> jid:remove_resource(JID);
_ ->
?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
jid:remove_resource(jid:make(JID))
end,
muc_subscribers_put(
#subscriber{jid = BareJID,
nick = Nick,
nodes = Nodes},
MUCSubs)
end, muc_subscribers_new(), Val),
StateData#state{muc_subscribers = MUCSubscribers};
affiliations ->
StateData#state{affiliations = maps:from_list(Val)};
subject ->
@ -3926,12 +3952,12 @@ set_vcard_xupdate(State) ->
-spec make_opts(state()) -> [{atom(), any()}].
make_opts(StateData) ->
Config = StateData#state.config,
Subscribers = maps:fold(
Subscribers = muc_subscribers_fold(
fun(_LJID, Sub, Acc) ->
[{Sub#subscriber.jid,
Sub#subscriber.nick,
Sub#subscriber.nodes}|Acc]
end, [], StateData#state.subscribers),
end, [], StateData#state.muc_subscribers),
[?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description),
?MAKE_CONFIG_OPT(#config.allow_change_subj),
?MAKE_CONFIG_OPT(#config.allow_query_users),
@ -4013,7 +4039,8 @@ destroy_room(DEl, StateData) ->
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet,
?NS_MUCSUB_NODES_CONFIG, StateData)
end, ok, get_users_and_subscribers(StateData)),
end, ok, get_users_and_subscribers_with_node(
?NS_MUCSUB_NODES_CONFIG, StateData)),
forget_room(StateData),
{result, undefined, stop}.
@ -4248,30 +4275,35 @@ process_iq_mucsub(From,
sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
try maps:get(LBareJID, StateData#state.subscribers) of
try muc_subscribers_get(LBareJID, StateData#state.muc_subscribers) of
#subscriber{nick = Nick1} when Nick1 /= Nick ->
Nodes = get_subscription_nodes(Packet),
case {nick_collision(From, Nick, StateData),
mod_muc:can_use_nick(StateData#state.server_host,
StateData#state.host,
From, Nick)} of
{true, _} ->
case nick_collision(From, Nick, StateData) of
true ->
ErrText = ?T("That nickname is already in use by another occupant"),
{error, xmpp:err_conflict(ErrText, Lang)};
{_, false} ->
Err = case Nick of
<<>> ->
xmpp:err_jid_malformed(?T("Nickname can't be empty"),
Lang);
_ ->
xmpp:err_conflict(?T("That nickname is registered"
" by another person"), Lang)
end,
{error, Err};
_ ->
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
{result, subscribe_result(Packet), NewStateData}
end;
false ->
case mod_muc:can_use_nick(StateData#state.server_host,
StateData#state.host,
From, Nick) of
false ->
Err = case Nick of
<<>> ->
xmpp:err_jid_malformed(
?T("Nickname can't be empty"),
Lang);
_ ->
xmpp:err_conflict(
?T("That nickname is registered"
" by another person"), Lang)
end,
{error, Err};
true ->
NewStateData =
set_subscriber(From, Nick, Nodes, StateData),
{result, subscribe_result(Packet), NewStateData}
end
end;
#subscriber{} ->
Nodes = get_subscription_nodes(Packet),
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
@ -4298,12 +4330,9 @@ process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = jid:remove_resource(From),
LBareJID = jid:tolower(BareJID),
try maps:get(LBareJID, StateData#state.subscribers) of
#subscriber{nick = Nick} ->
Nicks = maps:remove(Nick, StateData#state.subscriber_nicks),
Subscribers = maps:remove(LBareJID, StateData#state.subscribers),
NewStateData = StateData#state{subscribers = Subscribers,
subscriber_nicks = Nicks},
try muc_subscribers_remove_exn(LBareJID, StateData#state.muc_subscribers) of
{MUCSubscribers, #subscriber{nick = Nick}} ->
NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
store_room(NewStateData, [{del_subscription, LBareJID}]),
send_subscriptions_change_notifications(BareJID, Nick, unsubscribe, StateData),
ejabberd_hooks:run(muc_unsubscribed, ServerHost, [ServerHost, Room, Host, BareJID]),
@ -4326,7 +4355,7 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
true ->
ShowJid = IsModerator orelse
(StateData#state.config)#config.anonymous == false,
Subs = maps:fold(
Subs = muc_subscribers_fold(
fun(_, #subscriber{jid = J, nick = N, nodes = Nodes}, Acc) ->
case ShowJid of
true ->
@ -4334,7 +4363,7 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
_ ->
[#muc_subscription{nick = N, events = Nodes}|Acc]
end
end, [], StateData#state.subscribers),
end, [], StateData#state.muc_subscribers),
{result, #muc_subscriptions{list = Subs}, StateData};
_ ->
Txt = ?T("Moderator privileges required"),
@ -4347,8 +4376,7 @@ process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
-spec remove_subscriptions(state()) -> state().
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
StateData#state{subscribers = #{},
subscriber_nicks = #{}};
StateData#state{muc_subscribers = muc_subscribers_new()};
true ->
StateData
end.
@ -4597,7 +4625,7 @@ store_room(StateData, ChangesHints) ->
true ->
ok;
_ ->
erlang:put(muc_subscribers, StateData#state.subscribers)
erlang:put(muc_subscribers, StateData#state.muc_subscribers#muc_subscribers.subscribers)
end,
ShouldStore = case (StateData#state.config)#config.persistent of
true ->
@ -4611,7 +4639,15 @@ store_room(StateData, ChangesHints) ->
end
end,
if ShouldStore ->
store_room_no_checks(StateData, ChangesHints);
case erlang:function_exported(Mod, store_changes, 4) of
true when ChangesHints /= [] ->
mod_muc:store_changes(
StateData#state.server_host,
StateData#state.host, StateData#state.room,
ChangesHints);
_ ->
store_room_no_checks(StateData, ChangesHints)
end;
true ->
ok
end.
@ -4624,37 +4660,52 @@ store_room_no_checks(StateData, ChangesHints) ->
-spec send_subscriptions_change_notifications(jid(), binary(), subscribe|unsubscribe, state()) -> ok.
send_subscriptions_change_notifications(From, Nick, Type, State) ->
maps:fold(fun(_, #subscriber{nodes = Nodes, jid = JID}, _) ->
case lists:member(?NS_MUCSUB_NODES_SUBSCRIBERS, Nodes) of
true ->
ShowJid = case (State#state.config)#config.anonymous == false orelse
get_role(JID, State) == moderator orelse
get_default_role(get_affiliation(JID, State), State) == moderator of
true -> true;
_ -> false
end,
Payload = case {Type, ShowJid} of
{subscribe, true} ->
#muc_subscribe{jid = From, nick = Nick};
{subscribe, _} ->
#muc_subscribe{nick = Nick};
{unsubscribe, true} ->
#muc_unsubscribe{jid = From, nick = Nick};
{unsubscribe, _} ->
#muc_unsubscribe{nick = Nick}
end,
Packet = #message{
sub_els = [#ps_event{
items = #ps_items{
node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
items = [#ps_item{
id = p1_rand:get_string(),
sub_els = [Payload]}]}}]},
ejabberd_router:route(xmpp:set_from_to(Packet, State#state.jid, JID));
false ->
ok
end
end, ok, State#state.subscribers).
{WJ, WN} =
maps:fold(
fun(_, #subscriber{jid = JID}, {WithJid, WithNick}) ->
case (State#state.config)#config.anonymous == false orelse
get_role(JID, State) == moderator orelse
get_default_role(get_affiliation(JID, State), State) == moderator of
true ->
{[JID | WithJid], WithNick};
_ ->
{WithJid, [JID | WithNick]}
end
end, {[], []},
muc_subscribers_get_by_node(?NS_MUCSUB_NODES_SUBSCRIBERS,
State#state.muc_subscribers)),
if WJ /= [] ->
Payload1 = case Type of
subscribe -> #muc_subscribe{jid = From, nick = Nick};
_ -> #muc_unsubscribe{jid = From, nick = Nick}
end,
Packet1 = #message{
sub_els = [#ps_event{
items = #ps_items{
node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
items = [#ps_item{
id = p1_rand:get_string(),
sub_els = [Payload1]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
WJ, Packet1, true);
true -> ok
end,
if WN /= [] ->
Payload2 = case Type of
subscribe -> #muc_subscribe{nick = Nick};
_ -> #muc_unsubscribe{nick = Nick}
end,
Packet2 = #message{
sub_els = [#ps_event{
items = #ps_items{
node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
items = [#ps_item{
id = p1_rand:get_string(),
sub_els = [Payload2]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
WN, Packet2, true);
true -> ok
end.
-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
send_wrapped(From, To, Packet, Node, State) ->
@ -4666,7 +4717,7 @@ send_wrapped(From, To, Packet, Node, State) ->
_ -> false
end,
if IsOffline ->
try maps:get(LBareTo, State#state.subscribers) of
try muc_subscribers_get(LBareTo, State#state.muc_subscribers) of
#subscriber{nodes = Nodes, jid = JID} ->
case lists:member(Node, Nodes) of
true ->
@ -4713,7 +4764,7 @@ send_wrapped(From, To, Packet, Node, State) ->
ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
end.
-spec wrap(jid(), jid(), stanza(), binary(), binary()) -> message().
-spec wrap(jid(), undefined | jid(), stanza(), binary(), binary()) -> message().
wrap(From, To, Packet, Node, Id) ->
El = xmpp:set_from_to(Packet, From, To),
#message{
@ -4727,10 +4778,155 @@ wrap(From, To, Packet, Node, Id) ->
-spec send_wrapped_multiple(jid(), users(), stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
{Dir, Wra} =
maps:fold(
fun(_, #user{jid = To}, _) ->
send_wrapped(From, To, Packet, Node, State)
end, ok, Users).
fun(_, #user{jid = To, last_presence = LP}, {Direct, Wrapped} = Res) ->
IsOffline = LP == undefined,
if IsOffline ->
LBareTo = jid:tolower(jid:remove_resource(To)),
case muc_subscribers_find(LBareTo, State#state.muc_subscribers) of
{ok, #subscriber{nodes = Nodes}} ->
case lists:member(Node, Nodes) of
true ->
{Direct, [To | Wrapped]};
_ ->
%% TODO: check that this branch is never called
Res
end;
_ ->
Res
end;
true ->
{[To | Direct], Wrapped}
end
end, {[],[]}, Users),
case Dir of
[] -> ok;
_ ->
case Packet of
#presence{type = unavailable} ->
case xmpp:get_subtag(Packet, #muc_user{}) of
#muc_user{destroy = Destroy,
status_codes = Codes} ->
case Destroy /= undefined orelse
(lists:member(110,Codes) andalso
not lists:member(303, Codes)) of
true ->
ejabberd_router_multicast:route_multicast(
From, State#state.server_host, Dir,
#presence{id = p1_rand:get_string(),
type = unavailable}, false);
false ->
ok
end;
_ ->
false
end;
_ ->
ok
end,
ejabberd_router_multicast:route_multicast(From, State#state.server_host,
Dir, Packet, false)
end,
case Wra of
[] -> ok;
_ ->
MamEnabled = (State#state.config)#config.mam,
Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of
#stanza_id{id = Id2} ->
Id2;
_ ->
p1_rand:get_string()
end,
NewPacket = wrap(From, undefined, Packet, Node, Id),
NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
Wra, NewPacket2, true)
end.
%%%----------------------------------------------------------------------
%%% #muc_subscribers API
%%%----------------------------------------------------------------------
-spec muc_subscribers_new() -> #muc_subscribers{}.
muc_subscribers_new() ->
#muc_subscribers{}.
-spec muc_subscribers_get(ljid(), #muc_subscribers{}) -> #subscriber{}.
muc_subscribers_get({_, _, _} = LJID, MUCSubscribers) ->
maps:get(LJID, MUCSubscribers#muc_subscribers.subscribers).
-spec muc_subscribers_find(ljid(), #muc_subscribers{}) ->
{ok, #subscriber{}} | error.
muc_subscribers_find({_, _, _} = LJID, MUCSubscribers) ->
maps:find(LJID, MUCSubscribers#muc_subscribers.subscribers).
-spec muc_subscribers_is_key(ljid(), #muc_subscribers{}) -> boolean().
muc_subscribers_is_key({_, _, _} = LJID, MUCSubscribers) ->
maps:is_key(LJID, MUCSubscribers#muc_subscribers.subscribers).
-spec muc_subscribers_size(#muc_subscribers{}) -> integer().
muc_subscribers_size(MUCSubscribers) ->
maps:size(MUCSubscribers#muc_subscribers.subscribers).
-spec muc_subscribers_fold(Fun, Acc, #muc_subscribers{}) -> Acc when
Fun :: fun((ljid(), #subscriber{}, Acc) -> Acc).
muc_subscribers_fold(Fun, Init, MUCSubscribers) ->
maps:fold(Fun, Init, MUCSubscribers#muc_subscribers.subscribers).
-spec muc_subscribers_get_by_nick(binary(), #muc_subscribers{}) -> [#subscriber{}].
muc_subscribers_get_by_nick(Nick, MUCSubscribers) ->
maps:get(Nick, MUCSubscribers#muc_subscribers.subscriber_nicks, []).
-spec muc_subscribers_get_by_node(binary(), #muc_subscribers{}) -> subscribers().
muc_subscribers_get_by_node(Node, MUCSubscribers) ->
maps:get(Node, MUCSubscribers#muc_subscribers.subscriber_nodes, #{}).
-spec muc_subscribers_remove_exn(ljid(), #muc_subscribers{}) ->
{#muc_subscribers{}, #subscriber{}}.
muc_subscribers_remove_exn({_, _, _} = LJID, MUCSubscribers) ->
#muc_subscribers{subscribers = Subs,
subscriber_nicks = SubNicks,
subscriber_nodes = SubNodes} = MUCSubscribers,
Subscriber = maps:get(LJID, Subs),
#subscriber{nick = Nick, nodes = Nodes} = Subscriber,
NewSubNicks = maps:remove(Nick, SubNicks),
NewSubs = maps:remove(LJID, Subs),
NewSubNodes =
lists:foldl(
fun(Node, Acc) ->
NodeSubs = maps:get(Node, Acc, #{}),
NodeSubs2 = maps:remove(LJID, NodeSubs),
maps:put(Node, NodeSubs2, Acc)
end, SubNodes, Nodes),
{#muc_subscribers{subscribers = NewSubs,
subscriber_nicks = NewSubNicks,
subscriber_nodes = NewSubNodes}, Subscriber}.
-spec muc_subscribers_put(#subscriber{}, #muc_subscribers{}) ->
#muc_subscribers{}.
muc_subscribers_put(Subscriber, MUCSubscribers) ->
#subscriber{jid = JID,
nick = Nick,
nodes = Nodes} = Subscriber,
#muc_subscribers{subscribers = Subs,
subscriber_nicks = SubNicks,
subscriber_nodes = SubNodes} = MUCSubscribers,
LJID = jid:tolower(JID),
NewSubs = maps:put(LJID, Subscriber, Subs),
NewSubNicks = maps:put(Nick, [LJID], SubNicks),
NewSubNodes =
lists:foldl(
fun(Node, Acc) ->
NodeSubs = maps:get(Node, Acc, #{}),
NodeSubs2 = maps:put(LJID, Subscriber, NodeSubs),
maps:put(Node, NodeSubs2, Acc)
end, SubNodes, Nodes),
#muc_subscribers{subscribers = NewSubs,
subscriber_nicks = NewSubNicks,
subscriber_nodes = NewSubNodes}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaningful content

View File

@ -29,7 +29,8 @@
-behaviour(mod_muc_room).
%% API
-export([init/2, store_room/5, restore_room/3, forget_room/3,
-export([init/2, store_room/5, store_changes/4,
restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4,
import/3, export/1]).
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
@ -83,6 +84,12 @@ store_room(LServer, Host, Name, Opts, ChangesHints) ->
end,
ejabberd_sql:sql_transaction(LServer, F).
store_changes(LServer, Host, Name, Changes) ->
F = fun () ->
[change_room(Host, Name, Change) || Change <- Changes]
end,
ejabberd_sql:sql_transaction(LServer, F).
change_room(Host, Room, {add_subscription, JID, Nick, Nodes}) ->
SJID = jid:encode(JID),
SNodes = misc:term_to_expr(Nodes),
@ -185,13 +192,20 @@ get_rooms(LServer, Host) ->
{selected, Subs} ->
SubsD = lists:foldl(
fun({Room, Jid, Nick, Nodes}, Dict) ->
dict:append(Room, {jid:decode(Jid),
Nick, ejabberd_sql:decode_term(Nodes)}, Dict)
end, dict:new(), Subs),
Sub = {jid:decode(Jid),
Nick, ejabberd_sql:decode_term(Nodes)},
maps:update_with(
Room,
fun(SubAcc) ->
[Sub | SubAcc]
end,
[Sub],
Dict)
end, maps:new(), Subs),
lists:map(
fun({Room, Opts}) ->
OptsD = ejabberd_sql:decode_term(Opts),
OptsD2 = case {dict:find(Room, SubsD), lists:keymember(subscribers, 1, OptsD)} of
OptsD2 = case {maps:find(Room, SubsD), lists:keymember(subscribers, 1, OptsD)} of
{_, true} ->
store_room(LServer, Host, Room, mod_muc:opts_to_binary(OptsD), undefined),
OptsD;

View File

@ -392,8 +392,9 @@ perform(From, Packet, _,
{route_single, Group}) ->
lists:foreach(
fun(ToUser) ->
Group_others = strip_other_bcc(ToUser, Group#group.others),
route_packet(From, ToUser, Packet,
Group#group.others, Group#group.addresses)
Group_others, Group#group.addresses)
end, Group#group.dests);
perform(From, Packet, _,
{{route_multicast, JID, RLimits}, Group}) ->
@ -424,6 +425,21 @@ strip_addresses_element(Packet) ->
throw(eadsele)
end.
%%%-------------------------
%%% Strip third-party bcc 'addresses'
%%%-------------------------
strip_other_bcc(#dest{jid_jid = ToUserJid}, Group_others) ->
lists:filter(
fun(#address{jid = JID, type = Type}) ->
case {JID, Type} of
{ToUserJid, bcc} -> true;
{_, bcc} -> false;
_ -> true
end
end,
Group_others).
%%%-------------------------
%%% Split Addresses
%%%-------------------------
@ -545,7 +561,6 @@ build_other_xml(Dests) ->
case Dest#dest.type of
to -> [add_delivered(XML) | R];
cc -> [add_delivered(XML) | R];
bcc -> R;
_ -> [XML | R]
end
end,

View File

@ -572,6 +572,16 @@ check_event(#message{from = From, to = To, id = ID, type = Type} = Msg) ->
sub_els = [#xevent{id = ID, offline = true}]},
ejabberd_router:route(NewMsg),
true;
% Don't store composing events
#xevent{id = V, composing = true} when V /= undefined ->
false;
% Nor composing stopped events
#xevent{id = V, composing = false, delivered = false,
displayed = false, offline = false} when V /= undefined ->
false;
% But store other received notifications
#xevent{id = V} when V /= undefined ->
true;
_ ->
false
end.
@ -1315,19 +1325,19 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
[{?T("This example allows power users to have as much as 5000 "
"offline messages, administrators up to 2000, and all the "

View File

@ -154,7 +154,7 @@ handle_info({iq_reply, timeout, JID}, State) ->
{noreply, State#state{timers = Timers}};
handle_info({timeout, _TRef, {ping, JID}}, State) ->
Host = State#state.host,
From = jid:remove_resource(JID),
From = jid:make(Host),
IQ = #iq{from = From, to = JID, type = get, sub_els = [#ping{}]},
ejabberd_router:route_iq(IQ, JID,
gen_mod:get_module_proc(Host, ?MODULE),
@ -300,7 +300,7 @@ mod_doc() ->
desc =>
?T("How long to wait before deeming that a client "
"has not answered a given server ping request. "
"The default value is '32' seconds.")}},
"The default value is 'undefined'.")}},
{send_pings,
#{value => "true | false",
desc =>
@ -317,8 +317,8 @@ mod_doc() ->
"server ping request in less than period defined "
"in 'ping_ack_timeout' option: "
"'kill' means destroying the underlying connection, "
"'none' means to do nothing. NOTE: when 'mod_stream_mgmt' "
"module is loaded and stream management is enabled by "
"'none' means to do nothing. NOTE: when _`mod_stream_mgmt`_ "
"is loaded and stream management is enabled by "
"a client, killing the client connection doesn't mean "
"killing the client session - the session will be kept "
"alive in order to give the client a chance to resume it. "

View File

@ -887,25 +887,25 @@ mod_doc() ->
"https://xmpp.org/extensions/xep-0191.html"
"[XEP-0191: Blocking Command] which is implemented by "
"'mod_blocking' module. However, you still need "
"'mod_privacy' loaded in order for 'mod_blocking' to work.")],
"'mod_privacy' loaded in order for _`mod_blocking`_ to work.")],
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.

View File

@ -66,7 +66,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, ?MODULE, process_sm_iq),
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
@ -128,23 +128,23 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
jid(), jid(), binary(), binary()) ->

View File

@ -106,7 +106,7 @@ mod_doc() ->
?T("WARNING: Security issue: Privileged access gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust a component."), "",
?T("NOTE: This module is complementary to 'mod_delegation', "
?T("NOTE: This module is complementary to _`mod_delegation`_, "
"but can also be used separately.")],
opts =>
[{roster,

View File

@ -45,6 +45,7 @@
-include("mod_roster.hrl").
-include("translate.hrl").
-include("ejabberd_stacktrace.hrl").
-include("ejabberd_commands.hrl").
-define(STDTREE, <<"tree">>).
-define(STDNODE, <<"flat">>).
@ -93,6 +94,9 @@
handle_call/3, handle_cast/2, handle_info/2, mod_doc/0,
terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
%% ejabberd commands
-export([get_commands_spec/0, delete_old_items/1]).
-export([route/1]).
%%====================================================================
@ -210,7 +214,7 @@
pep_mapping :: [{binary(), binary()}],
ignore_pep_from_offline :: boolean(),
last_item_cache :: boolean(),
max_items_node :: non_neg_integer(),
max_items_node :: non_neg_integer()|unlimited,
max_subscriptions_node :: non_neg_integer()|undefined,
default_node_config :: [{atom(), binary()|boolean()|integer()|atom()}],
nodetree :: binary(),
@ -337,6 +341,7 @@ init([ServerHost|_]) ->
false ->
ok
end,
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins),
PepMapping = config(ServerHost, pep_mapping),
@ -806,7 +811,13 @@ terminate(_Reason,
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
ejabberd_router:unregister_route(Host)
end, Hosts).
end, Hosts),
case gen_mod:is_loaded_elsewhere(ServerHost, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@ -3399,14 +3410,14 @@ node_config(_, _, []) ->
%% @doc <p>Return the maximum number of items for a given node.</p>
%% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p>
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer() | unlimited.
max_items(Host, Options) ->
case get_option(Options, persist_items) of
true ->
case get_option(Options, max_items) of
I when is_integer(I), I < 0 -> 0;
I when is_integer(I) -> I;
_ -> ?MAXITEMS
_ -> get_max_items_node(Host)
end;
false ->
case get_option(Options, send_last_published_item) of
@ -3548,14 +3559,19 @@ decode_get_pending(#xdata{fields = Fs}, Lang) ->
{error, xmpp:err_resource_constraint(Txt, Lang)}
end.
-spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean().
-spec check_opt_range(atom(), [proplists:property()],
non_neg_integer() | unlimited | undefined) -> boolean().
check_opt_range(_Opt, _Opts, undefined) ->
true;
check_opt_range(_Opt, _Opts, unlimited) ->
true;
check_opt_range(Opt, Opts, Max) ->
Val = proplists:get_value(Opt, Opts, Max),
Val =< Max.
case proplists:get_value(Opt, Opts, Max) of
max -> true;
Val -> Val =< Max
end.
-spec get_max_items_node(host()) -> undefined | non_neg_integer().
-spec get_max_items_node(host()) -> undefined | unlimited | non_neg_integer().
get_max_items_node(Host) ->
config(Host, max_items_node, undefined).
@ -3707,6 +3723,7 @@ features() ->
<<"access-whitelist">>, % OPTIONAL
<<"collections">>, % RECOMMENDED
<<"config-node">>, % RECOMMENDED
<<"config-node-max">>,
<<"create-and-configure">>, % RECOMMENDED
<<"item-ids">>, % RECOMMENDED
<<"last-published">>, % RECOMMENDED
@ -4136,6 +4153,46 @@ purge_offline(Host, LJID, Node) ->
{error, xmpp:err_internal_server_error(Txt, Lang)}
end.
-spec delete_old_items(non_neg_integer()) -> ok | error.
delete_old_items(N) ->
Results = lists:flatmap(
fun(Host) ->
case tree_action(Host, get_all_nodes, [Host]) of
Nodes when is_list(Nodes) ->
lists:map(
fun(#pubsub_node{id = Nidx, type = Type}) ->
case node_action(Host, Type,
remove_extra_items,
[Nidx , N]) of
{result, _} ->
ok;
{error, _} ->
error
end
end, Nodes);
_ ->
error
end
end, ejabberd_option:hosts()),
case lists:member(error, Results) of
true ->
error;
false ->
ok
end.
-spec get_commands_spec() -> [ejabberd_commands()].
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_pubsub_items, tags = [purge],
desc = "Keep only NUMBER of PubSub items per node",
module = ?MODULE, function = delete_old_items,
args_desc = ["Number of items to keep per node"],
args = [{number, integer}],
result = {res, rescode},
result_desc = "0 if command failed, 1 when succeeded",
args_example = [1000],
result_example = ok}].
-spec mod_opt_type(atom()) -> econf:validator().
mod_opt_type(access_createnode) ->
econf:acl();
@ -4146,7 +4203,7 @@ mod_opt_type(ignore_pep_from_offline) ->
mod_opt_type(last_item_cache) ->
econf:bool();
mod_opt_type(max_items_node) ->
econf:non_neg_int();
econf:non_neg_int(unlimited);
mod_opt_type(max_nodes_discoitems) ->
econf:non_neg_int(infinity);
mod_opt_type(max_subscriptions_node) ->
@ -4212,7 +4269,7 @@ mod_doc() ->
"(https://xmpp.org/extensions/xep-0163.html"
"[XEP-0163: Personal Eventing via Pubsub]) "
"is enabled in the default ejabberd configuration file, "
"and it requires 'mod_caps'.")],
"and it requires _`mod_caps`_.")],
opts =>
[{access_createnode,
#{value => "AccessName",
@ -4225,7 +4282,7 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to "
?T("Same as top-level _`default_db`_ option, but applied to "
"this module only.")}},
{default_node_config,
#{value => "List of Key:Value",
@ -4273,7 +4330,7 @@ mod_doc() ->
"and allows to raise user connection rate. The cost "
"is memory usage, as every item is stored in memory.")}},
{max_items_node,
#{value => "MaxItems",
#{value => "non_neg_integer() | infinity",
desc =>
?T("Define the maximum number of items that can be "
"stored in a node. Default value is: '10'.")}},

View File

@ -98,7 +98,7 @@ start(Host, Opts) ->
init_cache(Mod, Host, Opts),
register_iq_handlers(Host),
register_hooks(Host),
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
-spec stop(binary()) -> ok.
stop(Host) ->
@ -191,23 +191,23 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
%%--------------------------------------------------------------------
%% ejabberd command callback.
@ -405,7 +405,7 @@ c2s_stanza(State, #stream_error{}, _SendResult) ->
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
Pkt, _SendResult) ->
?DEBUG("Notifying client of stanza", []),
notify(State, unwrap_message(Pkt), get_direction(Pkt)),
notify(State, Pkt, get_direction(Pkt)),
State;
c2s_stanza(State, _Pkt, _SendResult) ->
State.
@ -454,7 +454,7 @@ c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
{Pkt, Dir} = case mod_stream_mgmt:queue_find(
fun is_incoming_chat_msg/1, Queue) of
none -> {none, undefined};
Pkt0 -> {unwrap_message(Pkt0), get_direction(Pkt0)}
Pkt0 -> {Pkt0, get_direction(Pkt0)}
end,
notify(State, Pkt, Dir),
State;
@ -536,7 +536,8 @@ notify(LUser, LServer, Clients, Pkt, Dir) ->
-spec notify(binary(), ljid(), binary(), xdata(),
xmpp_element() | xmlel() | none, direction(),
fun((iq() | timeout) -> any())) -> ok.
notify(LServer, PushLJID, Node, XData, Pkt, Dir, HandleResponse) ->
notify(LServer, PushLJID, Node, XData, Pkt0, Dir, HandleResponse) ->
Pkt = unwrap_message(Pkt0),
From = jid:make(LServer),
Summary = make_summary(LServer, Pkt, Dir),
Item = #ps_item{sub_els = [#push_notification{xdata = Summary}]},
@ -714,7 +715,7 @@ make_summary(Host, #message{from = From} = Pkt, recv) ->
make_summary(_Host, _Pkt, _Dir) ->
undefined.
-spec unwrap_message(stanza()) -> stanza().
-spec unwrap_message(Stanza) -> Stanza when Stanza :: stanza() | none.
unwrap_message(#message{meta = #{carbon_copy := true}} = Msg) ->
misc:unwrap_carbon(Msg);
unwrap_message(#message{type = normal} = Msg) ->

View File

@ -87,20 +87,20 @@ mod_opt_type(wake_on_timeout) ->
econf:bool().
mod_options(_Host) ->
[{resume_timeout, timer:seconds(259200)},
[{resume_timeout, timer:hours(72)},
{wake_on_start, false},
{wake_on_timeout, true}].
mod_doc() ->
#{desc =>
[?T("This module tries to keep the stream management "
"session (see 'mod_stream_mgmt') of a disconnected "
"session (see _`mod_stream_mgmt`_) of a disconnected "
"mobile client alive if the client enabled push "
"notifications for that session. However, the normal "
"session resumption timeout is restored once a push "
"notification is issued, so the session will be closed "
"if the client doesn't respond to push notifications."), "",
?T("The module depends on 'mod_push'.")],
?T("The module depends on _`mod_push`_.")],
opts =>
[{resume_timeout,
#{value => "timeout()",
@ -109,9 +109,9 @@ mod_doc() ->
"the session of a disconnected push client times out. "
"This timeout is only in effect as long as no push "
"notification is issued. Once that happened, the "
"resumption timeout configured for the 'mod_stream_mgmt' "
"module is restored. "
"The default value is '72' minutes.")}},
"resumption timeout configured for _`mod_stream_mgmt`_ "
"is restored. "
"The default value is '72' hours.")}},
{wake_on_start,
#{value => "true | false",
desc =>

View File

@ -638,9 +638,9 @@ mod_doc() ->
?T("* Register a new account on the server."), "",
?T("* Change the password from an existing account on the server."), "",
?T("* Delete an existing account on the server."), "",
?T("This module reads also another option defined globally for the "
"server: 'registration_timeout'. Please check that option "
"documentation in the section with top-level options.")],
?T("This module reads also the top-level _`registration_timeout`_ "
"option defined globally for the server, "
"so please check that option documentation too.")],
opts =>
[{access,
#{value => ?T("AccessName"),
@ -664,9 +664,8 @@ mod_doc() ->
{captcha_protected,
#{value => "true | false",
desc =>
?T("Protect registrations with CAPTCHA (see section "
"https://docs.ejabberd.im/admin/configuration/basic/#captcha[CAPTCHA] "
"of the Configuration Guide). The default is 'false'.")}},
?T("Protect registrations with http://../basic/#captcha[CAPTCHA]. "
"The default is 'false'.")}},
{ip_access,
#{value => ?T("AccessName"),
desc =>
@ -680,7 +679,7 @@ mod_doc() ->
"https://en.wikipedia.org/wiki/Entropy_(information_theory)"
"[Shannon entropy] for passwords. The value 'Entropy' is a "
"number of bits of entropy. The recommended minimum is 32 bits. "
"The default is 0, i.e. no checks are performed.")}},
"The default is '0', i.e. no checks are performed.")}},
{registration_watchers,
#{value => "[JID, ...]",
desc =>

View File

@ -23,32 +23,6 @@
%%%
%%%----------------------------------------------------------------------
%%% IDEAS:
%%%
%%% * Implement those options, already present in mod_register:
%%% + access
%%% + captcha_protected
%%% + password_strength
%%% + welcome_message
%%% + registration_timeout
%%%
%%% * Improve this module to allow each virtual host to have different
%%% options. See http://support.process-one.net/browse/EJAB-561
%%%
%%% * Check that all the text is translatable.
%%%
%%% * Add option to use a custom CSS file, or custom CSS lines.
%%%
%%% * Don't hardcode the "register" path in URL.
%%%
%%% * Allow private email during register, and store in custom table.
%%% * Optionally require private email to register.
%%% * Optionally require email confirmation to register.
%%% * Allow to set a private email address anytime.
%%% * Allow to recover password using private email to confirm (mod_passrecover)
%%% * Optionally require invitation
%%% * Optionally register request is forwarded to admin, no account created.
-module(mod_register_web).
-author('badlop@process-one.net').
@ -533,14 +507,18 @@ form_del_get(Host, Lang) ->
%% {error, not_allowed} |
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
Access = mod_register_opt:access(Host),
case jid:make(Username, Host) of
error -> {error, invalid_jid};
JID ->
case acl:match_rule(Host, Access, JID) of
deny -> {error, not_allowed};
allow -> register_account2(Username, Host, Password)
end
try mod_register_opt:access(Host) of
Access ->
case jid:make(Username, Host) of
error -> {error, invalid_jid};
JID ->
case acl:match_rule(Host, Access, JID) of
deny -> {error, not_allowed};
allow -> register_account2(Username, Host, Password)
end
end
catch _:{module_not_loaded, mod_register, _Host} ->
{error, host_unknown}
end.
register_account2(Username, Host, Password) ->
@ -603,6 +581,8 @@ get_error_text({error, password_incorrect}) ->
?T("Incorrect password");
get_error_text({error, invalid_jid}) ->
?T("The username is not valid");
get_error_text({error, host_unknown}) ->
?T("Host unknown");
get_error_text({error, not_allowed}) ->
?T("Not allowed");
get_error_text({error, account_doesnt_exist}) ->
@ -625,13 +605,27 @@ mod_doc() ->
?T("- Register a new account on the server."), "",
?T("- Change the password from an existing account on the server."), "",
?T("- Unregister an existing account on the server."), "",
?T("This module supports CAPTCHA image to register a new account. "
"To enable this feature, configure the options 'captcha\_cmd' "
"and 'captcha\_url', which are documented in the section with "
"top-level options."), "",
?T("As an example usage, the users of the host 'example.org' can "
"visit the page: 'https://example.org:5281/register/' It is "
?T("This module supports http://../basic/#captcha[CAPTCHA] "
"to register a new account. "
"To enable this feature, configure the "
"top-level _`captcha_cmd`_ and "
"top-level _`captcha_url`_ options."), "",
?T("As an example usage, the users of the host 'localhost' can "
"visit the page: 'https://localhost:5280/register/' It is "
"important to include the last / character in the URL, "
"otherwise the subpages URL will be incorrect."), "",
?T("The module depends on 'mod_register' where all the configuration "
"is performed.")]}.
?T("This module is enabled in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers], "
"no need to enable in 'modules'."),
?T("The module depends on _`mod_register`_ where all the "
"configuration is performed.")],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /register: mod_register_web",
"",
"modules:",
" mod_register: {}"]}.

View File

@ -1345,29 +1345,29 @@ mod_doc() ->
"This option does not affect the client in any way. "
"This option is only useful if option 'versioning' is "
"set to 'true'. The default value is 'false'. "
"IMPORTANT: if you use 'mod_shared_roster' or "
"'mod_shared_roster_ldap', you must set the value "
"IMPORTANT: if you use _`mod_shared_roster`_ or "
" _`mod_shared_roster_ldap`_, you must set the value "
"of the option to 'false'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
["modules:",
" ...",

View File

@ -1266,7 +1266,7 @@ mod_doc() ->
?T("- Displayed: A list of groups that will be in the "
"rosters of this group's members. A group of other vhost can "
"be identified with 'groupid@vhost'."), "",
?T("This module depends on 'mod_roster'. "
?T("This module depends on _`mod_roster`_. "
"If not enabled, roster queries will return 503 errors.")],
opts =>
[{db_type,
@ -1274,25 +1274,25 @@ mod_doc() ->
desc =>
?T("Define the type of storage where the module will create "
"the tables and store user information. The default is "
"the storage defined by the global option 'default_db', "
"the storage defined by the top-level _`default_db`_ option, "
"or 'mnesia' if omitted. If 'sql' value is defined, "
"make sure you have defined the database.")}},
{use_cache,
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
[{?T("Take the case of a computer club that wants all its members "
"seeing each other in their rosters. To achieve this, they "

View File

@ -796,7 +796,7 @@ mod_doc() ->
"disable the check. Default value is 'true'.")}}] ++
[{Opt,
#{desc =>
{?T("Same as top-level '~s' option, but "
{?T("Same as top-level _`~s`_ option, but "
"applied to this module only."), [Opt]}}}
|| Opt <- [ldap_backups, ldap_base, ldap_uids, ldap_deref_aliases,
ldap_encrypt, ldap_password, ldap_port, ldap_rootdn,

View File

@ -962,12 +962,14 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, "
"but applied to this module only. "
"The default value is '48 hours'.")}}]}.

View File

@ -176,7 +176,7 @@ mod_doc() ->
"clients. If ejabberd's built-in TURN service is used, "
"TURN relays allocated using temporary credentials will "
"be terminated shortly after the credentials expired. The "
"default value is '12' hours. Note that restarting the "
"default value is '12 hours'. Note that restarting the "
"ejabberd node invalidates any temporary credentials "
"offered before the restart unless a 'secret' is "
"specified (see below).")}},

View File

@ -640,23 +640,23 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql | ldap",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}},
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}},
{vcard,
#{value => ?T("vCard"),
desc =>

View File

@ -571,7 +571,7 @@ mod_doc() ->
}]}}] ++
[{Opt,
#{desc =>
{?T("Same as top-level '~s' option, but "
{?T("Same as top-level _`~s`_ option, but "
"applied to this module only."), [Opt]}}}
|| Opt <- [ldap_base, ldap_servers, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,

View File

@ -228,26 +228,26 @@ mod_doc() ->
"frequently their presence. However, the overhead is significantly "
"reduced by the use of caching, so you probably don't want "
"to set 'use_cache' to 'false'."), "",
?T("The module depends on 'mod_vcard'."), "",
?T("The module depends on _`mod_vcard`_."), "",
?T("NOTE: Nowadays https://xmpp.org/extensions/xep-0153.html"
"[XEP-0153] is used mostly as \"read-only\", i.e. modern "
"clients don't publish their avatars inside vCards. Thus "
"in the majority of cases the module is only used along "
"with 'mod_avatar' module for providing backward compatibility.")],
"with _`mod_avatar`_ for providing backward compatibility.")],
opts =>
[{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.

View File

@ -39,7 +39,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -375,7 +376,8 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Subscribed == true)) ->
{error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
if MaxItems > 0;
MaxItems == unlimited ->
Now = erlang:timestamp(),
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
@ -402,6 +404,16 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
end
end.
remove_extra_items(Nidx, MaxItems) ->
{result, States} = get_states(Nidx),
Records = States ++ mnesia:read({pubsub_orphan, Nidx}),
ItemIds = lists:flatmap(fun(#pubsub_state{items = Is}) ->
Is;
(#pubsub_orphan{items = Is}) ->
Is
end, Records),
remove_extra_items(Nidx, MaxItems, ItemIds).
%% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no
@ -945,15 +957,12 @@ rsm_page(Count, Index, Offset, Items) ->
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> {MS,S,US};
_ -> Stamp
try xmpp_util:decode_timestamp(Stamp)
catch _:{bad_timestamp, _} ->
Stamp % We should return a proper error to the client instead.
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(Stamp) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
xmpp_util:encode_timestamp(Stamp).
transform({pubsub_state, {Id, Nidx}, Is, A, Ss}) ->
{pubsub_state, {Id, Nidx}, Nidx, Is, A, Ss};

View File

@ -40,9 +40,10 @@
-include("translate.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
create_node_permission/6, create_node/2, delete_node/1, purge_node/2,
subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -247,7 +248,8 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Subscribed == true)) ->
{error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
if MaxItems > 0;
MaxItems == unlimited ->
Now = erlang:timestamp(),
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
@ -258,20 +260,23 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
{result, _} ->
{error, xmpp:err_forbidden()};
_ ->
Items = [ItemId | itemids(Nidx, GenKey)],
{result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
OldIds = maybe_remove_extra_items(Nidx, MaxItems,
GenKey, ItemId),
set_item(#pubsub_item{
itemid = {ItemId, Nidx},
creation = {Now, GenKey},
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, OI}}
{result, {default, broadcast, OldIds}}
end;
true ->
{result, {default, broadcast, []}}
end
end.
remove_extra_items(Nidx, MaxItems) ->
remove_extra_items(Nidx, MaxItems, itemids(Nidx)).
remove_extra_items(_Nidx, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
@ -862,6 +867,18 @@ first_in_list(Pred, [H | T]) ->
_ -> first_in_list(Pred, T)
end.
itemids(Nidx) ->
case catch
ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where "
"nodeid=%(Nidx)d order by modification desc"))
of
{selected, RItems} ->
[ItemId || {ItemId} <- RItems];
_ ->
[]
end.
itemids(Nidx, {_U, _S, _R} = JID) ->
SJID = encode_jid(JID),
SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
@ -933,6 +950,16 @@ update_subscription(Nidx, JID, Subscription) ->
"-affiliation='n'"
]).
-spec maybe_remove_extra_items(mod_pubsub:nodeIdx(),
non_neg_integer() | unlimited, ljid(),
mod_pubsub:itemId()) -> [mod_pubsub:itemId()].
maybe_remove_extra_items(_Nidx, unlimited, _GenKey, _ItemId) ->
[];
maybe_remove_extra_items(Nidx, MaxItems, GenKey, ItemId) ->
ItemIds = [ItemId | itemids(Nidx, GenKey)],
{result, {_NewIds, OldIds}} = remove_extra_items(Nidx, MaxItems, ItemIds),
OldIds.
-spec decode_jid(SJID :: binary()) -> ljid().
decode_jid(SJID) ->
jid:tolower(jid:decode(SJID)).
@ -1037,15 +1064,14 @@ rsm_page(Count, Index, Offset, Items) ->
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> encode_now({MS,S,US});
_ -> Stamp
try xmpp_util:decode_timestamp(Stamp) of
Now ->
encode_now(Now)
catch _:{bad_timestamp, _} ->
Stamp % We should return a proper error to the client instead.
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(decode_now(Stamp)) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
xmpp_util:encode_timestamp(decode_now(Stamp)).
encode_now({T1, T2, T3}) ->
<<(misc:i2l(T1, 6))/binary, ":",

View File

@ -35,7 +35,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -135,6 +136,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems) ->
node_flat:remove_extra_items(Nidx, MaxItems).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).

View File

@ -37,7 +37,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -92,6 +93,9 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).

View File

@ -46,7 +46,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@ -98,6 +99,14 @@ get_nodes(Host, Limit) ->
{Nodes, _} -> Nodes
end.
get_all_nodes({_U, _S, _R} = Owner) ->
Host = jid:tolower(jid:remove_resource(Owner)),
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'});
get_all_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'})
++ mnesia:match_object(#pubsub_node{nodeid = {{'_', Host, '_'}, '_'},
_ = '_'}).
get_parentnodes(Host, Node, _From) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) ->

View File

@ -45,7 +45,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@ -165,6 +166,34 @@ get_nodes(Host, Limit) ->
[]
end.
get_all_nodes({_U, _S, _R} = JID) ->
SubKey = jid:tolower(JID),
GenKey = jid:remove_resource(SubKey),
EncKey = node_flat_sql:encode_jid(GenKey),
Pattern = <<(node_flat_sql:encode_jid_like(GenKey))/binary, "/%">>,
case ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(EncKey)s "
"or host like %(Pattern)s %ESCAPE")) of
{selected, RItems} ->
[raw_to_node(GenKey, Item) || Item <- RItems];
_ ->
[]
end;
get_all_nodes(Host) ->
Pattern1 = <<"%@", Host/binary>>,
Pattern2 = <<"%@", Host/binary, "/%">>,
case ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(Host)s "
"or host like %(Pattern1)s "
"or host like %(Pattern2)s %ESCAPE")) of
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
[]
end.
get_parentnodes(Host, Node, _From) ->
case get_node(Host, Node) of
Record when is_record(Record, pubsub_node) ->

View File

@ -38,7 +38,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@ -71,6 +72,9 @@ get_nodes(Host) ->
get_nodes(_Host, _Limit) ->
[].
get_all_nodes(_Host) ->
[].
get_parentnodes(_Host, _Node, _From) ->
[].

View File

@ -489,6 +489,14 @@ wait_for_complete(Config, N) ->
end
end, error, [0, 100, 200, 2000, 5000, 10000]).
xevent_stored(#message{body = [], subject = []}, _) -> false;
xevent_stored(#message{type = T}, _) when T /= chat, T /= normal -> false;
xevent_stored(_, #xevent{id = undefined}) -> true;
xevent_stored(_, #xevent{offline = true}) -> true;
xevent_stored(_, #xevent{delivered = true}) -> true;
xevent_stored(_, #xevent{displayed = true}) -> true;
xevent_stored(_, _) -> false.
message_iterator(Config) ->
ServerJID = server_jid(Config),
ChatStates = [[#chatstate{type = composing}]],
@ -511,8 +519,14 @@ message_iterator(Config) ->
fun(#message{type = error}) -> true;
(#message{type = groupchat}) -> false;
(#message{sub_els = [#hint{type = store}|_]}) when MamEnabled -> true;
(#message{sub_els = [#hint{type = 'no-store'}|_]}) -> false;
(#message{sub_els = [#offline{}|_]}) when not MamEnabled -> false;
(#message{sub_els = [_, #xevent{id = I}]}) when I /= undefined, not MamEnabled -> false;
(#message{sub_els = [#hint{type = store}, #xevent{} = Event | _]} = Msg) when not MamEnabled ->
xevent_stored(Msg#message{body = body, type = chat}, Event);
(#message{sub_els = [#xevent{} = Event]} = Msg) when not MamEnabled ->
xevent_stored(Msg, Event);
(#message{sub_els = [_, #xevent{} = Event | _]} = Msg) when not MamEnabled ->
xevent_stored(Msg, Event);
(#message{sub_els = [#xevent{id = I}]}) when I /= undefined, not MamEnabled -> false;
(#message{sub_els = [#hint{type = store}|_]}) -> true;
(#message{sub_els = [#hint{type = 'no-store'}|_]}) -> false;

View File

@ -42,7 +42,8 @@ INTRUDER()
{
NUMBERS=$(echo "$INPUT" | grep -o . | tr '\n' ' ')
SORTED_UNIQ_NUM=$(echo "${NUMBERS[@]}" | sort -u | tr '\n' ' ')
RANDOM_DIGITS=$(echo 123456789 | grep -o . | sort -R | tr '\n' ' ')
SORT_RANDOM_CMD="$( ( echo x|sort -R >&/dev/null && echo "sort -R" ) || ( echo x|shuf >&/dev/null && echo shuf ) || echo cat)"
RANDOM_DIGITS=$(echo 123456789 | grep -o . | eval "$SORT_RANDOM_CMD" | tr '\n' ' ')
INTRUDER=-1
for i in $RANDOM_DIGITS