Merge branch 'processone:master' into master

This commit is contained in:
Pouriya 2022-01-21 03:14:28 +03:30 committed by GitHub
commit 03485f5e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 2026 additions and 727 deletions

View File

@ -25,12 +25,12 @@ jobs:
strategy:
fail-fast: false
matrix:
otp: ['19.3', '24.0']
otp: ['19.3', '24']
include:
- otp: '19.3'
rebar: 2
os: ubuntu-18.04
- otp: '24.0'
- otp: '24'
rebar: 3
os: ubuntu-20.04
runs-on: ${{ matrix.os }}
@ -44,9 +44,17 @@ jobs:
- uses: actions/checkout@v2
- name: Test shell scripts
if: matrix.otp == 24
run: |
shellcheck test/ejabberd_SUITE_data/gencerts.sh
shellcheck tools/captcha.sh
shellcheck ejabberd.init.template
shellcheck -x ejabberdctl.template
- name: Get previous Erlang/OTP
uses: ErlGang/setup-erlang@master
if: matrix.otp != 24.0
if: matrix.otp != 24
with:
otp-version: ${{ matrix.otp }}
@ -81,7 +89,6 @@ jobs:
libsqlite3-dev libwebp-dev libyaml-dev
- name: Prepare rebar
id: rebar
run: |
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
]}.' >>rebar.config
@ -93,23 +100,26 @@ jobs:
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
echo '{ct_extra_params, "-verbosity 20"}.' >>rebar.config
echo "{ct_opts, [{verbosity, 20}, {keep_logs, 20}]}." >>rebar.config
- name: Cache rebar2
if: matrix.rebar == 2
- name: Cache rebar
uses: actions/cache@v2
with:
path: |
deps/
dialyzer/
ebin/
~/.cache/rebar3/
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
- name: Cache rebar3
if: matrix.rebar == 3
uses: actions/cache@v2
with:
path: ~/.cache/rebar3/
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
- name: Download test logs
if: matrix.otp == 24 && github.repository == 'processone/ejabberd'
continue-on-error: true
run: |
mkdir -p _build/test
curl -sSL https://github.com/processone/ecil/tarball/gh-pages |
tar -C _build/test --strip-components=1 --wildcards -xzf -
rm -rf _build/test/logs/last/
- name: Compile
run: |
@ -128,46 +138,50 @@ jobs:
- run: make hooks
- run: make options
- run: make xref
- run: make dialyzer
- run: make test
- run: |
make dialyzer
[ ${{ matrix.rebar }} = 3 ] && true \
|| { cat dialyzer/error.log ; test ! -s dialyzer/error.log ; }
- name: Run tests
if: matrix.otp != 24
run: make test
- name: Run tests (OTP 24)
if: matrix.otp == 24
id: ct
run: |
(cd priv && ln -sf ../sql)
COMMIT=`echo $GITHUB_SHA | cut -c 1-7`
DATE=`date +%s`
REF_NAME=`echo $GITHUB_REF_NAME | tr "/" "_"`
NODENAME=$DATE@$GITHUB_RUN_NUMBER-$GITHUB_ACTOR-$REF_NAME-$COMMIT
LABEL=`git show -s --format=%s | cut -c 1-30`
rebar3 ct --name $NODENAME --label "$LABEL"
rebar3 cover
- name: Check results
if: always()
run: |
[[ -d _build ]] && ln -s _build/test/logs/ logs \
&& ln `find _build/ -name "*dialyzer_warnings"` \
logs/dialyzer.log \
|| ln dialyzer/error.log logs/dialyzer.log
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
ln `find logs/ -name suite.log` logs/suite.log
grep 'TEST COMPLETE' logs/suite.log
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View dialyzer report
run: cat logs/dialyzer.log
- name: View full suite.log
run: cat logs/suite.log
- name: View suite.log failures
- name: View logs failures
if: failure()
run: cat logs/suite.log | awk
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
- name: View full ejabberd.log
if: failure()
run: find logs/ -name ejabberd.log -exec cat '{}' ';'
- name: View exunit.log
if: failure()
run: find logs/ -name exunit.log -exec cat '{}' ';'
run: |
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
find logs/ -name error.log -exec cat '{}' ';'
find logs/ -name exunit.log -exec cat '{}' ';'
- name: Send to coveralls
if: matrix.otp == 24.0
if: matrix.otp == 24
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
rebar3 as test coveralls send
DIAGNOSTIC=1 rebar3 as test coveralls send
curl -v -k https://coveralls.io/webhook \
--header "Content-Type: application/json" \
--data '{"repo_name":"$GITHUB_REPOSITORY",
@ -175,6 +189,61 @@ jobs:
"payload":{"build_num":$GITHUB_RUN_ID,
"status":"done"}}'
- name: Upload test logs
if: always() && steps.ct.outcome == 'failure' && github.repository == 'processone/ejabberd'
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: _build/test
exclude_assets: '.github,lib,plugins'
external_repository: processone/ecil
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
keep_files: true
- name: View ECIL address
if: always() && steps.ct.outcome == 'failure' && github.repository == 'processone/ejabberd'
run: |
CTRUN=`ls -la _build/test/logs/last | sed 's|.*-> ||'`
echo "::notice::View CT results: https://processone.github.io/ecil/logs/$CTRUN/"
- name: Prepare new schema
run: |
[[ -d logs ]] && rm -rf logs
[[ -d _build/test/logs ]] && rm -rf _build/test/logs || true
mysql -u root -proot -e "DROP DATABASE ejabberd_test;"
sudo -u postgres psql -c "DROP DATABASE ejabberd_test;"
mysql -u root -proot -e "CREATE DATABASE ejabberd_test;"
mysql -u root -proot -e "GRANT ALL ON ejabberd_test.*
TO 'ejabberd_test'@'localhost';"
mysql -u root -proot ejabberd_test < sql/mysql.new.sql
sudo -u postgres psql -c "CREATE DATABASE ejabberd_test;"
sudo -u postgres psql ejabberd_test -f sql/pg.new.sql
sudo -u postgres psql -c "GRANT ALL PRIVILEGES
ON DATABASE ejabberd_test TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
TABLES IN SCHEMA public
TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
SEQUENCES IN SCHEMA public
TO ejabberd_test;"
sudo sed -i 's|new_schema, false|new_schema, true|g' test/suite.erl
- run: CT_BACKENDS=mysql,pgsql make test
id: ctnewschema
- name: Check results
if: always() && steps.ctnewschema.outcome != 'skipped'
run: |
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
ln `find logs/ -name suite.log` logs/suite.log
grep 'TEST COMPLETE' logs/suite.log
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View logs failures
if: failure() && steps.ctnewschema.outcome != 'skipped'
run: |
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
find logs/ -name error.log -exec cat '{}' ';'
find logs/ -name exunit.log -exec cat '{}' ';'
binaries:
name: Binaries
needs: [tests]

4
.shellcheckrc Normal file
View File

@ -0,0 +1,4 @@
disable=SC2016,SC2086,SC2089,SC2090
external-sources=true
source=ejabberdctl.cfg.example
shell=sh

View File

@ -1,3 +1,50 @@
# Version 21.12
Commands
- `create_room_with_opts`: Fixed when using SQL storage
- `change_room_option`: Add missing fields from config inside `mod_muc_admin:change_options`
- piefxis: Fixed arguments of all commands
Modules
- mod_caps: Don't forget caps on XEP-0198 resumption
- mod_conversejs: New module to serve a simple page for Converse.js
- mod_http_upload_quota: Avoid `max_days` race
- mod_muc: Support MUC hats (XEP-0317, conversejs/prosody compatible)
- mod_muc: Optimize MucSub processing
- mod_muc: Fix exception in mucsub {un}subscription events multicast handler
- mod_multicast: Improve and optimize multicast routing code
- mod_offline: Allow storing non-composing x:events in offline
- mod_ping: Send ping from server, not bare user JID
- mod_push: Fix handling of MUC/Sub messages
- mod_register: New allow_modules option to restrict registration modules
- mod_register_web: Handle unknown host gracefully
- mod_register_web: Use mod_register configured restrictions
PubSub
- Add `delete_expired_pubsub_items` command
- Add `delete_old_pubsub_items` command
- Optimize publishing on large nodes (SQL)
- Support unlimited number of items
- Support `max_items=max` node configuration
- Bump default value for `max_items` limit from 10 to 1000
- Use configured `max_items` by default
- node_flat: Avoid catch-all clauses for RSM
- node_flat_sql: Avoid catch-all clauses for RSM
SQL
- Use `INSERT ... ON CONFLICT` in SQL_UPSERT for PostgreSQL >= 9.5
- mod_mam export: assign MUC entries to the MUC service
- MySQL: Fix typo when creating index
- PgSQL: Add SASL auth support, PostgreSQL 14
- PgSQL: Add missing SQL migration for table `push_session`
- PgSQL: Fix `vcard_search` definition in pgsql new schema
Other
- `captcha-ng.sh`: "sort -R" command not POSIX, added "shuf" and "cat" as fallback
- Make s2s connection table cleanup more robust
- Update export/import of scram password to XEP-0227 1.1
- Update Jose to 1.11.1 (the last in hex.pm correctly versioned)
# Version 21.07
Compilation

View File

@ -68,12 +68,6 @@ LUADIR = $(PRIVDIR)/lua
# /var/lib/ejabberd/
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
# /var/lock/ejabberdctl
CTLLOCKDIR = $(DESTDIR)@localstatedir@/lock/ejabberdctl
# /var/lib/ejabberd/.erlang.cookie
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
# /var/log/ejabberd/
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
@ -100,8 +94,10 @@ endif
ifeq "$(MIX)" "mix"
REBAR_VER:=6
REBAR_VER_318:=0
else
REBAR_VER:=$(shell $(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}')
REBAR_VER_318:=$(shell $(REBAR) --version | awk -F '[ .]' '/rebar / {print ($$2 == 3 && $$3 >= 18 ? 1 : 0)}')
endif
ifeq "$(REBAR_VER)" "6"
@ -121,7 +117,11 @@ else
ifeq "$(REBAR_VER)" "3"
SKIPDEPS=
LISTDEPS=tree
ifeq "$(REBAR_VER_318)" "1"
UPDATEDEPS=upgrade --all
else
UPDATEDEPS=upgrade
endif
DEPSPATTERN="s/ (.*//; /^ / s/.* \([a-z0-9_]*\).*/\1/p;"
DEPSBASE=_build
DEPSDIR=$(DEPSBASE)/default/lib
@ -249,7 +249,15 @@ $(call TO_DEST,priv/bin/captcha.sh): tools/captcha.sh $(call TO_DEST,priv/bin)
$(call TO_DEST,priv/lua/redis_sm.lua): priv/lua/redis_sm.lua $(call TO_DEST,priv/lua)
$(INSTALL) -m 644 $< $@
copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh priv/sql/lite.sql priv/sql/lite.new.sql priv/lua/redis_sm.lua)
ifeq (@sqlite@,true)
SQLITE_FILES = priv/sql/lite.sql priv/sql/lite.new.sql
endif
ifeq (@redis@,true)
REDIS_FILES = priv/lua/redis_sm.lua
endif
copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh $(SQLITE_FILES) $(REDIS_FILES))
.PHONY: $(call TO_DEST,$(DEPS_FILES) $(MAIN_DIRS) $(DEPS_DIRS))
@ -298,7 +306,8 @@ install: copy-files
chmod 755 ejabberd.init
#
# Service script
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" ejabberd.service.template \
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" \
-e "s*@installuser@*$(INIT_USER)*g" ejabberd.service.template \
> ejabberd.service
chmod 644 ejabberd.service
#
@ -306,12 +315,6 @@ install: copy-files
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(SPOOLDIR)
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
#
# ejabberdctl lock directory
$(INSTALL) -d -m 750 $(O_USER) $(CTLLOCKDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(CTLLOCKDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(CTLLOCKDIR)
#
# Log directory
$(INSTALL) -d -m 750 $(O_USER) $(LOGDIR)
@ -361,7 +364,6 @@ uninstall-all: uninstall-binary
rm -rf $(ETCDIR)
rm -rf $(EJABBERDDIR)
rm -rf $(SPOOLDIR)
rm -rf $(CTLLOCKDIR)
rm -rf $(LOGDIR)
clean:

View File

@ -4,8 +4,8 @@ After=network.target
[Service]
Type=notify
User=ejabberd
Group=ejabberd
User=@installuser@
Group=@installuser@
LimitNOFILE=65536
Restart=on-failure
RestartSec=5

View File

@ -12,6 +12,8 @@ ERLANG_NODE=ejabberd@localhost
# define default environment variables
[ -z "$SCRIPT" ] && SCRIPT=$0
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd -P)"
# shellcheck disable=SC2034
ERTS_VSN="{{erts_vsn}}"
ERL="{{erl}}"
IEX="{{bindir}}/iex"
EPMD="{{epmd}}"
@ -62,6 +64,7 @@ done
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
# Allows passing extra Erlang command-line arguments in vm.args file
: "${VMARGS:="$ETC_DIR/vm.args"}"
# shellcheck source=ejabberdctl.cfg.example
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
@ -81,7 +84,7 @@ if [ -n "$INET_DIST_INTERFACE" ] ; then
fi
# if vm.args file exists in config directory, pass it to Erlang VM
[ -f "$VMARGS" ] && ERLANG_OPTS="$ERLANG_OPTS -args_file $VMARGS"
ERL_LIBS={{libdir}}
ERL_LIBS='{{libdir}}'
ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
ERL_INETRC="$ETC_DIR"/inetrc
@ -105,6 +108,7 @@ export ERL_MAX_ETS_TABLES
export CONTRIB_MODULES_PATH
export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS
export SCRIPT_DIR
# run command either directly or via su $INSTALLUSER
exec_cmd()
@ -128,14 +132,6 @@ exec_iex()
# usage
debugwarning()
{
if [ "$OSTYPE" != "cygwin" ] && [ "$OSTYPE" != "win32" ] ; then
if [ "a$TERM" = "a" ] || [ "$TERM" = "dumb" ] ; then
echo "Terminal type not supported."
echo "You may have to set the TERM environment variable to fix this."
exit 8
fi
fi
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------"
echo ""
@ -153,7 +149,7 @@ debugwarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read -r input
read -r _
echo ""
fi
}
@ -176,7 +172,7 @@ livewarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read -r input
read -r _
echo ""
fi
}
@ -206,8 +202,9 @@ help()
uid()
{
uuid=$(uuidgen 2>/dev/null)
random=$(awk 'BEGIN { srand(); print int(rand()*32768) }' /dev/null)
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
[ -z "$uuid" ] && uuid=$(printf "%X" "${random:-$$}$(date +%M%S)")
uuid=$(printf '%s' $uuid | sed 's/^\(...\).*$/\1/')
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"

View File

@ -65,6 +65,7 @@
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
mam = false :: boolean(),
pubsub = <<"">> :: binary(),
enable_hats = false :: boolean(),
lang = ejabberd_option:language() :: binary()
}).
@ -124,6 +125,7 @@
history = #lqueue{} :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: ejabberd_shaper:shaper(),

View File

@ -23,7 +23,7 @@
-define(ERR_EXTENDED(E, C), mod_pubsub:extended_error(E, C)).
%% The actual limit can be configured with mod_pubsub's option max_items_node
-define(MAXITEMS, 10).
-define(MAXITEMS, 1000).
%% this is currently a hard limit.
%% Would be nice to have it configurable.

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: 07/21/2021
.\" Date: 12/08/2021
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "07/21/2021" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "12/08/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\&.07/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\&.12/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
.sp
Note that this document is intended to provide comprehensive description of all configuration options that can be consulted to understand the meaning of a particular option, its format and possible values\&. It will be quite hard to understand how to configure ejabberd by reading this document only \- for this purpose the reader is recommended to read online Configuration Guide available at https://docs\&.ejabberd\&.im/admin/configuration\&.
.SH "TOP LEVEL OPTIONS"
@ -316,14 +316,46 @@ means that the same username can be taken multiple times in anonymous login mode
.PP
\fBanonymous_protocol\fR: \fIlogin_anon | sasl_anon | both\fR
.RS 4
Define what anonymous protocol will be used:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIlogin_anon\fR
means that the anonymous login method will be used\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIsasl_anon\fR
means that the SASL Anonymous method will be used\&.
\fIboth\fR
means that SASL Anonymous and login anonymous are both enabled\&. The default value is
\fIsasl_anon\fR\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIboth\fR
means that SASL Anonymous and login anonymous are both enabled\&.
.RE
.RE
.sp
The default value is \fIsasl_anon\fR\&.
.PP
\fBapi_permissions\fR: \fI[Permission, \&.\&.\&.]\fR
.RS 4
@ -375,7 +407,7 @@ A list of authentication methods to use\&. If several methods are defined, authe
This is used by the contributed module
\fIejabberd_auth_http\fR
that can be installed from the
\fIejabberd\-contrib\fR
ejabberd\-contrib
Git repository\&. Please refer to that module\(cqs README file for details\&.
.RE
.sp
@ -383,14 +415,36 @@ Git repository\&. Please refer to that module\(cqs README file for details\&.
.PP
\fBauth_password_format\fR: \fIplain | scram\fR
.RS 4
The option defines in what format the users passwords are stored\&.
The option defines in what format the users passwords are stored:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIplain\fR: 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\&.
\fIscram\fR: 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\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIscram\fR: 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\&. The default value is
\fIplain\fR\&.
.RE
.RE
.PP
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
.RS 4
Hash algorith that should be used to store password in SCRAM format\&. You shouldn\(cqt change this if you already have passwords generated with a different algorithm \- users that have such passwords will not be able to authenticate\&.
Hash algorith that should be used to store password in SCRAM format\&. You shouldn\(cqt change this if you already have passwords generated with a different algorithm \- users that have such passwords will not be able to authenticate\&. The default value is
\fIsha\fR\&.
.RE
.PP
\fBauth_use_cache\fR: \fItrue | false\fR
@ -476,7 +530,7 @@ For server conections, this \fIca_file\fR option is overriden by the s2s_cafile
\fBcache_life_time\fR: \fItimeout()\fR
.RS 4
The time of a cached item to keep in cache\&. Once it\(cqs expired, the corresponding item is erased from cache\&. The default value is
\fIone hour\fR\&. Several modules have a similar option; and some core ejabberd parts support similar options too, see
\fI1 hour\fR\&. Several modules have a similar option; and some core ejabberd parts support similar options too, see
\fIauth_cache_life_time\fR,
\fIoauth_cache_life_time\fR,
\fIrouter_cache_life_time\fR, and
@ -507,7 +561,9 @@ A maximum number of items (not memory!) in cache\&. The rule of thumb, for all t
.PP
\fBcaptcha_cmd\fR: \fIPath\fR
.RS 4
Full path to a script that generates CAPTCHA images\&. There is no default value: when this option is not set, CAPTCHA functionality is completely disabled\&.
Full path to a script that generates
CAPTCHA
images\&. There is no default value: when this option is not set, CAPTCHA functionality is completely disabled\&.
.RE
.PP
\fBcaptcha_host\fR: \fIString\fR
@ -519,13 +575,17 @@ instead\&.
.PP
\fBcaptcha_limit\fR: \fIpos_integer() | infinity\fR
.RS 4
Maximum number of CAPTCHA generated images per minute for any given JID\&. The option is intended to protect the server from CAPTCHA DoS\&. The default value is
Maximum number of
CAPTCHA
generated images per minute for any given JID\&. The option is intended to protect the server from CAPTCHA DoS\&. The default value is
\fIinfinity\fR\&.
.RE
.PP
\fBcaptcha_url\fR: \fIURL\fR
.RS 4
An URL where CAPTCHA requests should be sent\&. NOTE: you need to configure
An URL where
CAPTCHA
requests should be sent\&. NOTE: you need to configure
\fIrequest_handlers\fR
for
\fIejabberd_http\fR
@ -815,7 +875,8 @@ Path to the file that contains the JWK Key\&. The default value is
.RS 4
The option defines the default language of server strings that can be seen by XMPP clients\&. If an XMPP client does not possess
\fIxml:lang\fR
attribute, the specified language is used\&.
attribute, the specified language is used\&. The default value is
\fI"en"\fR\&.
.RE
.PP
\fBldap_backups\fR: \fI[Host, \&.\&.\&.]\fR
@ -948,7 +1009,11 @@ section for details\&.
\fBlog_rotate_count\fR: \fINumber\fR
.RS 4
The number of rotated log files to keep\&. The default value is
\fI1\fR\&.
\fI1\fR, which means that only keeps
ejabberd\&.log\&.0,
error\&.log\&.0
and
crash\&.log\&.0\&.
.RE
.PP
\fBlog_rotate_size\fR: \fIpos_integer() | infinity\fR
@ -989,8 +1054,7 @@ seconds\&.
.RS 4
This option can be used to tune tick time parameter of
\fInet_kernel\fR\&. It tells Erlang VM how often nodes should check if intra\-node communication was not interrupted\&. This option 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
\fI1\fR
minute\&.
\fI1 minute\fR\&.
.RE
.PP
\fBnew_sql_schema\fR: \fItrue | false\fR
@ -998,7 +1062,7 @@ minute\&.
Whether to use
\fInew\fR
SQL schema\&. All schemas are located at
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
https://github\&.com/processone/ejabberd/tree/21\&.12/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
@ -1392,8 +1456,7 @@ if the latter is not set\&.
\fBs2s_timeout\fR: \fItimeout()\fR
.RS 4
A time to wait before closing an idle s2s connection\&. The default value is
\fI10\fR
minutes\&.
\fI10 minutes\fR\&.
.RE
.PP
\fBs2s_tls_compression\fR: \fItrue | false\fR
@ -1795,24 +1858,7 @@ Details for some commands:
\fIsrg\-create\fR: If you want to put a group Name with blankspaces, use the characters "\*(Aq and \*(Aq" to define when the Name starts and ends\&. See an example below\&.
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBmodule_resource\fR: \fIResource\fR
.RS 4
Indicate the resource that the XMPP stanzas must use in the FROM or TO JIDs\&. This is only useful in the
\fIget_vcard*\fR
and
\fIset_vcard*\fR
commands\&. The default value is
\fImod_admin_extra\fR\&.
.RE
.RE
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -1835,8 +1881,7 @@ access_rules:
vcard_set:
\- allow: adminextraresource
modules:
mod_admin_extra:
module_resource: "modadminextraf8x,31ad"
mod_admin_extra: {}
mod_vcard:
access_set: vcard_set
.fi
@ -1884,7 +1929,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\&. Please note that only PostgreSQL is supported\&. 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 API\&.
.sp
The module has no options\&.
.SS "mod_announce"
@ -2321,6 +2366,78 @@ While a client is inactive, queue presence stanzas that indicate (un)availabilit
The module provides server configuration functionality via XEP\-0050: Ad\-Hoc Commands\&. This module requires \fImod_adhoc\fR to be loaded\&.
.sp
The module has no options\&.
.SS "mod_conversejs"
.sp
This module serves a simple page for the Converse XMPP web browser client\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR\fIejabberd_http\fR → request_handlers\&.
.sp
You must also setup either the option \fIwebsocket_url\fR or \fIbosh_service_url\fR\&.
.sp
By default, the options \fIconversejs_css\fR and \fIconversejs_script\fR point to the public Converse\&.js client\&. Alternatively, you can host the client locally using \fImod_http_fileserver\fR\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBbosh_service_url\fR: \fIBoshURL\fR
.RS 4
BOSH service URL to which Converse\&.js can connect to\&.
.RE
.PP
\fBconversejs_css\fR: \fIURL\fR
.RS 4
Converse\&.js CSS URL\&.
.RE
.PP
\fBconversejs_script\fR: \fIURL\fR
.RS 4
Converse\&.js main script URL\&.
.RE
.PP
\fBdefault_domain\fR: \fIDomain\fR
.RS 4
Specify a domain to act as the default for user JIDs\&. The default value is the first domain defined in the ejabberd configuration file\&.
.RE
.PP
\fBwebsocket_url\fR: \fIWebsocketURL\fR
.RS 4
A websocket URL to which Converse\&.js can connect to\&.
.RE
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/websocket: ejabberd_http_ws
/conversejs: mod_conversejs
modules:
mod_conversejs:
websocket_url: "ws://example\&.org:5280/websocket"
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_delegation"
.sp
This module is an implementation of XEP\-0355: Namespace Delegation\&. Only admin mode has been implemented by now\&. Namespace delegation allows external services to handle IQ using specific namespace\&. This may be applied for external PEP service\&.
@ -2479,11 +2596,11 @@ server_info:
\-
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
@ -2563,13 +2680,40 @@ The number of C2S authentication failures to trigger the IP ban\&. The default v
.sp
This module provides a ReST API to call ejabberd commands using JSON data\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also add it to \fIrequest_handlers\fR of some listener\&.
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR\fIejabberd_http\fR → request_handlers\&.
.sp
To use a specific API version N, when defining the URL path in the request_handlers, add a \fIvN\fR\&. For example: \fI/api/v2: mod_http_api\fR
.sp
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/<command_name>\fR
.sp
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/api: mod_http_api
modules:
mod_http_api: {}
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_http_fileserver"
.sp
This simple module serves files from the local disk over HTTP\&.
@ -2697,7 +2841,7 @@ modules:
.sp
This module allows for requesting permissions to upload a file via HTTP as described in 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\&.
.sp
In order to use this module, it must be configured as a \fIrequest_handler\fR for \fIejabberd_http\fR listener\&.
In order to use this module, it must be enabled in \fIlisten\fR\fIejabberd_http\fR → request_handlers\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -2746,7 +2890,9 @@ This option defines the permission bits of uploaded files\&. The bits are specif
.PP
\fBget_url\fR: \fIURL\fR
.RS 4
This option specifies the initial part of the GET URLs used for downloading the files\&. By default, it is set to the same value as
This option specifies the initial part of the GET URLs used for downloading the files\&. The default value is
\fIundefined\fR\&. When this option is
\fIundefined\fR, this option is set to the same value as
\fIput_url\fR\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: if GET requests are handled by
\fImod_http_upload\fR, the
\fIget_url\fR
@ -2795,7 +2941,7 @@ A name of the service in the Service Discovery\&. This will only be displayed by
.PP
\fBput_url\fR: \fIURL\fR
.RS 4
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is "https://@HOST@:5443"\&.
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is "https://@HOST@:5443/upload"\&.
.RE
.PP
\fBrm_on_unregister\fR: \fItrue | false\fR
@ -3530,21 +3676,24 @@ This option specifies who is allowed to administrate the Multi\-User Chat servic
.PP
\fBaccess_create\fR: \fIAccessName\fR
.RS 4
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 allowed to create rooms\&.
To configure who is allowed to create new rooms at the Multi\-User Chat service, this option can be used\&. The default value is
\fIall\fR, which means everyone is allowed to create rooms\&.
.RE
.PP
\fBaccess_mam\fR: \fIAccessName\fR
.RS 4
To configure who is allowed to modify the
\fImam\fR
room option\&. By default any account in the local ejabberd server is allowed to modify that option\&.
room option\&. The default value is
\fIall\fR, which means everyone is allowed to modify that option\&.
.RE
.PP
\fBaccess_persistent\fR: \fIAccessName\fR
.RS 4
To configure who is allowed to modify the
\fIpersistent\fR
room option\&. By default any account in the local ejabberd server is allowed to modify that option\&.
room option\&. The default value is
\fIall\fR, which means everyone is allowed to modify that option\&.
.RE
.PP
\fBaccess_register\fR: \fIAccessName\fR
@ -3825,7 +3974,8 @@ This option defines the number of service admins or room owners allowed to enter
.PP
\fBmax_users_presence\fR: \fINumber\fR
.RS 4
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\&.
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\&. The default value is
\fI1000\fR\&.
.RE
.PP
\fBmin_message_interval\fR: \fINumber\fR
@ -4498,8 +4648,7 @@ This module implements support for XEP\-0199: XMPP Ping and periodic keepalives\
\fBping_ack_timeout\fR: \fItimeout()\fR
.RS 4
How long to wait before deeming that a client has not answered a given server ping request\&. The default value is
\fI32\fR
seconds\&.
\fIundefined\fR\&.
.RE
.PP
\fBping_interval\fR: \fItimeout()\fR
@ -4531,7 +4680,7 @@ means destroying the underlying connection,
\fInone\fR
means to do nothing\&. NOTE: when
\fImod_stream_mgmt\fR
module is loaded and stream management is enabled by a client, killing the client connection doesn\(cqt mean killing the client session \- the session will be kept alive in order to give the client a chance to resume it\&. The default value is
is loaded and stream management is enabled by a client, killing the client connection doesn\(cqt mean killing the client session \- the session will be kept alive in order to give the client a chance to resume it\&. The default value is
\fInone\fR\&.
.RE
.RE
@ -5136,10 +5285,16 @@ or
\fIfalse\fR\&. If not defined, pubsub does not cache last items\&. On systems with not so many nodes, caching last items speeds up pubsub and allows to raise user connection rate\&. The cost is memory usage, as every item is stored in memory\&.
.RE
.PP
\fBmax_items_node\fR: \fIMaxItems\fR
\fBmax_item_expire_node\fR: \fItimeout() | infinity\fR
.RS 4
Specify the maximum item epiry time\&. Default value is:
\fIinfinity\fR\&.
.RE
.PP
\fBmax_items_node\fR: \fInon_neg_integer() | infinity\fR
.RS 4
Define the maximum number of items that can be stored in a node\&. Default value is:
\fI10\fR\&.
\fI1000\fR\&.
.RE
.PP
\fBmax_nodes_discoitems\fR: \fIpos_integer() | infinity\fR
@ -5437,9 +5592,9 @@ The module depends on \fImod_push\fR\&.
.PP
\fBresume_timeout\fR: \fItimeout()\fR
.RS 4
This option specifies the period of time until 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
This option specifies the period of time until 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
\fImod_stream_mgmt\fR
module is restored\&. The default value is
is restored\&. The default value is
\fI72\fR
hours\&.
.RE
@ -5499,7 +5654,7 @@ Change the password from an existing account on the server\&.
Delete an existing account on the server\&.
.RE
.sp
This module reads also another option defined globally for the server: \fIregistration_timeout\fR\&. Please check that option documentation in the section with top\-level options\&.
This module reads also the top\-level \fIregistration_timeout\fR option defined globally for the server, so please check that option documentation too\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@ -5528,11 +5683,18 @@ doesn\(cqt allow to register new accounts from s2s or existing c2s sessions\&. Y
Specify rules to restrict access for user unregistration\&. By default any user is able to unregister their account\&.
.RE
.PP
\fBallow_modules\fR: \fIall | [Module, \&.\&.\&.]\fR
.RS 4
List of modules that can register accounts, or
\fIall\fR\&. The default value is
\fIall\fR, which is equivalent to something like
\fI[mod_register, mod_register_web]\fR\&.
.RE
.PP
\fBcaptcha_protected\fR: \fItrue | false\fR
.RS 4
Protect registrations with CAPTCHA (see section
CAPTCHA
of the Configuration Guide)\&. The default is
Protect registrations with
CAPTCHA\&. The default is
\fIfalse\fR\&.
.RE
.PP
@ -5551,7 +5713,8 @@ This option sets the minimum
Shannon entropy
for passwords\&. The value
\fIEntropy\fR
is a number of bits of entropy\&. The recommended minimum is 32 bits\&. The default is 0, i\&.e\&. no checks are performed\&.
is a number of bits of entropy\&. The recommended minimum is 32 bits\&. The default is
\fI0\fR, i\&.e\&. no checks are performed\&.
.RE
.PP
\fBredirect_url\fR: \fIURL\fR
@ -5610,13 +5773,40 @@ Change the password from an existing account on the server\&.
Unregister an existing account on the server\&.
.RE
.sp
This module supports CAPTCHA image to register a new account\&. To enable this feature, configure the options \fIcaptcha_cmd\fR and \fIcaptcha_url\fR, which are documented in the section with top\-level options\&.
This module supports CAPTCHA to register a new account\&. To enable this feature, configure the top\-level \fIcaptcha_cmd\fR and top\-level \fIcaptcha_url\fR options\&.
.sp
As an example usage, the users of the host \fIexample\&.org\fR can visit the page: \fIhttps://example\&.org:5281/register/\fR It is important to include the last / character in the URL, otherwise the subpages URL will be incorrect\&.
As an example usage, the users of the host \fIlocalhost\fR can visit the page: \fIhttps://localhost:5280/register/\fR It is important to include the last / character in the URL, otherwise the subpages URL will be incorrect\&.
.sp
The module depends on \fImod_register\fR where all the configuration is performed\&.
This module is enabled in \fIlisten\fR\fIejabberd_http\fR → request_handlers, no need to enable in \fImodules\fR\&. The module depends on \fImod_register\fR where all the configuration is performed\&.
.sp
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/register: mod_register_web
modules:
mod_register: {}
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_roster"
.sp
This module implements roster management as defined in RFC6121 Section 2\&. The module also adds support for XEP\-0237: Roster Versioning\&.
@ -5910,8 +6100,9 @@ option, but applied to this module only\&.
.PP
\fBdb_type\fR: \fImnesia | sql\fR
.RS 4
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
\fIdefault_db\fR, or
Define the type of storage where the module will create the tables and store user information\&. The default is the storage defined by the top\-level
\fIdefault_db\fR
option, or
\fImnesia\fR
if omitted\&. If
\fIsql\fR
@ -6521,7 +6712,8 @@ minute\&.
.RS 4
Same as top\-level
\fIcache_life_time\fR
option, but applied to this module only\&.
option, but applied to this module only\&. The default value is
\fI48 hours\fR\&.
.RE
.PP
\fBcache_size\fR: \fIpos_integer() | infinity\fR
@ -6594,8 +6786,7 @@ This option defines which access rule will be used to control who is allowed to
\fBcredentials_lifetime\fR: \fItimeout()\fR
.RS 4
The lifetime of temporary credentials offered to clients\&. If ejabberd\(cqs built\-in TURN service is used, TURN relays allocated using temporary credentials will be terminated shortly after the credentials expired\&. The default value is
\fI12\fR
hours\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
\fI12 hours\fR\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
\fIsecret\fR
is specified (see below)\&.
.RE
@ -7121,7 +7312,7 @@ The module depends on \fImod_vcard\fR\&.
.ps -1
.br
.sp
Nowadays XEP\-0153 is used mostly as "read\-only", i\&.e\&. modern clients don\(cqt publish their avatars inside vCards\&. Thus in the majority of cases the module is only used along with \fImod_avatar\fR module for providing backward compatibility\&.
Nowadays XEP\-0153 is used mostly as "read\-only", i\&.e\&. modern clients don\(cqt publish their avatars inside vCards\&. Thus in the majority of cases the module is only used along with \fImod_avatar\fR for providing backward compatibility\&.
.sp .5v
.RE
.sp
@ -7189,13 +7380,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 21\&.04\&.131\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 21\&.12\&. Configuration options of other ejabberd versions may differ significantly\&.
.SH "REPORTING BUGS"
.sp
Report bugs to https://github\&.com/processone/ejabberd/issues
.SH "SEE ALSO"
.sp
Default configuration file: https://github\&.com/processone/ejabberd/blob/21\&.07/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/21\&.12/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp

17
mix.exs
View File

@ -24,6 +24,7 @@ defmodule Ejabberd.MixProject 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
'latest.0' -> "0.0.0" # running 'docker-ejabberd/ecs/build.sh latest'
[_, _, ?., _, _] = x ->
head = String.replace(:erlang.list_to_binary(x), ~r/0+([0-9])/, "\\1")
<<head::binary, ".0">>
@ -87,6 +88,7 @@ defmodule Ejabberd.MixProject do
if_version_below('23', [{:d, :USE_OLD_PG2}]) ++
if_version_below('24', [{:d, :COMPILER_REPORTS_ONLY_LINES}]) ++
if_version_below('24', [{:d, :SYSTOOLS_APP_DEF_WITHOUT_OPTIONAL}]) ++
if_function_exported(:uri_string, :normalize, 1, [{:d, :HAVE_URI_STRING}])
if_function_exported(:erl_error, :format_exception, 6, [{:d, :HAVE_ERL_ERROR}])
defines = for {:d, value} <- result, do: {:d, value}
result ++ [{:d, :ALL_DEFS, defines}]
@ -102,7 +104,7 @@ defmodule Ejabberd.MixProject do
end
defp deps do
[{:base64url, "~> 0.0.1"},
[{:base64url, "~> 1.0"},
{:cache_tab, "~> 1.0"},
{:distillery, "~> 2.0"},
{:eimp, "~> 1.0"},
@ -113,7 +115,7 @@ defmodule Ejabberd.MixProject do
{:fast_yaml, "~> 1.0"},
{:idna, "~> 6.0"},
{:jiffy, "~> 1.0.5"},
{:jose, "~> 1.8"},
{:jose, "~> 1.11.1"},
{:lager, "~> 3.9.1"},
{:mqtree, "~> 1.0"},
{:p1_acme, "~> 1.0"},
@ -124,7 +126,7 @@ defmodule Ejabberd.MixProject do
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:stun, "~> 1.0"},
{:xmpp, git: "https://github.com/processone/xmpp", ref: "e943c0285aa85e3cbd4bfb9259f6b7de32b00395", override: true},
{:xmpp, "~> 1.5"},
{:yconf, "~> 1.0"}]
++ cond_deps()
end
@ -145,7 +147,7 @@ defmodule Ejabberd.MixProject do
for {:true, dep} <- [{config(:pam), {:epam, "~> 1.0"}},
{config(:redis), {:eredis, "~> 1.2.0"}},
{config(:zlib), {:ezlib, "~> 1.0"}},
{config(:lua), {:luerl, "~> 0.3.1"}},
{config(:lua), {:luerl, "~> 1.0"}},
{config(:sqlite), {:sqlite3, "~> 1.1"}}], do:
dep
end
@ -213,6 +215,7 @@ defmodule Ejabberd.MixProject do
epmd: config(:epmd),
bindir: Path.join([config(:release_dir), "releases", version()]),
release_dir: config(:release_dir),
erts_dir: config(:erts_dir),
erts_vsn: "erts-#{release.erts_version}"
]
ro = "rel/overlays"
@ -238,7 +241,9 @@ defmodule Ejabberd.MixProject do
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|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2> ejabberdctl.example2a")
Mix.Generator.copy_template("ejabberdctl.example2a", "ejabberdctl.example2b", assigns)
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2b > 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)
@ -246,6 +251,8 @@ defmodule Ejabberd.MixProject do
File.rm("ejabberdctl.example1")
File.rm("ejabberdctl.example2")
File.rm("ejabberdctl.example2a")
File.rm("ejabberdctl.example2b")
File.rm("ejabberdctl.example3")
File.rm("ejabberdctl.example4")
File.rm("ejabberdctl.example5")

View File

@ -1,36 +1,36 @@
%{
"artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
"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_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
"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"},
"esip": {:hex, :esip, "1.0.45", "2f21fb9750f7a505e6bbd43f6d48b0e879b808aba6c2224686c83f2bcd7a34bf", [: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.47", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "1f1eae69f2bd8d75f42c048409eabb4e3dc71ab6412fc5d998edbdade6ad5f75"},
"ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [: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", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"},
"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_xml": {:hex, :fast_xml, "1.1.48", "d41d14015227999a2367264cc97ac1e6770285aab1dc69545ac4f822be01a2d2", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "afcf9b808c77599395d4bd22ed4560b3d82aa1a24ff5b65f3930fe72a423b3cf"},
"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"},
"jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
"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_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"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.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.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"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"p1_acme": {:hex, :p1_acme, "1.0.16", "88b84cc24c9b6eb87204ea53969ccd9b524dcd4142de632441fdd2859ccab778", [:rebar3], [{:base64url, "1.0.1", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.11.1", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.12", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "ec0ef380a7345c38b57899733f6fece97c337a3d44fd02cc8898f6a2491a38a8"},
"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"},
"stun": {:hex, :stun, "1.0.47", "fae94c0dc7415263297e8f07f286f3355d327d8bf78b1b0743c9a5a492381f71", [: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", "377d8487f4add85f6bc6ecdebdb4dcbcbe890e9462f27d6d31f3db1cf9b2cc9b"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"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"},
"xmpp": {:hex, :xmpp, "1.5.6", "09259177a39c880d682817932f4da0537c471160fd43aa891ea9cb71cf827b52", [: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.48", [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", "59b7317c4077d3384f9a891e0517a591cdbd44a323260b835eafbede4f4eb12e"},
"yconf": {:hex, :yconf, "1.0.12", "78c119d39bb805207fcb7671cb884805d75ee89c9ec98632b678f90a597dee2c", [:rebar3], [{:fast_yaml, "1.0.32", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "12faa51c281e95bcb6abf185fd034a242209621a7bb04b6cc411c867b192e207"},
}

View File

@ -5,11 +5,20 @@
{" (Add * to the end of field to match substring)"," (Ajouter * à la fin du champ pour correspondre à la sous-chaîne)"}.
{" has set the subject to: "," a défini le sujet sur : "}.
{"# participants","# participants"}.
{"A description of the node","Une description du nœud"}.
{"A friendly name for the node","Un nom convivial pour le nœud"}.
{"A password is required to enter this room","Un mot de passe est nécessaire pour accéder à ce salon"}.
{"A Web Page","Une page Web"}.
{"Accept","Accepter"}.
{"Access denied by service policy","L'accès au service est refusé"}.
{"Access model of authorize","Modèle daccès de «autoriser»"}.
{"Access model of open","Modèle daccès de «ouvrir»"}.
{"Access model of presence","Modèle daccès de «présence»"}.
{"Access model of roster","Modèle daccès de «liste»"}.
{"Access model of whitelist","Modèle daccès de «liste blanche»"}.
{"Access model","Modèle daccès"}.
{"Account doesn't exist","Le compte nexiste pas"}.
{"Action on user","Action sur l'utilisateur"}.
{"Add Jabber ID","Ajouter un Jabber ID"}.
{"Add New","Ajouter"}.
@ -19,7 +28,9 @@
{"Administrator privileges required","Les droits d'administrateur sont nécessaires"}.
{"All activity","Toute activité"}.
{"All Users","Tous les utilisateurs"}.
{"Allow subscription","Autoriser labonnement"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Autoriser ce Jabber ID à s'abonner à ce nœud PubSub ?"}.
{"Allow this person to register with the room?","Autoriser cette personne à enregistrer ce salon?"}.
{"Allow users to change the subject","Autoriser les utilisateurs à changer le sujet"}.
{"Allow users to query other users","Autoriser les utilisateurs à envoyer des requêtes aux autres utilisateurs"}.
{"Allow users to send invites","Autoriser les utilisateurs à envoyer des invitations"}.
@ -28,8 +39,25 @@
{"Allow visitors to send private messages to","Autoriser les visiteurs à envoyer des messages privés"}.
{"Allow visitors to send status text in presence updates","Autoriser les visiteurs à envoyer un message d'état avec leur présence"}.
{"Allow visitors to send voice requests","Permettre aux visiteurs d'envoyer des demandes de 'voice'"}.
{"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.","Un groupe LDAP associé qui définit ladhésion à un salon; cela devrait être un nom distingué LDAP selon la définition spécifique à limplémentation ou au déploiement dun groupe."}.
{"Announcements","Annonces"}.
{"Answer associated with a picture","Réponse associée à une image"}.
{"Answer associated with a video","Réponse associée à une vidéo"}.
{"Answer associated with speech","Réponse associée à un discours"}.
{"Answer to a question","Réponse à une question"}.
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Nimporte qui dans le groupe de la liste spécifiée peut sabonner et récupérer les items"}.
{"Anyone may associate leaf nodes with the collection","Nimporte qui peut associer les feuilles avec la collection"}.
{"Anyone may publish","Nimporte qui peut publier"}.
{"Anyone may subscribe and retrieve items","Nimporte qui peut sabonner et récupérer les items"}.
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Nimporte qui avec un abonnement de présence peut sabonner et récupérer les items"}.
{"Anyone with Voice","Nimporte qui avec Voice"}.
{"Anyone","Nimporte qui"}.
{"April","Avril"}.
{"Attribute 'channel' is required for this request","Lattribut «channel» est requis pour la requête"}.
{"Attribute 'id' is mandatory for MIX messages","Lattribut «id» est obligatoire pour les messages MIX"}.
{"Attribute 'jid' is not allowed here","Lattribut «jid» nest pas autorisé ici"}.
{"Attribute 'node' is not allowed here","Lattribut «node» nest pas autorisé ici"}.
{"Attribute 'to' of stanza that triggered challenge","Lattribut «to» de la strophe qui a déclenché le défi"}.
{"August","Août"}.
{"Automatic node creation is not enabled","La creation implicite de nœud n'est pas disponible"}.
{"Backup Management","Gestion des sauvegardes"}.
@ -43,10 +71,14 @@
{"Cannot remove active list","La liste active ne peut être supprimée"}.
{"Cannot remove default list","La liste par défaut ne peut être supprimée"}.
{"CAPTCHA web page","Page web de CAPTCHA"}.
{"Challenge ID","Identifiant du défi"}.
{"Change Password","Modifier le mot de passe"}.
{"Change User Password","Changer le mot de passe de l'utilisateur"}.
{"Changing password is not allowed","La modification du mot de passe n'est pas autorisée"}.
{"Changing role/affiliation is not allowed","La modification role/affiliation n'est pas autorisée"}.
{"Channel already exists","Ce canal existe déjà"}.
{"Channel does not exist","Le canal nexiste pas"}.
{"Channels","Canaux"}.
{"Characters not allowed:","Caractères non autorisés :"}.
{"Chatroom configuration modified","Configuration du salon modifiée"}.
{"Chatroom is created","Le salon de discussion est créé"}.
@ -58,30 +90,39 @@
{"Choose storage type of tables","Choisissez un type de stockage pour les tables"}.
{"Choose whether to approve this entity's subscription.","Choisissez d'approuver ou non l'abonnement de cette entité."}.
{"City","Ville"}.
{"Client acknowledged more stanzas than sent by server","Le client accuse réception de plus de strophes que ce qui est envoyé par le serveur"}.
{"Commands","Commandes"}.
{"Conference room does not exist","Le salon de discussion n'existe pas"}.
{"Configuration of room ~s","Configuration pour le salon ~s"}.
{"Configuration","Configuration"}.
{"Connected Resources:","Ressources connectées :"}.
{"Contact Addresses (normally, room owner or owners)","Adresses de contact (normalement les administrateurs du salon)"}.
{"Country","Pays"}.
{"CPU Time:","Temps CPU :"}.
{"Current Discussion Topic","Sujet de discussion courant"}.
{"Database failure","Échec sur la base de données"}.
{"Database Tables at ~p","Tables de base de données sur ~p"}.
{"Database Tables Configuration at ","Configuration des tables de base de données sur "}.
{"Database","Base de données"}.
{"December","Décembre"}.
{"Default users as participants","Les utilisateurs sont participant par défaut"}.
{"Delete content","Supprimer le contenu"}.
{"Delete message of the day on all hosts","Supprimer le message du jour sur tous les domaines"}.
{"Delete message of the day","Supprimer le message du jour"}.
{"Delete Selected","Suppression des éléments sélectionnés"}.
{"Delete table","Supprimer la table"}.
{"Delete User","Supprimer l'utilisateur"}.
{"Deliver event notifications","Envoyer les notifications d'événement"}.
{"Deliver payloads with event notifications","Inclure le contenu du message avec la notification"}.
{"Description:","Description :"}.
{"Disc only copy","Copie sur disque uniquement"}.
{"'Displayed groups' not added (they do not exist!): ","«Groupes affichés» non ajoutés (ils nexistent pas!) : "}.
{"Displayed:","Affichés :"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Ne révélez votre mot de passe à personne, pas même aux administrateurs du serveur XMPP."}.
{"Dump Backup to Text File at ","Enregistrer la sauvegarde dans un fichier texte sur "}.
{"Dump to Text File","Sauvegarder dans un fichier texte"}.
{"Duplicated groups are not allowed by RFC6121","Les groupes dupliqués ne sont pas autorisés par la RFC6121"}.
{"Dynamically specify a replyto of the item publisher","Spécifie dynamiquement un «réponse à » de litem de léditeur"}.
{"Edit Properties","Modifier les propriétés"}.
{"Either approve or decline the voice request.","Accepter ou refuser la demande de voix."}.
{"ejabberd MUC module","Module MUC ejabberd"}.
@ -116,6 +157,7 @@
{"Failed to parse HTTP response","Échec de lecture de la réponse HTTP"}.
{"Failed to process option '~s'","Échec de traitement de l'option '~s'"}.
{"Family Name","Nom de famille"}.
{"FAQ Entry","Entrée FAQ"}.
{"February","Février"}.
{"File larger than ~w bytes","Taille de fichier suppérieur à ~w octets"}.
{"Fill in the form to search for any matching XMPP User","Complétez le formulaire pour rechercher un utilisateur XMPP correspondant"}.
@ -176,6 +218,7 @@
{"July","Juillet"}.
{"June","Juin"}.
{"Just created","Vient d'être créé"}.
{"Label:","Étiquette :"}.
{"Last Activity","Dernière activité"}.
{"Last login","Dernière connexion"}.
{"Last month","Dernier mois"}.
@ -192,7 +235,6 @@
{"Make room public searchable","Rendre le salon public"}.
{"Malformed username","Nom d'utilisateur invalide"}.
{"March","Mars"}.
{"Max # of items to persist","Nombre maximum d'éléments à stocker"}.
{"Max payload size in bytes","Taille maximum pour le contenu du message en octet"}.
{"Maximum file size","Taille maximale du fichier"}.
{"Maximum Number of History Messages Returned by Room","Nombre maximal de messages d'historique renvoyés par salle"}.
@ -274,6 +316,7 @@
{"Online Users:","Utilisateurs connectés :"}.
{"Online Users","Utilisateurs en ligne"}.
{"Online","En ligne"}.
{"Only admins can see this","Seuls les administrateurs peuvent voir cela"}.
{"Only deliver notifications to available users","Envoyer les notifications uniquement aux utilisateurs disponibles"}.
{"Only <enable/> or <disable/> tags are allowed","Seul le tag <enable/> ou <disable/> est autorisé"}.
{"Only <list/> element is allowed in this query","Seul l'élément <list/> est autorisé dans cette requête"}.
@ -283,6 +326,7 @@
{"Only moderators can approve voice requests","Seuls les modérateurs peuvent accépter les requêtes voix"}.
{"Only occupants are allowed to send messages to the conference","Seuls les occupants peuvent envoyer des messages à la conférence"}.
{"Only occupants are allowed to send queries to the conference","Seuls les occupants sont autorisés à envoyer des requêtes à la conférence"}.
{"Only publishers may publish","Seuls les éditeurs peuvent publier"}.
{"Only service administrators are allowed to send service messages","Seuls les administrateurs du service sont autoriser à envoyer des messages de service"}.
{"Organization Name","Nom de l'organisation"}.
{"Organization Unit","Unité de l'organisation"}.
@ -290,6 +334,7 @@
{"Outgoing s2s Connections:","Connexions s2s sortantes :"}.
{"Owner privileges required","Les droits de propriétaire sont nécessaires"}.
{"Packet","Paquet"}.
{"Participant","Participant"}.
{"Password Verification","Vérification du mot de passe"}.
{"Password Verification:","Vérification du mot de passe :"}.
{"Password","Mot de passe"}.
@ -323,6 +368,9 @@
{"Remove User","Supprimer l'utilisateur"}.
{"Remove","Supprimer"}.
{"Replaced by new connection","Remplacé par une nouvelle connexion"}.
{"Request has timed out","La demande a expiré"}.
{"Request is ignored","La demande est ignorée"}.
{"Requested role","Rôle demandé"}.
{"Resources","Ressources"}.
{"Restart Service","Redémarrer le service"}.
{"Restart","Redémarrer"}.

View File

@ -4,7 +4,7 @@
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," (在字段末添加*来匹配子串)"}.
{" has set the subject to: ","已将标题设置为: "}.
{" has set the subject to: "," 已将标题设置为: "}.
{"# participants","# 个参与人"}.
{"A description of the node","该节点的描述"}.
{"A friendly name for the node","该节点的友好名称"}.
@ -23,7 +23,7 @@
{"Add Jabber ID","添加Jabber ID"}.
{"Add New","添加新用户"}.
{"Add User","添加用户"}.
{"Administration of ","管理"}.
{"Administration of ","管理 "}.
{"Administration","管理"}.
{"Administrator privileges required","需要管理员权限"}.
{"All activity","所有活动"}.
@ -62,7 +62,7 @@
{"Automatic node creation is not enabled","未启用自动节点创建"}.
{"Backup Management","备份管理"}.
{"Backup of ~p","~p的备份"}.
{"Backup to File at ","备份文件位于"}.
{"Backup to File at ","备份文件位于 "}.
{"Backup","备份"}.
{"Bad format","格式错误"}.
{"Birthday","出生日期"}.
@ -102,7 +102,7 @@
{"Current Discussion Topic","当前讨论话题"}.
{"Database failure","数据库失败"}.
{"Database Tables at ~p","位于~p的数据库表"}.
{"Database Tables Configuration at ","数据库表格配置位于"}.
{"Database Tables Configuration at ","数据库表格配置位于 "}.
{"Database","数据库"}.
{"December","十二月"}.
{"Default users as participants","用户默认被视为参与人"}.
@ -119,12 +119,12 @@
{"'Displayed groups' not added (they do not exist!): ","'显示的群组' 未被添加 (它们不存在!): "}.
{"Displayed:","已显示:"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","不要将密码告诉任何人, 就算是XMPP服务器的管理员也不可以."}.
{"Dump Backup to Text File at ","转储备份到文本文件于"}.
{"Dump Backup to Text File at ","将备份转储到位于以下位置的文本文件 "}.
{"Dump to Text File","转储到文本文件"}.
{"Duplicated groups are not allowed by RFC6121","按照RFC6121的规则不允许有重复的群组"}.
{"Dynamically specify a replyto of the item publisher","为项目发布者动态指定一个 replyto"}.
{"Edit Properties","编辑属性"}.
{"Either approve or decline the voice request.","接受或拒绝声音请求"}.
{"Either approve or decline the voice request.","接受或拒绝声音请求."}.
{"ejabberd HTTP Upload service","ejabberd HTTP 上传服务"}.
{"ejabberd MUC module","ejabberd MUC 模块"}.
{"ejabberd Multicast service","ejabberd多重映射服务"}.
@ -194,10 +194,10 @@
{"Import Directory","导入目录"}.
{"Import File","导入文件"}.
{"Import user data from jabberd14 spool file:","从 jabberd14 Spool 文件导入用户数据:"}.
{"Import User from File at ","导入用户的文件位于"}.
{"Import User from File at ","从以下位置的文件导入用户 "}.
{"Import users data from a PIEFXIS file (XEP-0227):","从 PIEFXIS 文件 (XEP-0227) 导入用户数据:"}.
{"Import users data from jabberd14 spool directory:","从jabberd14 Spool目录导入用户数据:"}.
{"Import Users from Dir at ","导入用户的目录位于"}.
{"Import Users from Dir at ","从以下位置目录导入用户 "}.
{"Import Users From jabberd14 Spool Files","从 jabberd14 Spool 文件导入用户"}.
{"Improper domain part of 'from' attribute","不恰当的'from'属性域名部分"}.
{"Improper message type","不恰当的消息类型"}.
@ -249,7 +249,6 @@
{"Malformed username","用户名无效"}.
{"MAM preference modification denied by service policy","MAM偏好被服务策略拒绝"}.
{"March","三月"}.
{"Max # of items to persist","允许持久化的最大内容条目数"}.
{"Max payload size in bytes","最大有效负载字节数"}.
{"Maximum file size","最大文件大小"}.
{"Maximum Number of History Messages Returned by Room","房间返回的历史消息最大值"}.
@ -288,7 +287,7 @@
{"Never","从未"}.
{"New Password:","新密码:"}.
{"Nickname can't be empty","昵称不能为空"}.
{"Nickname Registration at ","昵称注册于"}.
{"Nickname Registration at ","昵称注册于 "}.
{"Nickname ~s does not exist in the room","昵称~s不在该房间"}.
{"Nickname","昵称"}.
{"No address elements found","没有找到地址的各元素"}.
@ -326,6 +325,7 @@
{"Node ~p","节点~p"}.
{"Nodeprep has failed","Nodeprep 已失效"}.
{"Nodes","节点"}.
{"Node","节点"}.
{"None","无"}.
{"Not allowed","不允许"}.
{"Not Found","没有找到"}.
@ -339,7 +339,6 @@
{"Number of Offline Messages","离线消息数量"}.
{"Number of online users","在线用户数"}.
{"Number of registered users","注册用户数"}.
{"Number of seconds after which to automatically purge items","自动清除项目要等待的秒数"}.
{"Occupants are allowed to invite others","允许成员邀请其他人"}.
{"Occupants May Change the Subject","成员可以修改主题"}.
{"October","十月"}.
@ -428,7 +427,7 @@
{"Resources","资源"}.
{"Restart Service","重启服务"}.
{"Restart","重启"}.
{"Restore Backup from File at ","要恢复的备份文件位于"}.
{"Restore Backup from File at ","从以下位置的文件恢复备份 "}.
{"Restore binary backup after next ejabberd restart (requires less memory):","在下次 ejabberd 重启后恢复二进制备份(需要的内存更少):"}.
{"Restore binary backup immediately:","立即恢复二进制备份:"}.
{"Restore plain text backup immediately:","立即恢复普通文本备份:"}.
@ -455,7 +454,7 @@
{"Search Results for ","搜索结果属于关键词 "}.
{"Search the text","搜索文本"}.
{"Search until the date","搜索截至日期"}.
{"Search users in ","搜索用户于"}.
{"Search users in ","在以下位置搜索用户 "}.
{"Select All","全选"}.
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
{"Send announcement to all online users","发送通知给所有在线用户"}.
@ -519,7 +518,6 @@
{"The JIDs of those with an affiliation of owner","隶属所有人的JID"}.
{"The JIDs of those with an affiliation of publisher","隶属发布人的JID"}.
{"The list of JIDs that may associate leaf nodes with a collection","可以将叶节点与集合关联的JID列表"}.
{"The maximum number of child nodes that can be associated with a collection","可以与集合关联的最大子节点数"}.
{"The minimum number of milliseconds between sending any two notification digests","发送任何两个通知摘要之间的最小毫秒数"}.
{"The name of the node","该节点的名称"}.
{"The node is a collection node","该节点是集合节点"}.
@ -544,13 +542,12 @@
{"The type of node data, usually specified by the namespace of the payload (if any)","节点数据的类型, 如果有, 通常由有效负载的名称空间指定"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","XSL转换的URL可以将其应用于有效负载以生成适当的消息正文元素。"}.
{"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","XSL转换的URL, 可以将其应用于有效负载格式, 以生成有效的数据表单结果, 客户端可以使用通用数据表单呈现引擎来显示该结果"}.
{"The username is not valid","用户名无效"}.
{"There was an error changing the password: ","修改密码出错: "}.
{"There was an error creating the account: ","帐户创建出错: "}.
{"There was an error deleting the account: ","帐户删除失败: "}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","此处不区分大小写: macbeth 与 MacBeth 和 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.","本页面允许在此服务器上注册XMPP帐户. 你的JID (Jabber ID) 的形式如下: 用户名@服务器. 请仔细阅读说明并正确填写相应字段."}.
{"This page allows to unregister an XMPP account in this XMPP server.","此页面允许在此XMPP服务器上注销XMPP帐户"}.
{"This page allows to unregister an XMPP account in this XMPP server.","此页面允许在此 XMPP 服务器上注销 XMPP 帐户"}.
{"This room is not anonymous","此房间不是匿名房间"}.
{"This service can not process the address: ~s","此服务无法处理地址: ~s"}.
{"Thursday","星期四"}.
@ -565,7 +562,7 @@
{"Too many child elements","太多子元素"}.
{"Too many <item/> elements","太多 <item/> 元素"}.
{"Too many <list/> elements","太多 <list/> 元素"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","来自IP地址(~p)的(~s)失败认证太多. 该地址将在UTC时间~s被禁用."}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","来自IP地址(~p)的(~s)失败认证太多。将在UTC时间 ~s 解除对该地址的封锁"}.
{"Too many receiver fields were specified","指定的接收者字段太多"}.
{"Too many unacked stanzas","未被确认的节太多"}.
{"Too many users in this conference","该会议的用户太多"}.
@ -656,7 +653,7 @@
{"You need a client that supports x:data to register the nickname","您需要一个支持 x:data 的客户端来注册昵称"}.
{"You need an x:data capable client to search","您需要一个兼容 x:data 的客户端来搜索"}.
{"Your active privacy list has denied the routing of this stanza.","你的活跃私聊列表拒绝了在此房间进行路由分发."}.
{"Your contact offline message queue is full. The message has been discarded.","您的联系人离线消息队列已满. 消息已被丢弃"}.
{"Your contact offline message queue is full. The message has been discarded.","您的联系人离线消息队列已满。消息已被丢弃。"}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","您发送给~s的消息已被阻止. 要解除阻止, 请访问 ~s"}.
{"Your XMPP account was successfully registered.","你的XMPP帐户注册成功."}.
{"Your XMPP account was successfully unregistered.","你的XMPP帐户注销成功."}.

View File

@ -30,25 +30,31 @@
{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.43"}}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.45"}}}},
{if_var_true, zlib,
{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_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.48"}}},
{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"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}},
{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"}}}},
{if_not_rebar3,
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "1.0"}}}
}},
{if_var_true, lua,
{if_rebar3,
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "1.0.0"}}}
}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.14"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.13"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.16"}}},
{if_var_true, mysql,
{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.12"}}}},
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.16"}}}},
{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
@ -58,12 +64,12 @@
{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.44"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e943c0285aa85e3cbd4bfb9259f6b7de32b00395"}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.47"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.5.6"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.12"}}}
]}.
{gitonly_deps, [elixir, luerl]}.
{gitonly_deps, [elixir]}.
{if_var_true, latest_deps,
{floating_deps, [cache_tab,
@ -110,6 +116,7 @@
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
{if_have_fun, {erl_error, format_exception, 6}, {d, 'HAVE_ERL_ERROR'}},
{if_have_fun, {uri_string, normalize, 1}, {d, 'HAVE_URI_STRING'}},
{src_dirs, [src,
{if_rebar3, sql},
{if_var_true, tools, tools},
@ -177,12 +184,10 @@
{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-
{copy, "rel/files/erl", "erts-\{\{erts_vsn\}\}/bin/erl"},
{template, "ejabberdctl.template", "bin/ejabberdctl"},
{copy, "inetrc", "etc/ejabberd/inetrc"},
{copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"},
@ -193,6 +198,7 @@
{dev_mode, false},
{include_erts, true},
{include_src, true},
{generate_start_script, false},
{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"}]}]}]},
@ -201,6 +207,8 @@
{dev_mode, true},
{include_erts, true},
{include_src, false},
{generate_start_script, true},
{extended_start_script, true},
{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/"},

View File

@ -387,8 +387,8 @@ Rules = [
]}]), []},
{[plugins], IsRebar3 and (os:getenv("GITHUB_ACTIONS") == "true"),
AppendList([{coveralls, {git,
"https://github.com/RoadRunnr/coveralls-erl.git",
{branch, "feature/git-info"}}} ]), []},
"https://github.com/processone/coveralls-erl.git",
{branch, "addjsonfile"}}} ]), []},
{[overrides], [post_hook_configure], SystemDeps == false,
AppendList2(GenDepsConfigure), [], []},
{[ct_extra_params], [eunit_compile_opts], true,

View File

@ -89,7 +89,6 @@ Sys = [{lib_dirs, []},
Overlay = [
{mkdir, "var/log/ejabberd"},
{mkdir, "var/lock"},
{mkdir, "var/lib/ejabberd"},
{mkdir, "etc/ejabberd"},
{mkdir, "doc"},

View File

@ -330,6 +330,7 @@ CREATE TABLE muc_room_subscribers (
);
CREATE INDEX i_muc_room_subscribers_host_jid ON muc_room_subscribers(host, jid);
CREATE INDEX i_muc_room_subscribers_jid ON muc_room_subscribers(jid);
CREATE UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers(host, room, jid);
CREATE TABLE motd (

View File

@ -302,6 +302,7 @@ CREATE TABLE muc_room_subscribers (
);
CREATE INDEX i_muc_room_subscribers_host_jid ON muc_room_subscribers(host, jid);
CREATE INDEX i_muc_room_subscribers_jid ON muc_room_subscribers(jid);
CREATE UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers(host, room, jid);
CREATE TABLE motd (

View File

@ -150,6 +150,7 @@ CREATE TABLE [dbo].[muc_room_subscribers] (
CREATE UNIQUE CLUSTERED INDEX [muc_room_subscribers_host_room_jid] ON [muc_room_subscribers] (host, room, jid);
CREATE INDEX [muc_room_subscribers_host_jid] ON [muc_room_subscribers] (host, jid);
CREATE INDEX [muc_room_subscribers_jid] ON [muc_room_subscribers] (jid);
CREATE TABLE [dbo].[privacy_default_list] (
[username] [varchar] (250) NOT NULL,

View File

@ -347,6 +347,7 @@ CREATE TABLE muc_room_subscribers (
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_muc_room_subscribers_host_jid USING BTREE ON muc_room_subscribers(host, jid);
CREATE INDEX i_muc_room_subscribers_jid USING BTREE ON muc_room_subscribers(jid);
CREATE TABLE motd (
username varchar(191) NOT NULL,

View File

@ -77,6 +77,7 @@ BEGIN
ALTER TABLE `last` ADD PRIMARY KEY (`server_host`, `username`);
ALTER TABLE `sr_group` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `name`;
ALTER TABLE `sr_group` ALTER COLUMN `server_host` DROP DEFAULT;
ALTER TABLE `sr_group` ADD UNIQUE INDEX `i_sr_group_sh_name` (`server_host`, `name`);
ALTER TABLE `sr_group` ADD PRIMARY KEY (`server_host`, `name`);
ALTER TABLE `muc_registered` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `host`;
ALTER TABLE `muc_registered` ALTER COLUMN `server_host` DROP DEFAULT;
@ -99,6 +100,7 @@ BEGIN
ALTER TABLE `sr_user` DROP INDEX `i_sr_user_jid_group`;
ALTER TABLE `sr_user` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `jid`;
ALTER TABLE `sr_user` ALTER COLUMN `server_host` DROP DEFAULT;
ALTER TABLE `sr_user` ADD UNIQUE INDEX `i_sr_user_sh_jid_group` (`server_host`, `jid`, `grp`);
ALTER TABLE `sr_user` ADD INDEX `i_sr_user_sh_jid` (`server_host`, `jid`);
ALTER TABLE `sr_user` ADD INDEX `i_sr_user_sh_grp` (`server_host`, `grp`);
ALTER TABLE `sr_user` ADD PRIMARY KEY (`server_host`, `jid`, `grp`);

View File

@ -319,6 +319,7 @@ CREATE TABLE muc_room_subscribers (
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX i_muc_room_subscribers_host_jid USING BTREE ON muc_room_subscribers(host, jid);
CREATE INDEX i_muc_room_subscribers_jid USING BTREE ON muc_room_subscribers(jid);
CREATE TABLE motd (
username varchar(191) PRIMARY KEY,

View File

@ -311,7 +311,7 @@ CREATE TABLE vcard_search (
lorgname text NOT NULL,
orgunit text NOT NULL,
lorgunit text NOT NULL,
PRIMARY KEY (server_host, username)
PRIMARY KEY (server_host, lusername)
);
CREATE INDEX i_vcard_search_sh_lfn ON vcard_search(server_host, lfn);
@ -495,6 +495,7 @@ CREATE TABLE muc_room_subscribers (
);
CREATE INDEX i_muc_room_subscribers_host_jid ON muc_room_subscribers USING btree (host, jid);
CREATE INDEX i_muc_room_subscribers_jid ON muc_room_subscribers USING btree (jid);
CREATE UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers USING btree (host, room, jid);
CREATE TABLE motd (

View File

@ -320,6 +320,7 @@ CREATE TABLE muc_room_subscribers (
);
CREATE INDEX i_muc_room_subscribers_host_jid ON muc_room_subscribers USING btree (host, jid);
CREATE INDEX i_muc_room_subscribers_jid ON muc_room_subscribers USING btree (jid);
CREATE UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers USING btree (host, room, jid);
CREATE TABLE motd (

View File

@ -120,7 +120,10 @@ get_commands_spec() ->
module = init, function = restart,
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs],
desc = "Reopen the log files",
desc = "Reopen the log files after being renamed",
longdesc = "This can be useful when an external tool is "
"used for log rotation. See "
"https://docs.ejabberd.im/admin/guide/troubleshooting/#log-files",
policy = admin,
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
@ -178,6 +181,8 @@ get_commands_spec() ->
result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user",
longdesc = "This deletes the authentication and all the "
"data associated to the account (roster, vcard...).",
policy = admin,
module = ?MODULE, function = unregister,
args_desc = ["Username", "Local vhost served by ejabberd"],
@ -345,31 +350,41 @@ get_commands_spec() ->
{oldbackup, string}, {newbackup, string}],
result = {res, restuple}},
#ejabberd_commands{name = backup, tags = [mnesia],
desc = "Store the database to backup file",
desc = "Backup the Mnesia database to a binary file",
module = ?MODULE, function = backup_mnesia,
args_desc = ["Full path for the destination backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = restore, tags = [mnesia],
desc = "Restore the database from backup file",
desc = "Restore the Mnesia database from a binary backup file",
longdesc = "This restores immediately from a "
"binary backup file the internal Mnesia "
"database. This will consume a lot of memory if "
"you have a large database, you may prefer "
"'install_fallback'.",
module = ?MODULE, function = restore_mnesia,
args_desc = ["Full path to the backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump, tags = [mnesia],
desc = "Dump the database to a text file",
desc = "Dump the Mnesia database to a text file",
module = ?MODULE, function = dump_mnesia,
args_desc = ["Full path for the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump_table, tags = [mnesia],
desc = "Dump a table to a text file",
desc = "Dump a Mnesia table to a text file",
module = ?MODULE, function = dump_table,
args_desc = ["Full path for the text file", "Table name"],
args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
args = [{file, string}, {table, string}], result = {res, restuple}},
#ejabberd_commands{name = load, tags = [mnesia],
desc = "Restore the database from a text file",
desc = "Restore Mnesia database from a text dump file",
longdesc = "Restore immediately. This is not "
"recommended for big databases, as it will "
"consume much time, memory and processor. In "
"that case it's preferable to use 'backup' and "
"'install_fallback'.",
module = ?MODULE, function = load_mnesia,
args_desc = ["Full path to the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
@ -385,7 +400,14 @@ get_commands_spec() ->
args_example = ["roster"],
args = [{table, string}], result = {res, string}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
desc = "Install Mnesia database from a binary backup file",
longdesc = "The binary backup file is "
"installed as fallback: it will be used to "
"restore the database at the next ejabberd "
"start. This means that, after running this "
"command, you have to restart ejabberd. This "
"command requires less memory than
'restore'.",
module = ?MODULE, function = install_fallback_mnesia,
args_desc = ["Full path to the fallback file"],
args_example = ["/var/lib/ejabberd/database.fallback"],

View File

@ -299,6 +299,20 @@ export(_Server) ->
["username=%(LUser)s",
"server_host=%(LServer)s",
"password=%(Password)s"])];
(Host, {passwd, {LUser, LServer},
{scram, StoredKey1, ServerKey, Salt, IterationCount}})
when LServer == Host ->
Hash = sha,
StoredKey = scram_hash_encode(Hash, StoredKey1),
[?SQL("delete from users where username=%(LUser)s and %(LServer)H;"),
?SQL_INSERT(
"users",
["username=%(LUser)s",
"server_host=%(LServer)s",
"password=%(StoredKey)s",
"serverkey=%(ServerKey)s",
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"])];
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
when LServer == Host ->
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),

View File

@ -94,6 +94,7 @@ get_commands_spec() ->
result_example = ok},
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation],
desc = "Generates markdown documentation for ejabberd_commands",
note = "added in 21.12",
module = ejabberd_commands_doc, function = generate_tags_md,
args = [{file, binary}],
result = {res, rescode},

View File

@ -378,7 +378,11 @@ format_arg("", string) ->
format_arg(Arg, string) ->
NumChars = integer_to_list(length(Arg)),
Parse = "~" ++ NumChars ++ "c",
format_arg2(Arg, Parse).
format_arg2(Arg, Parse);
format_arg(Arg, Format) ->
S = unicode:characters_to_binary(Arg, utf8),
JSON = jiffy:decode(S),
mod_http_api:format_arg(JSON, Format).
format_arg2(Arg, Parse)->
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
@ -525,6 +529,7 @@ print_usage(Version) ->
print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands =
[
{"help", ["[arguments]"], "Get help"},
{"status", [], "Get ejabberd status"},
{"stop", [], "Stop ejabberd"},
{"restart", [], "Restart ejabberd"},

View File

@ -24,17 +24,15 @@
%%%----------------------------------------------------------------------
%%% Not implemented:
%%% - PEP nodes export/import
%%% - message archives export/import
%%% - write mod_piefxis with ejabberdctl commands
%%% - Export from mod_offline_sql.erl
%%% - Export from mod_private_sql.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
%%%% Headers
-module(ejabberd_piefxis).
-protocol({xep, 227, '1.0'}).
-protocol({xep, 227, '1.1'}).
-export([import_file/1, export_server/1, export_host/2]).
@ -166,33 +164,66 @@ export_users([], _Server, _Fd) ->
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
Pass = case ejabberd_auth:password_format(LServer) of
scram -> format_scram_password(Password);
_ -> Password
{PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of
scram -> {[], [format_scram_password(Password)]};
_ -> {[{<<"password">>, Password}], []}
end,
Els = get_offline(User, Server) ++
Els =
PassScram ++
get_offline(User, Server) ++
get_vcard(User, Server) ++
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User},
{<<"password">>, Pass}],
attrs = [{<<"name">>, User} | PassPlain],
children = Els})).
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
salt = Salt, iterationcount = IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = (integer_to_binary(IterationCount)),
Hash2 = case Hash of
sha -> <<>>;
sha256 -> <<"sha256,">>;
sha512 -> <<"sha512,">>
end,
<<"scram:", Hash2/binary, StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = (integer_to_binary(IterationCount)),
MechanismB = case Hash of
sha -> <<"SCRAM-SHA-1">>;
sha256 -> <<"SCRAM-SHA-256">>;
sha512 -> <<"SCRAM-SHA-512">>
end,
Children =
[
#xmlel{name = <<"iter-count">>,
children = [{xmlcdata, IterationCountBin}]},
#xmlel{name = <<"salt">>,
children = [{xmlcdata, SaltB64}]},
#xmlel{name = <<"server-key">>,
children = [{xmlcdata, ServerKeyB64}]},
#xmlel{name = <<"stored-key">>,
children = [{xmlcdata, StoredKeyB64}]}
],
#xmlel{name = <<"scram-credentials">>,
attrs = [{<<"xmlns">>, <<?NS_PIE/binary, "#scram">>},
{<<"mechanism">>, MechanismB}],
children = Children}.
parse_scram_password(#xmlel{attrs = Attrs} = El) ->
Hash = case fxml:get_attr_s(<<"mechanism">>, Attrs) of
<<"SCRAM-SHA-1">> -> sha;
<<"SCRAM-SHA-256">> -> sha256;
<<"SCRAM-SHA-512">> -> sha512
end,
StoredKeyB64 = fxml:get_path_s(El, [{elem, <<"stored-key">>}, cdata]),
ServerKeyB64 = fxml:get_path_s(El, [{elem, <<"server-key">>}, cdata]),
IterationCountBin = fxml:get_path_s(El, [{elem, <<"iter-count">>}, cdata]),
SaltB64 = fxml:get_path_s(El, [{elem, <<"salt">>}, cdata]),
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
};
parse_scram_password(PassData) ->
Split = binary:split(PassData, <<",">>, [global]),
@ -214,26 +245,30 @@ parse_scram_password(PassData) ->
get_vcard(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_vcard:get_vcard(LUser, LServer) of
try mod_vcard:get_vcard(LUser, LServer) of
error -> [];
Els -> Els
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_offline:get_offline_els(LUser, LServer) of
try mod_offline:get_offline_els(LUser, LServer) of
[] ->
[];
Els ->
NewEls = lists:map(fun xmpp:encode/1, Els),
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
try mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
lists = [_|_] = Lists}} ->
XLists = lists:map(
@ -246,12 +281,14 @@ get_privacy(User, Server) ->
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) ->
JID = jid:make(User, Server),
case mod_roster:get_roster(User, Server) of
try mod_roster:get_roster(User, Server) of
[_|_] = Items ->
Subs =
lists:flatmap(
@ -278,15 +315,19 @@ get_roster(User, Server) ->
[xmpp:encode(#roster_query{items = Rs}) | Subs];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) ->
case mod_private:get_data(User, Server) of
try mod_private:get_data(User, Server) of
[_|_] = Els ->
[xmpp:encode(#private{sub_els = Els})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
@ -398,21 +439,10 @@ process_users([_|Els], State) ->
process_users([], State) ->
{ok, State}.
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
#state{server = LServer} = State) ->
Name = fxml:get_attr_s(<<"name">>, Attrs),
Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_auth:password_format(LServer),
Pass = case PasswordFormat of
scram ->
case Password of
<<"scram:", PassData/binary>> ->
parse_scram_password(PassData);
P -> P
end;
_ -> Password
end,
Pass = process_password(El, LServer),
case jid:nodeprep(Name) of
error ->
stop("Invalid 'user': ~ts", [Name]);
@ -420,13 +450,29 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
case ejabberd_auth:try_register(LUser, LServer, Pass) of
ok ->
process_user_els(Els, State#state{user = LUser});
{error, invalid_password} when (Password == <<>>) ->
{error, invalid_password} when (Pass == <<>>) ->
process_user_els(Els, State#state{user = LUser});
{error, Err} ->
stop("Failed to create user '~ts': ~p", [Name, Err])
end
end.
process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) ->
{PassPlain, PassOldScram} = case fxml:get_attr_s(<<"password">>, Attrs) of
<<"scram:", PassData/binary>> -> {<<"">>, PassData};
P -> {P, false}
end,
ScramCred = fxml:get_subtag(El, <<"scram-credentials">>),
PasswordFormat = ejabberd_auth:password_format(LServer),
case {PassPlain, PassOldScram, ScramCred, PasswordFormat} of
{PassPlain, false, false, plain} -> PassPlain;
{<<"">>, false, ScramCred, plain} -> parse_scram_password(ScramCred);
{<<"">>, PassOldScram, false, plain} -> parse_scram_password(PassOldScram);
{PassPlain, false, false, scram} -> PassPlain;
{<<"">>, false, ScramCred, scram} -> parse_scram_password(ScramCred);
{<<"">>, PassOldScram, false, scram} -> parse_scram_password(PassOldScram)
end.
process_user_els([#xmlel{} = El|Els], State) ->
case process_user_el(El, State) of
{ok, NewState} ->

View File

@ -33,8 +33,8 @@
%% API
-export([start_link/0, stop/0, route/1, have_connection/1,
get_connections_pids/1, try_register/1,
remove_connection/2, start_connection/2, start_connection/3,
get_connections_pids/1,
start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
stop_s2s_connections/0,
@ -64,7 +64,7 @@
%% once a server is temporary blocked, it stay blocked for 60 seconds
-record(s2s, {fromto :: {binary(), binary()},
-record(s2s, {fromto :: {binary(), binary()} | '_',
pid :: pid()}).
-record(state, {}).
@ -112,24 +112,6 @@ is_temporarly_blocked(Host) ->
end
end.
-spec remove_connection({binary(), binary()}, pid()) -> ok.
remove_connection({From, To} = FromTo, Pid) ->
case mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, pid = Pid}) of
[#s2s{pid = Pid}] ->
F = fun() ->
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed to unregister s2s connection ~ts -> ~ts: "
"Mnesia failure: ~p",
[From, To, Reason])
end;
_ ->
ok
end.
-spec have_connection({binary(), binary()}) -> boolean().
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
@ -148,31 +130,6 @@ get_connections_pids(FromTo) ->
[]
end.
-spec try_register({binary(), binary()}) -> boolean().
try_register({From, To} = FromTo) ->
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
F = fun () ->
L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
mnesia:write(#s2s{fromto = FromTo, pid = self()}),
true;
true -> false
end
end,
case mnesia:transaction(F) of
{atomic, Res} -> Res;
{aborted, Reason} ->
?ERROR_MSG("Failed to register s2s connection ~ts -> ~ts: "
"Mnesia failure: ~p",
[From, To, Reason]),
false
end.
-spec dirty_get_connections() -> [{binary(), binary()}].
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
@ -269,6 +226,8 @@ init([]) ->
{stop, Reason}
end.
handle_call({new_connection, Args}, _From, State) ->
{reply, erlang:apply(fun new_connection_int/7, Args), State};
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
@ -289,6 +248,21 @@ handle_info({route, Packet}, State) ->
misc:format_exception(2, Class, Reason, StackTrace)])
end,
{noreply, State};
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
case mnesia:dirty_match_object(s2s, #s2s{fromto = '_', pid = Pid}) of
[#s2s{pid = Pid, fromto = {From, To}} = Obj] ->
F = fun() -> mnesia:delete_object(Obj) end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed to unregister s2s connection for pid ~p (~ts -> ~ts):"
"Mnesia failure: ~p",
[Pid, From, To, Reason])
end,
{noreply, State};
_ ->
{noreply, State}
end;
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
@ -458,6 +432,18 @@ open_several_connections(N, MyServer, Server, From,
integer(), integer(), [proplists:property()]) -> [pid()].
new_connection(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
case whereis(ejabberd_s2s) == self() of
true ->
new_connection_int(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts);
false ->
gen_server:call(ejabberd_s2s, {new_connection, [MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts]})
end.
new_connection_int(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
{ok, Pid} = ejabberd_s2s_out:start(MyServer, Server, Opts),
F = fun() ->
L = mnesia:read({s2s, FromTo}),
@ -474,6 +460,7 @@ new_connection(MyServer, Server, From, FromTo,
case TRes of
{atomic, Pid1} ->
if Pid1 == Pid ->
erlang:monitor(process, Pid),
ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop_async(Pid)

View File

@ -318,7 +318,6 @@ handle_info(Info, #{server_host := ServerHost} = State) ->
terminate(Reason, #{server := LServer,
remote_server := RServer} = State) ->
ejabberd_s2s:remove_connection({LServer, RServer}, self()),
State1 = case Reason of
normal -> State;
_ -> State#{stop_reason => internal_failure}
@ -351,21 +350,12 @@ bounce_queue(State) ->
end, State).
-spec bounce_message_queue({binary(), binary()}, state()) -> state().
bounce_message_queue({LServer, RServer} = FromTo, State) ->
Pids = ejabberd_s2s:get_connections_pids(FromTo),
case lists:member(self(), Pids) of
true ->
?WARNING_MSG("Outgoing s2s connection ~ts -> ~ts is supposed "
"to be unregistered, but pid ~p still presents "
"in 's2s' table", [LServer, RServer, self()]),
State;
false ->
receive {route, Pkt} ->
State1 = bounce_packet(Pkt, State),
bounce_message_queue(FromTo, State1)
after 0 ->
State
end
bounce_message_queue(FromTo, State) ->
receive {route, Pkt} ->
State1 = bounce_packet(Pkt, State),
bounce_message_queue(FromTo, State1)
after 0 ->
State
end.
-spec bounce_packet(xmpp_element(), state()) -> state().

View File

@ -564,15 +564,23 @@ make_sql_upsert(Table, ParseRes, Pos) ->
[]
end,
erl_syntax:fun_expr(
[erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90100))],
[make_sql_upsert_pgsql901(Table, ParseRes),
erl_syntax:atom(ok)])] ++
MySqlReplace ++
[erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90500))],
[make_sql_upsert_pgsql905(Table, ParseRes),
erl_syntax:atom(ok)]),
erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90100))],
[make_sql_upsert_pgsql901(Table, ParseRes),
erl_syntax:atom(ok)])] ++
MySqlReplace ++
[erl_syntax:clause(
[erl_syntax:underscore(), erl_syntax:underscore()],
none,
@ -713,6 +721,57 @@ make_sql_upsert_pgsql901(Table, ParseRes0) ->
erl_syntax:atom(sql_query_t),
[Upsert]).
make_sql_upsert_pgsql905(Table, ParseRes0) ->
ParseRes = lists:map(
fun({"family", A2, A3}) -> {"\"family\"", A2, A3};
(Other) -> Other
end, ParseRes0),
Vals =
lists:map(
fun({_Field, _, ST}) ->
ST
end, ParseRes),
Fields =
lists:map(
fun({Field, _, _ST}) ->
#state{'query' = [{str, Field}]}
end, ParseRes),
SPairs =
lists:flatmap(
fun({_Field, key, _ST}) ->
[];
({_Field, {false}, _ST}) ->
[];
({Field, {true}, ST}) ->
[ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
}]
end, ParseRes),
Set = join_states(SPairs, ", "),
KeyFields =
lists:flatmap(
fun({Field, key, _ST}) ->
[#state{'query' = [{str, Field}]}];
({_Field, _, _ST}) ->
[]
end, ParseRes),
State =
concat_states(
[#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
join_states(Fields, ", "),
#state{'query' = [{str, ") VALUES ("}]},
join_states(Vals, ", "),
#state{'query' = [{str, ") ON CONFLICT ("}]},
join_states(KeyFields, ", "),
#state{'query' = [{str, ") DO UPDATE SET "}]},
Set
]),
Upsert = make_sql_query(State),
erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Upsert]).
check_upsert(ParseRes, Pos) ->
Set =

View File

@ -73,11 +73,16 @@ export(Server, Output) ->
end, Modules),
close_output(Output, IO).
export(Server, Output, Module1) ->
Module = case Module1 of
mod_pubsub -> pubsub_db;
_ -> Module1
end,
export(Server, Output, mod_mam = M1) ->
MucServices = gen_mod:get_module_opt_hosts(Server, mod_muc),
[export2(MucService, Output, M1, M1) || MucService <- MucServices],
export2(Server, Output, M1, M1);
export(Server, Output, mod_pubsub = M1) ->
export2(Server, Output, M1, pubsub_db);
export(Server, Output, M1) ->
export2(Server, Output, M1, M1).
export2(Server, Output, Module1, Module) ->
SQLMod = gen_mod:db_mod(sql, Module),
LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output),

View File

@ -133,6 +133,10 @@
{result, {[itemId()], [itemId()]}
}.
-callback remove_expired_items(NodeIdx :: nodeIdx(),
Seconds :: infinity | non_neg_integer()) ->
{result, [itemId()]}.
-callback get_node_affiliations(NodeIdx :: nodeIdx()) ->
{result, [{ljid(), affiliation()}]}.

View File

@ -140,6 +140,7 @@ update_tables(State) ->
add_sh_column(State, "sr_group"),
add_pkey(State, "sr_group", ["server_host", "name"]),
create_unique_index(State, "sr_group", "i_sr_group_sh_name", ["server_host", "name"]),
drop_sh_default(State, "sr_group"),
add_sh_column(State, "sr_user"),
@ -147,6 +148,7 @@ update_tables(State) ->
drop_index(State, "i_sr_user_jid"),
drop_index(State, "i_sr_user_grp"),
add_pkey(State, "sr_user", ["server_host", "jid", "grp"]),
create_unique_index(State, "sr_user", "i_sr_user_sh_jid_grp", ["server_host", "jid", "grp"]),
create_index(State, "sr_user", "i_sr_user_sh_jid", ["server_host", "jid"]),
create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
drop_sh_default(State, "sr_user"),

View File

@ -49,7 +49,8 @@
handle_cast/2, terminate/2, code_change/3]).
-export([user_send_packet/1, user_receive_packet/1,
c2s_presence_in/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
c2s_presence_in/2, c2s_copy_session/2,
mod_opt_type/1, mod_options/1, mod_doc/0]).
-include("logger.hrl").
@ -274,6 +275,13 @@ c2s_presence_in(C2SState,
C2SState#{caps_resources => NewRs}
end.
-spec c2s_copy_session(ejabberd_c2s:state(), ejabberd_c2s:state())
-> ejabberd_c2s:state().
c2s_copy_session(C2SState, #{caps_resources := Rs}) ->
C2SState#{caps_resources => Rs};
c2s_copy_session(C2SState, _) ->
C2SState.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
@ -304,6 +312,8 @@ init([Host|_]) ->
caps_stream_features, 75),
ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE,
caps_stream_features, 75),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 75),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_features, 75),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
@ -341,6 +351,8 @@ terminate(_Reason, State) ->
?MODULE, caps_stream_features, 75),
ejabberd_hooks:delete(s2s_in_post_auth_features, Host,
?MODULE, caps_stream_features, 75),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 75),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, disco_features, 75),
ejabberd_hooks:delete(disco_local_identity, Host,

157
src/mod_conversejs.erl Normal file
View File

@ -0,0 +1,157 @@
%%%----------------------------------------------------------------------
%%% File : mod_conversejs.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Serve simple page for Converse.js XMPP web browser client
%%% Created : 8 Nov 2021 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2021 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_conversejs).
-author('alexey@process-one.net').
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process/2, depends/2,
mod_opt_type/1, mod_options/1, mod_doc/0]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
-include("translate.hrl").
-include("ejabberd_web_admin.hrl").
start(_Host, _Opts) ->
ok.
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
process([], #request{method = 'GET'}) ->
Host = ejabberd_config:get_myname(),
Domain = gen_mod:get_module_opt(Host, ?MODULE, default_domain),
Script = gen_mod:get_module_opt(Host, ?MODULE, conversejs_script),
CSS = gen_mod:get_module_opt(Host, ?MODULE, conversejs_css),
Init = [{<<"discover_connection_methods">>, false},
{<<"jid">>, Domain},
{<<"default_domain">>, Domain},
{<<"domain_placeholder">>, Domain},
{<<"view_mode">>, <<"fullscreen">>}],
Init2 =
case gen_mod:get_module_opt(Host, ?MODULE, websocket_url) of
undefined -> Init;
WSURL -> [{<<"websocket_url">>, WSURL} | Init]
end,
Init3 =
case gen_mod:get_module_opt(Host, ?MODULE, bosh_service_url) of
undefined -> Init2;
BoshURL -> [{<<"bosh_service_url">>, BoshURL} | Init2]
end,
{200, [html],
[<<"<!DOCTYPE html>">>,
<<"<html>">>,
<<"<head>">>,
<<"<meta charset='utf-8'>">>,
<<"<link rel='stylesheet' type='text/css' media='screen' href='">>,
fxml:crypt(CSS), <<"'>">>,
<<"<script src='">>, fxml:crypt(Script), <<"' charset='utf-8'></script>">>,
<<"</head>">>,
<<"<body>">>,
<<"<script>">>,
<<"converse.initialize(">>, jiffy:encode({Init3}), <<");">>,
<<"</script>">>,
<<"</body>">>,
<<"</html>">>]};
process(_, _) ->
ejabberd_web:error(not_found).
mod_opt_type(bosh_service_url) ->
econf:either(undefined, econf:binary());
mod_opt_type(websocket_url) ->
econf:either(undefined, econf:binary());
mod_opt_type(conversejs_script) ->
econf:binary();
mod_opt_type(conversejs_css) ->
econf:binary();
mod_opt_type(default_domain) ->
econf:binary().
mod_options(_) ->
[{bosh_service_url, undefined},
{websocket_url, undefined},
{default_domain, ejabberd_config:get_myname()},
{conversejs_script, <<"https://cdn.conversejs.org/dist/converse.min.js">>},
{conversejs_css, <<"https://cdn.conversejs.org/dist/converse.min.css">>}].
mod_doc() ->
#{desc =>
[?T("This module serves a simple page for the "
"https://conversejs.org/[Converse] XMPP web browser client."), "",
?T("This module is available since ejabberd 21.12."), "",
?T("To use this module, in addition to adding it to the 'modules' "
"section, you must also enable it in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers]."), "",
?T("You must also setup either the option 'websocket_url' or 'bosh_service_url'."), "",
?T("By default, the options 'conversejs_css' and 'conversejs_script'"
" point to the public Converse.js client. Alternatively, you can"
" host the client locally using _`mod_http_fileserver`_.")
],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /websocket: ejabberd_http_ws",
" /conversejs: mod_conversejs",
"",
"modules:",
" mod_conversejs:",
" websocket_url: \"ws://example.org:5280/websocket\""],
opts =>
[{websocket_url,
#{value => ?T("WebsocketURL"),
desc =>
?T("A websocket URL to which Converse.js can connect to.")}},
{bosh_service_url,
#{value => ?T("BoshURL"),
desc =>
?T("BOSH service URL to which Converse.js can connect to.")}},
{default_domain,
#{value => ?T("Domain"),
desc =>
?T("Specify a domain to act as the default for user JIDs. "
"The default value is the first domain defined in the "
"ejabberd configuration file.")}},
{conversejs_script,
#{value => ?T("URL"),
desc =>
?T("Converse.js main script URL.")}},
{conversejs_css,
#{value => ?T("URL"),
desc =>
?T("Converse.js CSS URL.")}}]
}.

View File

@ -0,0 +1,41 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_conversejs_opt).
-export([bosh_service_url/1]).
-export([conversejs_css/1]).
-export([conversejs_script/1]).
-export([default_domain/1]).
-export([websocket_url/1]).
-spec bosh_service_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
bosh_service_url(Opts) when is_map(Opts) ->
gen_mod:get_opt(bosh_service_url, Opts);
bosh_service_url(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, bosh_service_url).
-spec conversejs_css(gen_mod:opts() | global | binary()) -> binary().
conversejs_css(Opts) when is_map(Opts) ->
gen_mod:get_opt(conversejs_css, Opts);
conversejs_css(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_css).
-spec conversejs_script(gen_mod:opts() | global | binary()) -> binary().
conversejs_script(Opts) when is_map(Opts) ->
gen_mod:get_opt(conversejs_script, Opts);
conversejs_script(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_script).
-spec default_domain(gen_mod:opts() | global | binary()) -> binary().
default_domain(Opts) when is_map(Opts) ->
gen_mod:get_opt(default_domain, Opts);
default_domain(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, default_domain).
-spec websocket_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
websocket_url(Opts) when is_map(Opts) ->
gen_mod:get_opt(websocket_url, Opts);
websocket_url(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, websocket_url).

View File

@ -30,6 +30,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process/2, depends/2,
format_arg/2,
mod_options/1, mod_doc/0]).
-include_lib("xmpp/include/xmpp.hrl").

View File

@ -28,6 +28,7 @@
-protocol({xep, 313, '0.6.1'}).
-protocol({xep, 334, '0.2'}).
-protocol({xep, 359, '0.5.0'}).
-protocol({xep, 441, '0.2.0'}).
-behaviour(gen_mod).

View File

@ -1134,8 +1134,8 @@ is_expired(#publish{meta = Meta, properties = Props} = Pkt) ->
%%% Authentication
%%%===================================================================
-spec parse_credentials(connect()) -> {ok, jid:jid()} | {error, reason_code()}.
parse_credentials(#connect{client_id = <<>>}) ->
parse_credentials(#connect{client_id = p1_rand:get_string()});
parse_credentials(#connect{client_id = <<>>} = C) ->
parse_credentials(C#connect{client_id = p1_rand:get_string()});
parse_credentials(#connect{username = <<>>, client_id = ClientID}) ->
Host = ejabberd_config:get_myname(),
JID = case jid:make(ClientID, Host) of

View File

@ -69,6 +69,7 @@
get_online_rooms_by_user/3,
can_use_nick/4,
get_subscribed_rooms/2,
remove_user/2,
procname/2,
route/1, unhibernate_room/3]).
@ -122,6 +123,8 @@
start(Host, Opts) ->
case mod_muc_sup:start(Host) of
{ok, _} ->
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
MyHosts = gen_mod:get_opt_hosts(Opts),
Mod = gen_mod:db_mod(Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
@ -133,6 +136,8 @@ start(Host, Opts) ->
end.
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
Proc = mod_muc_sup:procname(Host),
supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
@ -1122,6 +1127,32 @@ count_online_rooms(ServerHost, Host) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:count_online_rooms(ServerHost, Host).
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
case erlang:function_exported(Mod, remove_user, 2) of
true ->
Mod:remove_user(LUser, LServer);
false ->
ok
end,
JID = jid:make(User, Server),
lists:foreach(
fun(Host) ->
lists:foreach(
fun({_, _, Pid}) ->
mod_muc_room:change_item_async(
Pid, JID, affiliation, none, <<"User removed">>),
mod_muc_room:change_item_async(
Pid, JID, role, none, <<"User removed">>)
end,
get_online_rooms(LServer, Host))
end,
gen_mod:get_module_opt_hosts(LServer, mod_muc)),
ok.
opts_to_binary(Opts) ->
lists:map(
fun({title, Title}) ->
@ -1225,6 +1256,8 @@ mod_opt_type(user_message_shaper) ->
econf:atom();
mod_opt_type(user_presence_shaper) ->
econf:atom();
mod_opt_type(cleanup_affiliations_on_start) ->
econf:bool();
mod_opt_type(default_room_options) ->
econf:options(
#{allow_change_subj => econf:bool(),
@ -1302,6 +1335,7 @@ mod_options(Host) ->
{preload_rooms, true},
{hibernation_timeout, infinity},
{vcard, undefined},
{cleanup_affiliations_on_start, false},
{default_room_options,
[{allow_change_subj,true},
{allow_private_messages,true},
@ -1580,6 +1614,11 @@ mod_doc() ->
" -",
" work: true",
" street: Elm Street"]}]}},
{cleanup_affiliations_on_start,
#{value => "true | false",
desc =>
?T("Remove affiliations for non-existing local users on startup. "
"The default value is 'false'.")}},
{default_room_options,
#{value => ?T("Options"),
desc =>

View File

@ -40,8 +40,11 @@
change_room_option/4, get_room_options/2,
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]).
subscribe_room/4, subscribe_room_many/3,
unsubscribe_room/2, get_subscribers/2,
web_page_host/3,
mod_opt_type/1, mod_options/1,
get_commands_spec/0, find_hosts/1]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
@ -281,7 +284,7 @@ get_commands_spec() ->
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
desc = "Send a direct invitation to several destinations",
longdesc = "Since ejabberd 20.10, this command is "
longdesc = "Since ejabberd 20.12, this command is "
"asynchronous: the API call may return before the "
"server has send all the invitations.\n\n"
"Password and Message can also be: none. "
@ -331,6 +334,26 @@ get_commands_spec() ->
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = subscribe_room_many, tags = [muc_room],
desc = "Subscribe several users to a MUC conference",
longdesc = "This command accept up to 50 users at once (this is configurable with `subscribe_room_many_max_users` option)",
module = ?MODULE, function = subscribe_room_many,
args_desc = ["Users JIDs and nicks",
"the room to subscribe",
"nodes separated by commas: ,"],
args_example = [[{"tom@localhost", "Tom"},
{"jerry@localhost", "Jerry"}],
"room1@conference.localhost",
"urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
args = [{users, {list,
{user, {tuple,
[{jid, binary},
{nick, binary}
]}}
}},
{room, binary},
{nodes, binary}],
result = {res, rescode}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room,
@ -710,7 +733,7 @@ create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
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),
{atomic, _} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
ok;
false ->
ok
@ -860,7 +883,14 @@ get_online_rooms(ServiceArg) ->
|| {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
end, Hosts).
get_all_rooms(Host) ->
get_all_rooms(ServiceArg) ->
Hosts = find_services(ServiceArg),
lists:flatmap(
fun(Host) ->
get_all_rooms2(Host)
end, Hosts).
get_all_rooms2(Host) ->
ServerHost = ejabberd_router:host_of_route(Host),
OnlineRooms = get_online_rooms(Host),
OnlineMap = lists:foldl(
@ -1324,6 +1354,18 @@ subscribe_room(User, Nick, Room, Nodes) ->
throw({error, "Malformed room JID"})
end.
subscribe_room_many(Users, Room, Nodes) ->
MaxUsers = mod_muc_admin_opt:subscribe_room_many_max_users(global),
if
length(Users) > MaxUsers ->
throw({error, "Too many users in subscribe_room_many command"});
true ->
lists:foreach(
fun({User, Nick}) ->
subscribe_room(User, Nick, Room, Nodes)
end, Users)
end.
unsubscribe_room(User, Room) ->
try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
@ -1406,11 +1448,22 @@ find_hosts(ServerHost) ->
[]
end.
mod_options(_) -> [].
mod_opt_type(subscribe_room_many_max_users) ->
econf:int().
mod_options(_) ->
[{subscribe_room_many_max_users, 50}].
mod_doc() ->
#{desc =>
[?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`_.")],
opts =>
[{subscribe_room_many_max_users,
#{value => ?T("Number"),
desc =>
?T("How many users can be subscribed to a room at once using "
"the 'subscribe_room_many' command. "
"The default value is '50'.")}}]}.

13
src/mod_muc_admin_opt.erl Normal file
View File

@ -0,0 +1,13 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_muc_admin_opt).
-export([subscribe_room_many_max_users/1]).
-spec subscribe_room_many_max_users(gen_mod:opts() | global | binary()) -> integer().
subscribe_room_many_max_users(Opts) when is_map(Opts) ->
gen_mod:get_opt(subscribe_room_many_max_users, Opts);
subscribe_room_many_max_users(Host) ->
gen_mod:get_module_opt(Host, mod_muc_admin, subscribe_room_many_max_users).

View File

@ -9,6 +9,7 @@
-export([access_mam/1]).
-export([access_persistent/1]).
-export([access_register/1]).
-export([cleanup_affiliations_on_start/1]).
-export([db_type/1]).
-export([default_room_options/1]).
-export([hibernation_timeout/1]).
@ -73,6 +74,12 @@ access_register(Opts) when is_map(Opts) ->
access_register(Host) ->
gen_mod:get_module_opt(Host, mod_muc, access_register).
-spec cleanup_affiliations_on_start(gen_mod:opts() | global | binary()) -> boolean().
cleanup_affiliations_on_start(Opts) when is_map(Opts) ->
gen_mod:get_opt(cleanup_affiliations_on_start, Opts);
cleanup_affiliations_on_start(Host) ->
gen_mod:get_module_opt(Host, mod_muc, cleanup_affiliations_on_start).
-spec db_type(gen_mod:opts() | global | binary()) -> atom().
db_type(Opts) when is_map(Opts) ->
gen_mod:get_opt(db_type, Opts);

View File

@ -27,6 +27,8 @@
-author('alexey@process-one.net').
-protocol({xep, 317, '0.1'}).
-behaviour(p1_fsm).
%% External exports
@ -48,6 +50,7 @@
set_config/2,
get_state/1,
change_item/5,
change_item_async/5,
config_reloaded/1,
subscribe/4,
unsubscribe/2,
@ -76,6 +79,12 @@
-define(DEFAULT_MAX_USERS_PRESENCE,1000).
-define(MUC_HAT_ADD_CMD, <<"http://prosody.im/protocol/hats#add">>).
-define(MUC_HAT_REMOVE_CMD, <<"http://prosody.im/protocol/hats#remove">>).
-define(MUC_HAT_LIST_CMD, <<"p1:hats#list">>).
-define(MAX_HATS_USERS, 100).
-define(MAX_HATS_PER_USER, 10).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
@ -194,6 +203,11 @@ change_item(Pid, JID, Type, AffiliationOrRole, Reason) ->
{error, notfound}
end.
-spec change_item_async(pid(), jid(), affiliation | role, affiliation() | role(), binary()) -> ok.
change_item_async(Pid, JID, Type, AffiliationOrRole, Reason) ->
p1_fsm:send_all_state_event(
Pid, {process_item_change, {JID, Type, AffiliationOrRole, Reason}, undefined}).
-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}.
get_state(Pid) ->
try p1_fsm:sync_send_all_state_event(Pid, get_state)
@ -298,7 +312,8 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType])
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
ejabberd_hooks:run(start_room, ServerHost, [ServerHost, Room, Host]),
{ok, normal_state, reset_hibernate_timer(State)}.
State1 = cleanup_affiliations(State),
{ok, normal_state, reset_hibernate_timer(State1)}.
normal_state({route, <<"">>,
#message{from = From, type = Type, lang = Lang} = Packet},
@ -446,6 +461,8 @@ normal_state({route, <<"">>,
process_iq_mucsub(From, IQ, StateData);
#xcaptcha{} ->
process_iq_captcha(From, IQ, StateData);
#adhoc_command{} ->
process_iq_adhoc(From, IQ, StateData);
_ ->
Txt = ?T("The feature requested is not "
"supported by the conference"),
@ -664,6 +681,16 @@ handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
NewStateData = set_affiliations(Affiliations, StateData),
{next_state, StateName, NewStateData};
handle_event({process_item_change, Item, UJID}, StateName, StateData) ->
case process_item_change(Item, StateData, UJID) of
{error, _} ->
{next_state, StateName, StateData};
StateData ->
{next_state, StateName, StateData};
NSD ->
store_room(NSD),
{next_state, StateName, NSD}
end;
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@ -712,6 +739,8 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
case process_item_change(Item, StateData, UJID) of
{error, _} = Err ->
{reply, Err, StateName, StateData};
StateData ->
{reply, {ok, StateData}, StateName, StateData};
NSD ->
store_room(NSD),
{reply, {ok, NSD}, StateName, NSD}
@ -1405,6 +1434,12 @@ is_occupant_or_admin(JID, StateData) ->
_ -> false
end.
%% Check if the user is an admin or owner.
-spec is_admin(jid(), state()) -> boolean().
is_admin(JID, StateData) ->
FAffiliation = get_affiliation(JID, StateData),
FAffiliation == admin orelse FAffiliation == owner.
%% Decide the fate of the message and its sender
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-spec decide_fate_message(message(), jid(), state()) ->
@ -1602,7 +1637,7 @@ do_get_affiliation_fallback(JID, StateData) ->
-spec get_affiliations(state()) -> affiliations().
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
get_affiliations_callback(StateData);
get_affiliations_fallback(StateData);
get_affiliations(StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
@ -1610,13 +1645,13 @@ get_affiliations(StateData) ->
Mod = gen_mod:db_mod(ServerHost, mod_muc),
case Mod:get_affiliations(ServerHost, Room, Host) of
{error, _} ->
get_affiliations_callback(StateData);
get_affiliations_fallback(StateData);
{ok, Affiliations} ->
Affiliations
end.
-spec get_affiliations_callback(state()) -> affiliations().
get_affiliations_callback(StateData) ->
-spec get_affiliations_fallback(state()) -> affiliations().
get_affiliations_fallback(StateData) ->
StateData#state.affiliations.
-spec get_service_affiliation(jid(), state()) -> owner | none.
@ -1935,7 +1970,7 @@ filter_presence(Presence) ->
XMLNS = xmpp:get_ns(El),
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
?NS_MUC -> false;
_ -> true
_ -> XMLNS /= ?NS_HATS
end
end, xmpp:get_els(Presence)),
xmpp:set_els(Presence, Els).
@ -2485,9 +2520,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
Pres = if Presence == undefined -> #presence{};
true -> Presence
end,
Packet = xmpp:set_subtag(
Pres, #muc_user{items = [Item],
status_codes = StatusCodes}),
Packet = xmpp:set_subtag(
add_presence_hats(NJID, Pres, StateData),
#muc_user{items = [Item],
status_codes = StatusCodes}),
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
Type = xmpp:get_type(Packet),
@ -2536,7 +2572,9 @@ send_existing_presences1(ToJID, StateData) ->
false -> Item0
end,
Packet = xmpp:set_subtag(
Presence, #muc_user{items = [Item]}),
add_presence_hats(
FromJID, Presence, StateData),
#muc_user{items = [Item]}),
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
end
@ -3579,7 +3617,8 @@ get_config(Lang, StateData, From) ->
{allow_voice_requests, Config#config.allow_voice_requests},
{allow_subscription, Config#config.allow_subscription},
{voice_request_min_interval, Config#config.voice_request_min_interval},
{pubsub, Config#config.pubsub}]
{pubsub, Config#config.pubsub},
{enable_hats, Config#config.enable_hats}]
++
case ejabberd_captcha:is_feature_available() of
true ->
@ -3667,6 +3706,7 @@ set_config(Opts, Config, ServerHost, Lang) ->
({maxusers, V}, C) -> C#config{max_users = V};
({enablelogging, V}, C) -> C#config{logging = V};
({pubsub, V}, C) -> C#config{pubsub = V};
({enable_hats, V}, C) -> C#config{enable_hats = V};
({lang, L}, C) -> C#config{lang = L};
({captcha_whitelist, Js}, C) ->
LJIDs = [jid:tolower(J) || J <- Js],
@ -3897,6 +3937,9 @@ set_opts([{Opt, Val} | Opts], StateData) ->
allow_subscription ->
StateData#state{config =
(StateData#state.config)#config{allow_subscription = Val}};
enable_hats ->
StateData#state{config =
(StateData#state.config)#config{enable_hats = Val}};
lang ->
StateData#state{config =
(StateData#state.config)#config{lang = Val}};
@ -3927,6 +3970,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
end,
StateData#state{subject = Subj};
subject_author -> StateData#state{subject_author = Val};
hats_users ->
Hats = maps:from_list(
lists:map(fun({U, H}) -> {U, maps:from_list(H)} end,
Val)),
StateData#state{hats_users = Hats};
_ -> StateData
end,
set_opts(Opts, NSD).
@ -3983,6 +4031,7 @@ make_opts(StateData) ->
?MAKE_CONFIG_OPT(#config.vcard),
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
?MAKE_CONFIG_OPT(#config.pubsub),
?MAKE_CONFIG_OPT(#config.enable_hats),
?MAKE_CONFIG_OPT(#config.lang),
{captcha_whitelist,
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
@ -3990,6 +4039,9 @@ make_opts(StateData) ->
maps:to_list(StateData#state.affiliations)},
{subject, StateData#state.subject},
{subject_author, StateData#state.subject_author},
{hats_users,
lists:map(fun({U, H}) -> {U, maps:to_list(H)} end,
maps:to_list(StateData#state.hats_users))},
{hibernation_time, erlang:system_time(microsecond)},
{subscribers, Subscribers}].
@ -4080,6 +4132,7 @@ maybe_forget_room(StateData) ->
make_disco_info(_From, StateData) ->
Config = StateData#state.config,
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_COMMANDS,
?CONFIG_OPT_TO_FEATURE((Config#config.public),
<<"muc_public">>, <<"muc_hidden">>),
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
@ -4119,6 +4172,77 @@ process_iq_disco_info(From, #iq{type = get, lang = Lang,
DiscoInfo = make_disco_info(From, StateData),
Extras = iq_disco_info_extras(Lang, StateData, false),
{result, DiscoInfo#disco_info{xdata = [Extras]}};
process_iq_disco_info(From, #iq{type = get, lang = Lang,
sub_els = [#disco_info{node = ?NS_COMMANDS}]},
StateData) ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result,
#disco_info{
identities = [#identity{category = <<"automation">>,
type = <<"command-list">>,
name = translate:translate(
Lang, ?T("Commands"))}]}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_info(From, #iq{type = get, lang = Lang,
sub_els = [#disco_info{node = ?MUC_HAT_ADD_CMD}]},
StateData) ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result,
#disco_info{
identities = [#identity{category = <<"automation">>,
type = <<"command-node">>,
name = translate:translate(
Lang, ?T("Add a hat to a user"))}],
features = [?NS_COMMANDS]}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_info(From, #iq{type = get, lang = Lang,
sub_els = [#disco_info{node = ?MUC_HAT_REMOVE_CMD}]},
StateData) ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result,
#disco_info{
identities = [#identity{category = <<"automation">>,
type = <<"command-node">>,
name = translate:translate(
Lang, ?T("Remove a hat from a user"))}],
features = [?NS_COMMANDS]}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_info(From, #iq{type = get, lang = Lang,
sub_els = [#disco_info{node = ?MUC_HAT_LIST_CMD}]},
StateData) ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result,
#disco_info{
identities = [#identity{category = <<"automation">>,
type = <<"command-node">>,
name = translate:translate(
Lang, ?T("List users with hats"))}],
features = [?NS_COMMANDS]}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_info(From, #iq{type = get, lang = Lang,
sub_els = [#disco_info{node = Node}]},
StateData) ->
@ -4199,6 +4323,46 @@ process_iq_disco_items(From, #iq{type = get, sub_els = [#disco_items{node = <<>>
{result, #disco_items{}}
end
end;
process_iq_disco_items(From, #iq{type = get, lang = Lang,
sub_els = [#disco_items{node = ?NS_COMMANDS}]},
StateData) ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result,
#disco_items{
items = [#disco_item{jid = StateData#state.jid,
node = ?MUC_HAT_ADD_CMD,
name = translate:translate(
Lang, ?T("Add a hat to a user"))},
#disco_item{jid = StateData#state.jid,
node = ?MUC_HAT_REMOVE_CMD,
name = translate:translate(
Lang, ?T("Remove a hat from a user"))},
#disco_item{jid = StateData#state.jid,
node = ?MUC_HAT_LIST_CMD,
name = translate:translate(
Lang, ?T("List users with hats"))}]}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_items(From, #iq{type = get, lang = Lang,
sub_els = [#disco_items{node = Node}]},
StateData)
when Node == ?MUC_HAT_ADD_CMD;
Node == ?MUC_HAT_REMOVE_CMD;
Node == ?MUC_HAT_LIST_CMD ->
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
{result, #disco_items{}};
false ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}
end;
process_iq_disco_items(_From, #iq{lang = Lang}, _StateData) ->
Txt = ?T("Node not found"),
{error, xmpp:err_item_not_found(Txt, Lang)}.
@ -4441,6 +4605,271 @@ get_mucroom_disco_items(StateData) ->
end, [], StateData#state.nicks),
#disco_items{items = Items}.
-spec process_iq_adhoc(jid(), iq(), state()) ->
{result, adhoc_command()} |
{result, adhoc_command(), state()} |
{error, stanza_error()}.
process_iq_adhoc(_From, #iq{type = get}, _StateData) ->
{error, xmpp:err_bad_request()};
process_iq_adhoc(From, #iq{type = set, lang = Lang1,
sub_els = [#adhoc_command{} = Request]},
StateData) ->
% Ad-Hoc Commands are used only for Hats here
case (StateData#state.config)#config.enable_hats andalso
is_admin(From, StateData)
of
true ->
#adhoc_command{lang = Lang2, node = Node,
action = Action, xdata = XData} = Request,
Lang = case Lang2 of
<<"">> -> Lang1;
_ -> Lang2
end,
case {Node, Action} of
{_, cancel} ->
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{status = canceled, lang = Lang,
node = Node})};
{?MUC_HAT_ADD_CMD, execute} ->
Form =
#xdata{
title = translate:translate(
Lang, ?T("Add a hat to a user")),
type = form,
fields =
[#xdata_field{
type = 'jid-single',
label = translate:translate(Lang, ?T("Jabber ID")),
required = true,
var = <<"jid">>},
#xdata_field{
type = 'text-single',
label = translate:translate(Lang, ?T("Hat title")),
var = <<"hat_title">>},
#xdata_field{
type = 'text-single',
label = translate:translate(Lang, ?T("Hat URI")),
required = true,
var = <<"hat_uri">>}
]},
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{
status = executing,
xdata = Form})};
{?MUC_HAT_ADD_CMD, complete} when XData /= undefined ->
JID = try
jid:decode(hd(xmpp_util:get_xdata_values(
<<"jid">>, XData)))
catch _:_ -> error
end,
URI = try
hd(xmpp_util:get_xdata_values(
<<"hat_uri">>, XData))
catch _:_ -> error
end,
Title = case xmpp_util:get_xdata_values(
<<"hat_title">>, XData) of
[] -> <<"">>;
[T] -> T
end,
if
(JID /= error) and (URI /= error) ->
case add_hat(JID, URI, Title, StateData) of
{ok, NewStateData} ->
store_room(NewStateData),
send_update_presence(
JID, NewStateData, StateData),
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{status = completed}),
NewStateData};
{error, size_limit} ->
Txt = ?T("Hats limit exceeded"),
{error, xmpp:err_not_allowed(Txt, Lang)}
end;
true ->
{error, xmpp:err_bad_request()}
end;
{?MUC_HAT_ADD_CMD, complete} ->
{error, xmpp:err_bad_request()};
{?MUC_HAT_ADD_CMD, _} ->
Txt = ?T("Incorrect value of 'action' attribute"),
{error, xmpp:err_bad_request(Txt, Lang)};
{?MUC_HAT_REMOVE_CMD, execute} ->
Form =
#xdata{
title = translate:translate(
Lang, ?T("Remove a hat from a user")),
type = form,
fields =
[#xdata_field{
type = 'jid-single',
label = translate:translate(Lang, ?T("Jabber ID")),
required = true,
var = <<"jid">>},
#xdata_field{
type = 'text-single',
label = translate:translate(Lang, ?T("Hat URI")),
required = true,
var = <<"hat_uri">>}
]},
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{
status = executing,
xdata = Form})};
{?MUC_HAT_REMOVE_CMD, complete} when XData /= undefined ->
JID = try
jid:decode(hd(xmpp_util:get_xdata_values(
<<"jid">>, XData)))
catch _:_ -> error
end,
URI = try
hd(xmpp_util:get_xdata_values(
<<"hat_uri">>, XData))
catch _:_ -> error
end,
if
(JID /= error) and (URI /= error) ->
NewStateData = del_hat(JID, URI, StateData),
store_room(NewStateData),
send_update_presence(
JID, NewStateData, StateData),
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{status = completed}),
NewStateData};
true ->
{error, xmpp:err_bad_request()}
end;
{?MUC_HAT_REMOVE_CMD, complete} ->
{error, xmpp:err_bad_request()};
{?MUC_HAT_REMOVE_CMD, _} ->
Txt = ?T("Incorrect value of 'action' attribute"),
{error, xmpp:err_bad_request(Txt, Lang)};
{?MUC_HAT_LIST_CMD, execute} ->
Hats = get_all_hats(StateData),
Items =
lists:map(
fun({JID, URI, Title}) ->
[#xdata_field{
var = <<"jid">>,
values = [jid:encode(JID)]},
#xdata_field{
var = <<"hat_title">>,
values = [URI]},
#xdata_field{
var = <<"hat_uri">>,
values = [Title]}]
end, Hats),
Form =
#xdata{
title = translate:translate(
Lang, ?T("List of users with hats")),
type = result,
reported =
[#xdata_field{
label = translate:translate(Lang, ?T("Jabber ID")),
var = <<"jid">>},
#xdata_field{
label = translate:translate(Lang, ?T("Hat title")),
var = <<"hat_title">>},
#xdata_field{
label = translate:translate(Lang, ?T("Hat URI")),
var = <<"hat_uri">>}],
items = Items},
{result,
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{
status = completed,
xdata = Form})};
{?MUC_HAT_LIST_CMD, _} ->
Txt = ?T("Incorrect value of 'action' attribute"),
{error, xmpp:err_bad_request(Txt, Lang)};
_ ->
{error, xmpp:err_item_not_found()}
end;
_ ->
{error, xmpp:err_forbidden()}
end.
-spec add_hat(jid(), binary(), binary(), state()) ->
{ok, state()} | {error, size_limit}.
add_hat(JID, URI, Title, StateData) ->
Hats = StateData#state.hats_users,
LJID = jid:remove_resource(jid:tolower(JID)),
UserHats = maps:get(LJID, Hats, #{}),
UserHats2 = maps:put(URI, Title, UserHats),
USize = maps:size(UserHats2),
if
USize =< ?MAX_HATS_PER_USER ->
Hats2 = maps:put(LJID, UserHats2, Hats),
Size = maps:size(Hats2),
if
Size =< ?MAX_HATS_USERS ->
{ok, StateData#state{hats_users = Hats2}};
true ->
{error, size_limit}
end;
true ->
{error, size_limit}
end.
-spec del_hat(jid(), binary(), state()) -> state().
del_hat(JID, URI, StateData) ->
Hats = StateData#state.hats_users,
LJID = jid:remove_resource(jid:tolower(JID)),
UserHats = maps:get(LJID, Hats, #{}),
UserHats2 = maps:remove(URI, UserHats),
Hats2 =
case maps:size(UserHats2) of
0 ->
maps:remove(LJID, Hats);
_ ->
maps:put(LJID, UserHats2, Hats)
end,
StateData#state{hats_users = Hats2}.
-spec get_all_hats(state()) -> list({jid(), binary(), binary()}).
get_all_hats(StateData) ->
lists:flatmap(
fun({LJID, H}) ->
JID = jid:make(LJID),
lists:map(fun({URI, Title}) -> {JID, URI, Title} end,
maps:to_list(H))
end,
maps:to_list(StateData#state.hats_users)).
-spec add_presence_hats(jid(), #presence{}, state()) -> #presence{}.
add_presence_hats(JID, Pres, StateData) ->
case (StateData#state.config)#config.enable_hats of
true ->
Hats = StateData#state.hats_users,
LJID = jid:remove_resource(jid:tolower(JID)),
UserHats = maps:get(LJID, Hats, #{}),
case maps:size(UserHats) of
0 -> Pres;
_ ->
Items =
lists:map(fun({URI, Title}) ->
#muc_hat{uri = URI, title = Title}
end,
maps:to_list(UserHats)),
xmpp:set_subtag(Pres,
#muc_hats{hats = Items})
end;
false ->
Pres
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Voice request support
@ -4687,7 +5116,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
id = p1_rand:get_string(),
sub_els = [Payload1]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
WJ, Packet1, true);
WJ, Packet1, false);
true -> ok
end,
if WN /= [] ->
@ -4703,7 +5132,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
id = p1_rand:get_string(),
sub_els = [Payload2]}]}}]},
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
WN, Packet2, true);
WN, Packet2, false);
true -> ok
end.
@ -4927,6 +5356,23 @@ muc_subscribers_put(Subscriber, MUCSubscribers) ->
subscriber_nodes = NewSubNodes}.
cleanup_affiliations(State) ->
case mod_muc_opt:cleanup_affiliations_on_start(State#state.server_host) of
true ->
Affiliations =
maps:filter(
fun({LUser, LServer, _}, _) ->
case ejabberd_router:is_my_host(LServer) of
true ->
ejabberd_auth:user_exists(LUser, LServer);
false ->
true
end
end, State#state.affiliations),
State#state{affiliations = Affiliations};
false ->
State
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaningful content

View File

@ -38,7 +38,7 @@
register_online_user/4, unregister_online_user/4,
count_online_rooms_by_user/3, get_online_rooms_by_user/3,
get_subscribed_rooms/3, get_rooms_without_subscribers/2,
find_online_room_by_pid/2]).
find_online_room_by_pid/2, remove_user/2]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
@ -465,6 +465,13 @@ get_subscribed_rooms(LServer, Host, Jid) ->
{error, db_failure}
end.
remove_user(LUser, LServer) ->
SJID = jid:encode(jid:make(LUser, LServer)),
ejabberd_sql:sql_query(
LServer,
?SQL("delete from muc_room_subscribers where jid=%(SJID)s")),
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -35,7 +35,7 @@
%% API
-export([start/2, stop/1, reload/3,
user_send_packet/1]).
user_send_packet/1]).
%% gen_server callbacks
-export([init/1, handle_info/2, handle_call/3,
@ -51,11 +51,6 @@
response,
ts :: integer()}).
-record(dest, {jid_string :: binary() | none,
jid_jid :: jid() | undefined,
type :: bcc | cc | noreply | ofrom | replyroom | replyto | to,
address :: address()}).
-type limit_value() :: {default | custom, integer()}.
-record(limits, {message :: limit_value(),
presence :: limit_value()}).
@ -63,14 +58,6 @@
-record(service_limits, {local :: #limits{},
remote :: #limits{}}).
-type routing() :: route_single | {route_multicast, binary(), #service_limits{}}.
-record(group, {server :: binary(),
dests :: [#dest{}],
multicast :: routing() | undefined,
others :: [address()],
addresses :: [address()]}).
-record(state, {lserver :: binary(),
lservice :: binary(),
access :: atom(),
@ -117,7 +104,7 @@ reload(LServerS, NewOpts, OldOpts) ->
user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
case xmpp:get_subtag(Packet, #addresses{}) of
#addresses{list = Addresses} ->
{ToDeliver, _Delivereds} = split_addresses_todeliver(Addresses),
{CC, BCC, _Invalid, _Delivered} = partition_addresses(Addresses),
NewState =
lists:foldl(
fun(Address, St) ->
@ -138,7 +125,7 @@ user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
undefined ->
St
end
end, C2SState, ToDeliver),
end, C2SState, CC ++ BCC),
{Packet, NewState};
false ->
Acc
@ -308,19 +295,10 @@ iq_vcard(Lang, State) ->
%%%-------------------------
-spec route_trusted(binary(), binary(), jid(), [jid()], stanza()) -> 'ok'.
route_trusted(LServiceS, LServerS, FromJID,
Destinations, Packet) ->
Packet_stripped = Packet,
Delivereds = [],
Dests2 = lists:map(
fun(D) ->
#dest{jid_string = jid:encode(D),
jid_jid = D, type = bcc,
address = #address{type = bcc, jid = D}}
end, Destinations),
Groups = group_dests(Dests2),
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped).
route_trusted(LServiceS, LServerS, FromJID, Destinations, Packet) ->
Addresses = [#address{type = bcc, jid = D} || D <- Destinations],
Groups = group_by_destinations(Addresses, #{}),
route_grouped(LServerS, LServiceS, FromJID, Groups, [], Packet).
-spec route_untrusted(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
@ -356,50 +334,88 @@ route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
route_untrusted2(LServiceS, LServerS, Access, SLimits, Packet) ->
FromJID = xmpp:get_from(Packet),
ok = check_access(LServerS, Access, FromJID),
{ok, Packet_stripped, Addresses} = strip_addresses_element(Packet),
{To_deliver, Delivereds} = split_addresses_todeliver(Addresses),
Dests = convert_dest_record(To_deliver),
{Dests2, Not_jids} = split_dests_jid(Dests),
report_not_jid(FromJID, Packet, Not_jids),
ok = check_limit_dests(SLimits, FromJID, Packet, Dests2),
Groups = group_dests(Dests2),
{ok, PacketStripped, Addresses} = strip_addresses_element(Packet),
{CC, BCC, NotJids, Rest} = partition_addresses(Addresses),
report_not_jid(FromJID, Packet, NotJids),
ok = check_limit_dests(SLimits, FromJID, Packet, length(CC) + length(BCC)),
Groups0 = group_by_destinations(CC, #{}),
Groups = group_by_destinations(BCC, Groups0),
ok = check_relay(FromJID#jid.server, LServerS, Groups),
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped).
route_grouped(LServerS, LServiceS, FromJID, Groups, Rest, PacketStripped).
-spec route_common(binary(), binary(), jid(), [#group{}],
[address()], stanza()) -> 'ok'.
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped) ->
Groups2 = look_cached_servers(LServerS, LServiceS, Groups),
Groups3 = build_others_xml(Groups2),
Groups4 = add_addresses(Delivereds, Groups3),
AGroups = decide_action_groups(Groups4),
act_groups(FromJID, Packet_stripped, LServiceS,
AGroups).
-spec mark_as_delivered([address()]) -> [address()].
mark_as_delivered(Addresses) ->
[A#address{delivered = true} || A <- Addresses].
-spec act_groups(jid(), stanza(), binary(), [{routing(), #group{}}]) -> 'ok'.
act_groups(FromJID, Packet_stripped, LServiceS, AGroups) ->
-spec route_individual(jid(), [address()], [address()], [address()], stanza()) -> ok.
route_individual(From, CC, BCC, Other, Packet) ->
CCDelivered = mark_as_delivered(CC),
Addresses = CCDelivered ++ Other,
PacketWithAddresses = xmpp:append_subtags(Packet, [#addresses{list = Addresses}]),
lists:foreach(
fun(AGroup) ->
perform(FromJID, Packet_stripped, LServiceS,
AGroup)
end, AGroups).
-spec perform(jid(), stanza(), binary(),
{routing(), #group{}}) -> 'ok'.
perform(From, Packet, _,
{route_single, Group}) ->
fun(#address{jid = To}) ->
ejabberd_router:route(xmpp:set_from_to(PacketWithAddresses, From, To))
end, CC),
lists:foreach(
fun(ToUser) ->
Group_others = strip_other_bcc(ToUser, Group#group.others),
route_packet(From, ToUser, Packet,
Group_others, Group#group.addresses)
end, Group#group.dests);
perform(From, Packet, _,
{{route_multicast, JID, RLimits}, Group}) ->
route_packet_multicast(From, JID, Packet,
Group#group.dests, Group#group.addresses, RLimits).
fun(#address{jid = To} = Address) ->
Packet2 = case Addresses of
[] ->
Packet;
_ ->
xmpp:append_subtags(Packet, [#addresses{list = [Address | Addresses]}])
end,
ejabberd_router:route(xmpp:set_from_to(Packet2, From, To))
end, BCC).
-spec route_chunk(jid(), jid(), stanza(), [address()]) -> ok.
route_chunk(From, To, Packet, Addresses) ->
PacketWithAddresses = xmpp:append_subtags(Packet, [#addresses{list = Addresses}]),
ejabberd_router:route(xmpp:set_from_to(PacketWithAddresses, From, To)).
-spec route_in_chunks(jid(), jid(), stanza(), integer(), [address()], [address()], [address()]) -> ok.
route_in_chunks(_From, _To, _Packet, _Limit, [], [], _) ->
ok;
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses) when length(CC) > Limit ->
{Chunk, Rest} = lists:split(Limit, CC),
route_chunk(From, To, Packet, Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, Rest, BCC, RestOfAddresses);
route_in_chunks(From, To, Packet, Limit, [], BCC, RestOfAddresses) when length(BCC) > Limit ->
{Chunk, Rest} = lists:split(Limit, BCC),
route_chunk(From, To, Packet, Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, [], Rest, RestOfAddresses);
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses) when length(BCC) + length(CC) > Limit ->
{Chunk, Rest} = lists:split(Limit - length(CC), BCC),
route_chunk(From, To, Packet, CC ++ Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, [], Rest, RestOfAddresses);
route_in_chunks(From, To, Packet, _Limit, CC, BCC, RestOfAddresses) ->
route_chunk(From, To, Packet, CC ++ BCC ++ RestOfAddresses).
-spec route_multicast(jid(), jid(), [address()], [address()], [address()], stanza(), #limits{}) -> ok.
route_multicast(From, To, CC, BCC, RestOfAddresses, Packet, Limits) ->
{_Type, Limit} = get_limit_number(element(1, Packet),
Limits),
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses).
-spec route_grouped(binary(), binary(), jid(), #{}, [address()], stanza()) -> ok.
route_grouped(LServer, LService, From, Groups, RestOfAddresses, Packet) ->
maps:fold(
fun(Server, {CC, BCC}, _) ->
OtherCC = maps:fold(
fun(Server2, _, Res) when Server2 == Server ->
Res;
(_, {CC2, _}, Res) ->
mark_as_delivered(CC2) ++ Res
end, [], Groups),
case search_server_on_cache(Server,
LServer, LService,
{?MAXTIME_CACHE_POSITIVE,
?MAXTIME_CACHE_NEGATIVE}) of
route_single ->
route_individual(From, CC, BCC, OtherCC ++ RestOfAddresses, Packet);
{route_multicast, Service, Limits} ->
route_multicast(From, Service, CC, BCC, OtherCC ++ RestOfAddresses, Packet, Limits)
end
end, ok, Groups).
%%%-------------------------
%%% Check access permission
@ -425,245 +441,89 @@ 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
%%%-------------------------
-spec split_addresses_todeliver([address()]) -> {[address()], [address()]}.
split_addresses_todeliver(Addresses) ->
lists:partition(
fun(#address{delivered = true}) ->
false;
(#address{type = Type}) ->
case Type of
to -> true;
cc -> true;
bcc -> true;
_ -> false
end
end, Addresses).
partition_addresses(Addresses) ->
lists:foldl(
fun(#address{delivered = true} = A, {C, B, I, D}) ->
{C, B, I, [A | D]};
(#address{type = T, jid = undefined} = A, {C, B, I, D})
when T == to; T == cc; T == bcc ->
{C, B, [A | I], D};
(#address{type = T} = A, {C, B, I, D})
when T == to; T == cc ->
{[A | C], B, I, D};
(#address{type = bcc} = A, {C, B, I, D}) ->
{C, [A | B], I, D};
(A, {C, B, I, D}) ->
{C, B, I, [A | D]}
end, {[], [], [], []}, Addresses).
%%%-------------------------
%%% Check does not exceed limit of destinations
%%%-------------------------
-spec check_limit_dests(#service_limits{}, jid(), stanza(), [address()]) -> ok.
check_limit_dests(SLimits, FromJID, Packet,
Addresses) ->
-spec check_limit_dests(#service_limits{}, jid(), stanza(), integer()) -> ok.
check_limit_dests(SLimits, FromJID, Packet, NumOfAddresses) ->
SenderT = sender_type(FromJID),
Limits = get_slimit_group(SenderT, SLimits),
Type_of_stanza = type_of_stanza(Packet),
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
Limits),
case length(Addresses) > Limit_number of
StanzaType = type_of_stanza(Packet),
{_Type, Limit} = get_limit_number(StanzaType,
Limits),
case NumOfAddresses > Limit of
false -> ok;
true -> throw(etoorec)
end.
%%%-------------------------
%%% Convert Destination XML to record
%%%-------------------------
-spec convert_dest_record([address()]) -> [#dest{}].
convert_dest_record(Addrs) ->
lists:map(
fun(#address{jid = undefined, type = Type} = Addr) ->
#dest{jid_string = none,
type = Type, address = Addr};
(#address{jid = JID, type = Type} = Addr) ->
#dest{jid_string = jid:encode(JID), jid_jid = JID,
type = Type, address = Addr}
end, Addrs).
%%%-------------------------
%%% Split destinations by existence of JID
%%% and send error messages for other dests
%%%-------------------------
-spec split_dests_jid([#dest{}]) -> {[#dest{}], [#dest{}]}.
split_dests_jid(Dests) ->
lists:partition(fun (Dest) ->
case Dest#dest.jid_string of
none -> false;
_ -> true
end
end,
Dests).
-spec report_not_jid(jid(), stanza(), [#dest{}]) -> any().
report_not_jid(From, Packet, Dests) ->
Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.address))
|| Dest <- Dests],
[route_error(
xmpp:set_from_to(Packet, From, From), jid_malformed,
str:format(?T("This service can not process the address: ~s"), [D]))
|| D <- Dests2].
-spec report_not_jid(jid(), stanza(), [address()]) -> any().
report_not_jid(From, Packet, Addresses) ->
lists:foreach(
fun(Address) ->
route_error(
xmpp:set_from_to(Packet, From, From), jid_malformed,
str:format(?T("This service can not process the address: ~s"),
[fxml:element_to_binary(xmpp:encode(Address))]))
end, Addresses).
%%%-------------------------
%%% Group destinations by their servers
%%%-------------------------
-spec group_dests([#dest{}]) -> [#group{}].
group_dests(Dests) ->
D = lists:foldl(fun (Dest, Dict) ->
ServerS = (Dest#dest.jid_jid)#jid.server,
dict:append(ServerS, Dest, Dict)
end,
dict:new(), Dests),
Keys = dict:fetch_keys(D),
[#group{server = Key, dests = dict:fetch(Key, D),
addresses = [], others = []}
|| Key <- Keys].
%%%-------------------------
%%% Look for cached responses
%%%-------------------------
look_cached_servers(LServerS, LServiceS, Groups) ->
[look_cached(LServerS, LServiceS, Group) || Group <- Groups].
look_cached(LServerS, LServiceS, G) ->
Maxtime_positive = (?MAXTIME_CACHE_POSITIVE),
Maxtime_negative = (?MAXTIME_CACHE_NEGATIVE),
Cached_response = search_server_on_cache(G#group.server,
LServerS, LServiceS,
{Maxtime_positive,
Maxtime_negative}),
G#group{multicast = Cached_response}.
%%%-------------------------
%%% Build delivered XML element
%%%-------------------------
build_others_xml(Groups) ->
[Group#group{others =
build_other_xml(Group#group.dests)}
|| Group <- Groups].
build_other_xml(Dests) ->
lists:foldl(fun (Dest, R) ->
XML = Dest#dest.address,
case Dest#dest.type of
to -> [add_delivered(XML) | R];
cc -> [add_delivered(XML) | R];
_ -> [XML | R]
end
end,
[], Dests).
-spec add_delivered(address()) -> address().
add_delivered(Addr) ->
Addr#address{delivered = true}.
%%%-------------------------
%%% Add preliminary packets
%%%-------------------------
add_addresses(Delivereds, Groups) ->
Ps = [Group#group.others || Group <- Groups],
add_addresses2(Delivereds, Groups, [], [], Ps).
add_addresses2(_, [], Res, _, []) -> Res;
add_addresses2(Delivereds, [Group | Groups], Res, Pa,
[Pi | Pz]) ->
Addresses = lists:append([Delivereds] ++ Pa ++ Pz),
Group2 = Group#group{addresses = Addresses},
add_addresses2(Delivereds, Groups, [Group2 | Res],
[Pi | Pa], Pz).
%%%-------------------------
%%% Decide action groups
%%%-------------------------
-spec decide_action_groups([#group{}]) -> [{routing(), #group{}}].
decide_action_groups(Groups) ->
[{Group#group.multicast, Group}
|| Group <- Groups].
group_by_destinations(Addrs, Map) ->
lists:foldl(
fun
(#address{type = Type, jid = #jid{lserver = Server}} = Addr, Map2) when Type == to; Type == cc ->
maps:update_with(Server,
fun({CC, BCC}) ->
{[Addr | CC], BCC}
end, {[Addr], []}, Map2);
(#address{type = bcc, jid = #jid{lserver = Server}} = Addr, Map2) ->
maps:update_with(Server,
fun({CC, BCC}) ->
{CC, [Addr | BCC]}
end, {[], [Addr]}, Map2)
end, Map, Addrs).
%%%-------------------------
%%% Route packet
%%%-------------------------
-spec route_packet(jid(), #dest{}, stanza(), [addresses()], [addresses()]) -> 'ok'.
route_packet(From, ToDest, Packet, Others, Addresses) ->
Dests = case ToDest#dest.type of
bcc -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
Packet, {Others, Addresses}).
-spec route_packet_multicast(jid(), binary(), stanza(), [#dest{}], [address()], #limits{}) -> 'ok'.
route_packet_multicast(From, ToS, Packet, Dests,
Addresses, Limits) ->
Type_of_stanza = type_of_stanza(Packet),
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
Limits),
Fragmented_dests = fragment_dests(Dests, Limit_number),
lists:foreach(fun(DFragment) ->
route_packet2(From, ToS, DFragment, Packet,
Addresses)
end, Fragmented_dests).
-spec route_packet2(jid(), binary(), [#dest{}], stanza(), {[address()], [address()]} | [address()]) -> 'ok'.
route_packet2(From, ToS, Dests, Packet, Addresses) ->
Els = case append_dests(Dests, Addresses) of
[] ->
xmpp:get_els(Packet);
ACs ->
[#addresses{list = ACs}|xmpp:get_els(Packet)]
end,
Packet2 = xmpp:set_els(Packet, Els),
ToJID = stj(ToS),
ejabberd_router:route(xmpp:set_from_to(Packet2, From, ToJID)).
-spec append_dests([#dest{}], {[address()], [address()]} | [address()]) -> [address()].
append_dests(_Dests, {Others, Addresses}) ->
Addresses ++ Others;
append_dests([], Addresses) -> Addresses;
append_dests([Dest | Dests], Addresses) ->
append_dests(Dests, [Dest#dest.address | Addresses]).
%%%-------------------------
%%% Check relay
%%%-------------------------
-spec check_relay(binary(), binary(), [#group{}]) -> ok.
-spec check_relay(binary(), binary(), #{}) -> ok.
check_relay(RS, LS, Gs) ->
case check_relay_required(RS, LS, Gs) of
false -> ok;
true -> throw(edrelay)
case lists:suffix(str:tokens(LS, <<".">>),
str:tokens(RS, <<".">>)) orelse
(maps:is_key(LS, Gs) andalso maps:size(Gs) == 1) of
true -> ok;
_ -> throw(edrelay)
end.
-spec check_relay_required(binary(), binary(), [#group{}]) -> boolean().
check_relay_required(RServer, LServerS, Groups) ->
case lists:suffix(str:tokens(LServerS, <<".">>),
str:tokens(RServer, <<".">>)) of
true -> false;
false -> check_relay_required(LServerS, Groups)
end.
-spec check_relay_required(binary(), [#group{}]) -> boolean().
check_relay_required(LServerS, Groups) ->
lists:any(fun (Group) -> Group#group.server /= LServerS
end,
Groups).
%%%-------------------------
%%% Check protocol support: Send request
%%%-------------------------
@ -1060,20 +920,6 @@ get_slimit_group(local, SLimits) ->
get_slimit_group(remote, SLimits) ->
SLimits#service_limits.remote.
fragment_dests(Dests, Limit_number) ->
{R, _} = lists:foldl(fun (Dest, {Res, Count}) ->
case Count of
Limit_number ->
Head2 = [Dest], {[Head2 | Res], 0};
_ ->
[Head | Tail] = Res,
Head2 = [Dest | Head],
{[Head2 | Tail], Count + 1}
end
end,
{[[]], 0}, Dests),
R.
%%%-------------------------
%%% Limits: XEP-0128 Service Discovery Extensions
%%%-------------------------

View File

@ -95,7 +95,7 @@
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([get_commands_spec/0, delete_old_items/1, delete_expired_items/0]).
-export([route/1]).
@ -3431,6 +3431,14 @@ max_items(Host, Options) ->
end
end.
-spec item_expire(host(), [{atom(), any()}]) -> non_neg_integer() | infinity.
item_expire(Host, Options) ->
case get_option(Options, item_expire) of
I when is_integer(I), I < 0 -> 0;
I when is_integer(I) -> I;
_ -> get_max_item_expire_node(Host)
end.
-spec get_configure_xfields(_, pubsub_node_config:result(),
binary(), [binary()]) -> [xdata_field()].
get_configure_xfields(_Type, Options, Lang, Groups) ->
@ -3504,17 +3512,24 @@ decode_node_config(undefined, _, _) ->
decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
try
Config = pubsub_node_config:decode(Fs),
Max = get_max_items_node(Host),
case {check_opt_range(max_items, Config, Max),
MaxItems = get_max_items_node(Host),
MaxExpiry = get_max_item_expire_node(Host),
case {check_opt_range(max_items, Config, MaxItems),
check_opt_range(item_expire, Config, MaxExpiry),
check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of
{true, true} ->
{true, true, true} ->
Config;
{true, false} ->
{true, true, false} ->
erlang:error(
{pubsub_node_config,
{bad_var_value, <<"pubsub#max_payload_size">>,
?NS_PUBSUB_NODE_CONFIG}});
{false, _} ->
{true, false, _} ->
erlang:error(
{pubsub_node_config,
{bad_var_value, <<"pubsub#item_expire">>,
?NS_PUBSUB_NODE_CONFIG}});
{false, _, _} ->
erlang:error(
{pubsub_node_config,
{bad_var_value, <<"pubsub#max_items">>,
@ -3560,20 +3575,24 @@ decode_get_pending(#xdata{fields = Fs}, Lang) ->
end.
-spec check_opt_range(atom(), [proplists:property()],
non_neg_integer() | unlimited | undefined) -> boolean().
check_opt_range(_Opt, _Opts, undefined) ->
true;
non_neg_integer() | unlimited | infinity) -> boolean().
check_opt_range(_Opt, _Opts, unlimited) ->
true;
check_opt_range(_Opt, _Opts, infinity) ->
true;
check_opt_range(Opt, Opts, Max) ->
case proplists:get_value(Opt, Opts, Max) of
max -> true;
Val -> Val =< Max
end.
-spec get_max_items_node(host()) -> undefined | unlimited | non_neg_integer().
-spec get_max_items_node(host()) -> unlimited | non_neg_integer().
get_max_items_node(Host) ->
config(Host, max_items_node, undefined).
config(Host, max_items_node, ?MAXITEMS).
-spec get_max_item_expire_node(host()) -> infinity | non_neg_integer().
get_max_item_expire_node(Host) ->
config(Host, max_item_expire_node, infinity).
-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer().
get_max_subscriptions_node(Host) ->
@ -4181,16 +4200,63 @@ delete_old_items(N) ->
ok
end.
-spec delete_expired_items() -> ok | error.
delete_expired_items() ->
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,
options = Options}) ->
case item_expire(Host, Options) of
infinity ->
ok;
Seconds ->
case node_action(
Host, Type,
remove_expired_items,
[Nidx, Seconds]) of
{result, []} ->
ok;
{result, [_|_]} ->
unset_cached_item(
Host, Nidx);
{error, _} ->
error
end
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",
note = "added in 21.12",
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},
#ejabberd_commands{name = delete_expired_pubsub_items, tags = [purge],
desc = "Delete expired PubSub items",
note = "added in 21.12",
module = ?MODULE, function = delete_expired_items,
args = [],
result = {res, rescode},
result_desc = "0 if command failed, 1 when succeeded",
result_example = ok}].
-spec mod_opt_type(atom()) -> econf:validator().
@ -4204,6 +4270,8 @@ mod_opt_type(last_item_cache) ->
econf:bool();
mod_opt_type(max_items_node) ->
econf:non_neg_int(unlimited);
mod_opt_type(max_item_expire_node) ->
econf:timeout(second, infinity);
mod_opt_type(max_nodes_discoitems) ->
econf:non_neg_int(infinity);
mod_opt_type(max_subscriptions_node) ->
@ -4251,6 +4319,7 @@ mod_options(Host) ->
{ignore_pep_from_offline, true},
{last_item_cache, false},
{max_items_node, ?MAXITEMS},
{max_item_expire_node, infinity},
{max_nodes_discoitems, 100},
{nodetree, ?STDTREE},
{pep_mapping, []},
@ -4329,11 +4398,17 @@ mod_doc() ->
" so many nodes, caching last items speeds up pubsub "
"and allows to raise user connection rate. The cost "
"is memory usage, as every item is stored in memory.")}},
{max_item_expire_node,
#{value => "timeout() | infinity",
note => "added in 21.12",
desc =>
?T("Specify the maximum item epiry time. Default value "
"is: 'infinity'.")}},
{max_items_node,
#{value => "non_neg_integer() | infinity",
desc =>
?T("Define the maximum number of items that can be "
"stored in a node. Default value is: '10'.")}},
"stored in a node. Default value is: '1000'.")}},
{max_nodes_discoitems,
#{value => "pos_integer() | infinity",
desc =>

View File

@ -11,6 +11,7 @@
-export([hosts/1]).
-export([ignore_pep_from_offline/1]).
-export([last_item_cache/1]).
-export([max_item_expire_node/1]).
-export([max_items_node/1]).
-export([max_nodes_discoitems/1]).
-export([max_subscriptions_node/1]).
@ -68,7 +69,13 @@ last_item_cache(Opts) when is_map(Opts) ->
last_item_cache(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, last_item_cache).
-spec max_items_node(gen_mod:opts() | global | binary()) -> non_neg_integer().
-spec max_item_expire_node(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
max_item_expire_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_item_expire_node, Opts);
max_item_expire_node(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, max_item_expire_node).
-spec max_items_node(gen_mod:opts() | global | binary()) -> 'unlimited' | non_neg_integer().
max_items_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_items_node, Opts);
max_items_node(Host) ->

View File

@ -52,7 +52,7 @@ store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
case ?SQL_UPSERT(LServer, "push_session",
["!username=%(LUser)s",
"!server_host=%(LServer)s",
"!timestamp=%(TS)d",
"timestamp=%(TS)d",
"!service=%(Service)s",
"!node=%(Node)s",
"xml=%(XML)s"]) of

View File

@ -32,11 +32,13 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, stream_feature_register/2,
c2s_unauthenticated_packet/2, try_register/4,
c2s_unauthenticated_packet/2, try_register/4, try_register/5,
process_iq/1, send_registration_notifications/3,
mod_opt_type/1, mod_options/1, depends/2,
format_error/1, mod_doc/0]).
-deprecated({try_register, 4}).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("translate.hrl").
@ -283,7 +285,7 @@ try_register_or_set_password(User, Server, Password,
_ when CaptchaSucceed ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password, Source, Lang) of
case try_register(User, Server, Password, Source, ?MODULE, Lang) of
ok ->
xmpp:make_iq_result(IQ);
{error, Error} ->
@ -328,6 +330,13 @@ try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) ->
xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang))
end.
try_register(User, Server, Password, SourceRaw, Module) ->
Modules = mod_register_opt:allow_modules(Server),
case (Modules == all) orelse lists:member(Module, Modules) of
true -> try_register(User, Server, Password, SourceRaw);
false -> {error, eaccess}
end.
try_register(User, Server, Password, SourceRaw) ->
case jid:is_nodename(User) of
false ->
@ -363,8 +372,8 @@ try_register(User, Server, Password, SourceRaw) ->
end
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case try_register(User, Server, Password, SourceRaw) of
try_register(User, Server, Password, SourceRaw, Module, Lang) ->
case try_register(User, Server, Password, SourceRaw, Module) of
ok ->
JID = jid:make(User, Server),
Source = may_remove_resource(SourceRaw),
@ -597,6 +606,8 @@ mod_opt_type(access_from) ->
econf:acl();
mod_opt_type(access_remove) ->
econf:acl();
mod_opt_type(allow_modules) ->
econf:either(all, econf:list(econf:atom()));
mod_opt_type(captcha_protected) ->
econf:bool();
mod_opt_type(ip_access) ->
@ -623,6 +634,7 @@ mod_options(_Host) ->
[{access, all},
{access_from, none},
{access_remove, all},
{allow_modules, all},
{captcha_protected, false},
{ip_access, all},
{password_strength, 0},
@ -661,6 +673,13 @@ mod_doc() ->
desc =>
?T("Specify rules to restrict access for user unregistration. "
"By default any user is able to unregister their account.")}},
{allow_modules,
#{value => "all | [Module, ...]",
note => "added in 21.12",
desc =>
?T("List of modules that can register accounts, or 'all'. "
"The default value is 'all', which is equivalent to "
"something like '[mod_register, mod_register_web]'.")}},
{captcha_protected,
#{value => "true | false",
desc =>

View File

@ -6,6 +6,7 @@
-export([access/1]).
-export([access_from/1]).
-export([access_remove/1]).
-export([allow_modules/1]).
-export([captcha_protected/1]).
-export([ip_access/1]).
-export([password_strength/1]).
@ -31,6 +32,12 @@ access_remove(Opts) when is_map(Opts) ->
access_remove(Host) ->
gen_mod:get_module_opt(Host, mod_register, access_remove).
-spec allow_modules(gen_mod:opts() | global | binary()) -> 'all' | [atom()].
allow_modules(Opts) when is_map(Opts) ->
gen_mod:get_opt(allow_modules, Opts);
allow_modules(Host) ->
gen_mod:get_module_opt(Host, mod_register, allow_modules).
-spec captcha_protected(gen_mod:opts() | global | binary()) -> boolean().
captcha_protected(Opts) when is_map(Opts) ->
gen_mod:get_opt(captcha_protected, Opts);

View File

@ -85,7 +85,7 @@ process([Section],
process([<<"new">>],
#request{method = 'POST', q = Q, ip = {Ip, _Port},
lang = Lang, host = _HTTPHost}) ->
case form_new_post(Q) of
case form_new_post(Q, Ip) of
{success, ok, {Username, Host, _Password}} ->
Jid = jid:make(Username, Host),
mod_register:send_registration_notifications(?MODULE, Jid, Ip),
@ -290,10 +290,10 @@ form_new_get2(Host, Lang, CaptchaEls) ->
%%% Formulary new POST
%%%----------------------------------------------------------------------
form_new_post(Q) ->
form_new_post(Q, Ip) ->
case catch get_register_parameters(Q) of
[Username, Host, Password, Password, Id, Key] ->
form_new_post(Username, Host, Password, {Id, Key});
form_new_post(Username, Host, Password, {Id, Key}, Ip);
[_Username, _Host, _Password, _Password2, false, false] ->
{error, passwords_not_identical};
[_Username, _Host, _Password, _Password2, Id, Key] ->
@ -312,13 +312,12 @@ get_register_parameters(Q) ->
[<<"username">>, <<"host">>, <<"password">>, <<"password2">>,
<<"id">>, <<"key">>]).
form_new_post(Username, Host, Password,
{false, false}) ->
register_account(Username, Host, Password);
form_new_post(Username, Host, Password, {Id, Key}) ->
form_new_post(Username, Host, Password, {false, false}, Ip) ->
register_account(Username, Host, Password, Ip);
form_new_post(Username, Host, Password, {Id, Key}, Ip) ->
case ejabberd_captcha:check_captcha(Id, Key) of
captcha_valid ->
register_account(Username, Host, Password);
register_account(Username, Host, Password, Ip);
captcha_non_valid -> {error, captcha_non_valid};
captcha_not_found -> {error, captcha_non_valid}
end.
@ -502,11 +501,11 @@ form_del_get(Host, Lang) ->
{<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} |
%% @spec(Username, Host, Password, Ip) -> {success, ok, {Username, Host, Password} |
%% {success, exists, {Username, Host, Password}} |
%% {error, not_allowed} |
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
register_account(Username, Host, Password, Ip) ->
try mod_register_opt:access(Host) of
Access ->
case jid:make(Username, Host) of
@ -514,16 +513,15 @@ register_account(Username, Host, Password) ->
JID ->
case acl:match_rule(Host, Access, JID) of
deny -> {error, not_allowed};
allow -> register_account2(Username, Host, Password)
allow -> register_account2(Username, Host, Password, Ip)
end
end
catch _:{module_not_loaded, mod_register, _Host} ->
{error, host_unknown}
end.
register_account2(Username, Host, Password) ->
case ejabberd_auth:try_register(Username, Host,
Password)
register_account2(Username, Host, Password, Ip) ->
case mod_register:try_register(Username, Host, Password, Ip, ?MODULE)
of
ok ->
{success, ok, {Username, Host, Password}};
@ -579,12 +577,8 @@ get_error_text({error, exists}) ->
?T("The account already exists");
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}) ->
?T("Account doesn't exist");
get_error_text({error, account_exists}) ->
@ -594,7 +588,9 @@ get_error_text({error, password_not_changed}) ->
get_error_text({error, passwords_not_identical}) ->
?T("The passwords are different");
get_error_text({error, wrong_parameters}) ->
?T("Wrong parameters in the web formulary").
?T("Wrong parameters in the web formulary");
get_error_text({error, Why}) ->
mod_register:format_error(Why).
mod_options(_) ->
[].

View File

@ -80,9 +80,10 @@ get_roster(LUser, LServer) ->
[]
end,
GroupsDict = lists:foldl(fun({J, G}, Acc) ->
dict:append(J, G, Acc)
Gs = maps:get(J, Acc, []),
maps:put(J, [G | Gs], Acc)
end,
dict:new(), JIDGroups),
maps:new(), JIDGroups),
{ok, lists:flatmap(
fun(I) ->
case raw_to_record(LServer, I) of
@ -90,10 +91,7 @@ get_roster(LUser, LServer) ->
error -> [];
R ->
SJID = jid:encode(R#roster.jid),
Groups = case dict:find(SJID, GroupsDict) of
{ok, Gs} -> Gs;
error -> []
end,
Groups = maps:get(SJID, GroupsDict, []),
[R#roster{groups = Groups}]
end
end, Items)};

View File

@ -870,12 +870,15 @@ c2s_self_presence(Acc) ->
Acc.
-spec unset_presence(binary(), binary(), binary(), binary()) -> ok.
unset_presence(LUser, LServer, Resource, Status) ->
unset_presence(User, Server, Resource, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Resources = ejabberd_sm:get_user_resources(LUser,
LServer),
?DEBUG("Unset_presence for ~p @ ~p / ~p -> ~p "
"(~p resources)",
[LUser, LServer, Resource, Status, length(Resources)]),
[LUser, LServer, LResource, Status, length(Resources)]),
case length(Resources) of
0 ->
lists:foreach(

View File

@ -689,9 +689,9 @@ mod_doc() ->
?T("- Connection parameters: The module also accepts the "
"connection parameters, all of which default to the top-level "
"parameter of the same name, if unspecified. "
"See http://../database-ldap/#ldap-connection[LDAP Connection] "
"See http://../ldap/#ldap-connection[LDAP Connection] "
"section for more information about them."), "",
?T("Check also the http://../database-ldap/#configuration-examples"
?T("Check also the http://../ldap/#ldap-examples"
"[Configuration examples] section to get details about "
"retrieving the roster, "
"and configuration examples including Flat DIT and Deep DIT.")],
@ -721,13 +721,13 @@ mod_doc() ->
"name of roster entries (usually full names of people in "
"the roster). See also the parameters 'ldap_userdesc' and "
"'ldap_useruid'. For more information check the LDAP "
"http://../database-ldap/#filters[Filters] section.")}},
"http://../ldap/#filters[Filters] section.")}},
{ldap_filter,
#{desc =>
?T("Additional filter which is AND-ed together "
"with \"User Filter\" and \"Group Filter\". "
"For more information check the LDAP "
"http://../database-ldap/#filters[Filters] section.")}},
"http://../ldap/#filters[Filters] section.")}},
%% Attributes:
{ldap_groupattr,
#{desc =>
@ -785,7 +785,7 @@ mod_doc() ->
#{desc =>
?T("A regex for extracting user ID from the value of the "
"attribute named by 'ldap_memberattr'. Check the LDAP "
"http://../database-ldap/#control-parameters"
"http://../ldap/#control-parameters"
"[Control Parameters] section.")}},
{ldap_auth_check,
#{value => "true | false",

View File

@ -646,7 +646,7 @@ get_listener_ips(#{ip := {0, 0, 0, 0, 0, 0, 0, 1}} = Opts) ->
{undefined, get_turn_ipv6_addr(Opts)};
get_listener_ips(#{ip := {_, _, _, _} = IP}) ->
{IP, undefined};
get_listener_ips(#{ip := {_, _, _, _, _,_, _, _, _} = IP}) ->
get_listener_ips(#{ip := {_, _, _, _, _, _, _, _} = IP}) ->
{undefined, IP}.
-spec get_turn_ipv4_addr(map()) -> inet:ip4_address() | undefined.

View File

@ -40,7 +40,7 @@
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,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -432,6 +432,22 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
remove_expired_items(_Nidx, infinity) ->
{result, []};
remove_expired_items(Nidx, Seconds) ->
Items = mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx),
ExpT = misc:usec_to_now(
erlang:system_time(microsecond) - (Seconds * 1000000)),
ExpItems = lists:filtermap(
fun(#pubsub_item{itemid = {ItemId, _},
modification = {ModT, _}}) when ModT < ExpT ->
{true, ItemId};
(#pubsub_item{}) ->
false
end, Items),
del_items(Nidx, ExpItems),
{result, ExpItems}.
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>

View File

@ -43,7 +43,7 @@
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,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -285,6 +285,23 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
remove_expired_items(_Nidx, infinity) ->
{result, []};
remove_expired_items(Nidx, Seconds) ->
ExpT = encode_now(
misc:usec_to_now(
erlang:system_time(microsecond) - (Seconds * 1000000))),
case ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where nodeid=%(Nidx)d "
"and creation < %(ExpT)s")) of
{selected, RItems} ->
ItemIds = [ItemId || {ItemId} <- RItems],
del_items(Nidx, ItemIds),
{result, ItemIds};
_ ->
{result, []}
end.
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
SubKey = jid:tolower(Publisher),
GenKey = jid:remove_resource(SubKey),

View File

@ -36,7 +36,7 @@
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,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -81,10 +81,12 @@ features() ->
[<<"create-nodes">>,
<<"auto-create">>,
<<"auto-subscribe">>,
<<"config-node">>,
<<"delete-nodes">>,
<<"delete-items">>,
<<"filtered-notifications">>,
<<"modify-affiliations">>,
<<"multi-items">>,
<<"outcast-affiliation">>,
<<"persistent-items">>,
<<"publish">>,
@ -142,6 +144,9 @@ remove_extra_items(Nidx, MaxItems) ->
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
remove_expired_items(Nidx, Seconds) ->
node_flat:remove_expired_items(Nidx, Seconds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).

View File

@ -38,7 +38,7 @@
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,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@ -99,6 +99,9 @@ remove_extra_items(Nidx, MaxItems) ->
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
remove_expired_items(Nidx, Seconds) ->
node_flat_sql:remove_expired_items(Nidx, Seconds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).

View File

@ -118,7 +118,7 @@ eval_file(Path) ->
case luerl:eval(NewData, State1) of
{ok, _} = Res ->
Res;
{error, Why} = Err ->
{error, Why, _} = Err ->
?ERROR_MSG("Failed to eval ~ts: ~p", [Path, Why]),
Err
end;

View File

@ -191,13 +191,26 @@ base_url(Server, Path) ->
_ -> Url
end.
-ifdef(HAVE_URI_STRING).
uri_hack(Str) ->
case uri_string:normalize("%25") of
"%" -> % This hack around bug in httpc >21 <23.2
binary:replace(Str, <<"%25">>, <<"%2525">>, [global]);
_ -> Str
end.
-else.
uri_hack(Str) ->
Str.
-endif.
url(Url, []) ->
Url;
url(Url, Params) ->
L = [<<"&", (iolist_to_binary(Key))/binary, "=",
(misc:url_encode(Value))/binary>>
|| {Key, Value} <- Params],
<<$&, Encoded/binary>> = iolist_to_binary(L),
<<$&, Encoded0/binary>> = iolist_to_binary(L),
Encoded = uri_hack(Encoded0),
<<Url/binary, $?, Encoded/binary>>.
url(Server, Path, Params) ->
case binary:split(base_url(Server, Path), <<"?">>) of

View File

@ -224,13 +224,21 @@ get_items(Config, Version) ->
sub_els = [#roster_query{ver = Version}]}) of
#iq{type = result,
sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
{NewVersion, Items};
{NewVersion, normalize_items(Items)};
#iq{type = result, sub_els = []} ->
{empty, []};
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
normalize_items(Items) ->
Items2 =
lists:map(
fun(I) ->
I#roster_item{groups = lists:sort(I#roster_item.groups)}
end, Items),
lists:sort(Items2).
get_item(Config, JID) ->
case get_items(Config) of
{_Ver, Items} when is_list(Items) ->

View File

@ -15,69 +15,62 @@
INPUT=$1
if test -n ${BASH_VERSION:-''} ; then
get_random ()
{
R=$RANDOM
}
else
for n in `od -A n -t u2 -N 48 /dev/urandom`; do RL="$RL$n "; done
get_random ()
{
R=${RL%% *}
RL=${RL#* }
}
fi
for n in $(od -A n -t u2 -N 48 /dev/urandom); do RL="$RL$n "; done
get_random ()
{
R=${RL%% *}
RL=${RL#* }
}
get_random
WAVE1_AMPLITUDE=$((2 + $R % 5))
WAVE1_AMPLITUDE=$((2 + R % 5))
get_random
WAVE1_LENGTH=$((50 + $R % 25))
WAVE1_LENGTH=$((50 + R % 25))
get_random
WAVE2_AMPLITUDE=$((2 + $R % 5))
WAVE2_AMPLITUDE=$((2 + R % 5))
get_random
WAVE2_LENGTH=$((50 + $R % 25))
WAVE2_LENGTH=$((50 + R % 25))
get_random
WAVE3_AMPLITUDE=$((2 + $R % 5))
WAVE3_AMPLITUDE=$((2 + R % 5))
get_random
WAVE3_LENGTH=$((50 + $R % 25))
WAVE3_LENGTH=$((50 + R % 25))
get_random
W1_LINE_START_Y=$((10 + $R % 40))
W1_LINE_START_Y=$((10 + R % 40))
get_random
W1_LINE_STOP_Y=$((10 + $R % 40))
W1_LINE_STOP_Y=$((10 + R % 40))
get_random
W2_LINE_START_Y=$((10 + $R % 40))
W2_LINE_START_Y=$((10 + R % 40))
get_random
W2_LINE_STOP_Y=$((10 + $R % 40))
W2_LINE_STOP_Y=$((10 + R % 40))
get_random
W3_LINE_START_Y=$((10 + $R % 40))
W3_LINE_START_Y=$((10 + R % 40))
get_random
W3_LINE_STOP_Y=$((10 + $R % 40))
W3_LINE_STOP_Y=$((10 + R % 40))
get_random
B1_LINE_START_Y=$(($R % 40))
B1_LINE_START_Y=$((R % 40))
get_random
B1_LINE_STOP_Y=$(($R % 40))
B1_LINE_STOP_Y=$((R % 40))
get_random
B2_LINE_START_Y=$(($R % 40))
B2_LINE_START_Y=$((R % 40))
get_random
B2_LINE_STOP_Y=$(($R % 40))
#B3_LINE_START_Y=$(($R % 40))
#B3_LINE_STOP_Y=$(($R % 40))
B2_LINE_STOP_Y=$((R % 40))
#B3_LINE_START_Y=$((R % 40))
#B3_LINE_STOP_Y=$((R % 40))
get_random
B1_LINE_START_X=$(($R % 20))
B1_LINE_START_X=$((R % 20))
get_random
B1_LINE_STOP_X=$((100 + $R % 40))
B1_LINE_STOP_X=$((100 + R % 40))
get_random
B2_LINE_START_X=$(($R % 20))
B2_LINE_START_X=$((R % 20))
get_random
B2_LINE_STOP_X=$((100 + $R % 40))
#B3_LINE_START_X=$(($R % 20))
#B3_LINE_STOP_X=$((100 + $R % 40))
B2_LINE_STOP_X=$((100 + R % 40))
#B3_LINE_START_X=$((R % 20))
#B3_LINE_STOP_X=$((100 + R % 40))
get_random
ROLL_X=$(($R % 40))
ROLL_X=$((R % 40))
convert -size 180x60 xc:none -pointsize 40 \
\( -clone 0 -fill white \

View File

@ -51,9 +51,10 @@
{release, true}.
{release_dir, "${SCRIPT_DIR%/*}"}.
{sysconfdir, "{{release_dir}}/etc"}.
{erts_dir, "{{release_dir}}/erts-${ERTS_VSN#erts-}"}.
{installuser, "@INSTALLUSER@"}.
{erl, "{{release_dir}}/{{erts_vsn}}/bin/erl"}.
{epmd, "{{release_dir}}/{{erts_vsn}}/bin/epmd"}.
{erl, "{{erts_dir}}/bin/erl"}.
{epmd, "{{erts_dir}}/bin/epmd"}.
{localstatedir, "{{release_dir}}/var"}.
{libdir, "{{release_dir}}/lib"}.
{docdir, "{{release_dir}}/doc"}.