mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-22 16:20:52 +01:00
Merge branch 'processone:master' into master
This commit is contained in:
commit
03485f5e94
143
.github/workflows/ci.yml
vendored
143
.github/workflows/ci.yml
vendored
@ -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
4
.shellcheckrc
Normal file
@ -0,0 +1,4 @@
|
||||
disable=SC2016,SC2086,SC2089,SC2090
|
||||
external-sources=true
|
||||
source=ejabberdctl.cfg.example
|
||||
shell=sh
|
47
CHANGELOG.md
47
CHANGELOG.md
@ -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
|
||||
|
32
Makefile.in
32
Makefile.in
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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}"
|
||||
|
@ -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(),
|
||||
|
@ -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.
|
||||
|
@ -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
17
mix.exs
@ -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")
|
||||
|
22
mix.lock
22
mix.lock
@ -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"},
|
||||
}
|
||||
|
@ -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 d’accès de « autoriser »"}.
|
||||
{"Access model of open","Modèle d’accès de « ouvrir »"}.
|
||||
{"Access model of presence","Modèle d’accès de « présence »"}.
|
||||
{"Access model of roster","Modèle d’accès de « liste »"}.
|
||||
{"Access model of whitelist","Modèle d’accès de « liste blanche »"}.
|
||||
{"Access model","Modèle d’accès"}.
|
||||
{"Account doesn't exist","Le compte n’existe 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 l’abonnement"}.
|
||||
{"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 l’adhésion à un salon ; cela devrait être un nom distingué LDAP selon la définition spécifique à l’implémentation ou au déploiement d’un 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","N’importe qui dans le groupe de la liste spécifiée peut s’abonner et récupérer les items"}.
|
||||
{"Anyone may associate leaf nodes with the collection","N’importe qui peut associer les feuilles avec la collection"}.
|
||||
{"Anyone may publish","N’importe qui peut publier"}.
|
||||
{"Anyone may subscribe and retrieve items","N’importe qui peut s’abonner et récupérer les items"}.
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","N’importe qui avec un abonnement de présence peut s’abonner et récupérer les items"}.
|
||||
{"Anyone with Voice","N’importe qui avec Voice"}.
|
||||
{"Anyone","N’importe qui"}.
|
||||
{"April","Avril"}.
|
||||
{"Attribute 'channel' is required for this request","L’attribut « channel » est requis pour la requête"}.
|
||||
{"Attribute 'id' is mandatory for MIX messages","L’attribut « id » est obligatoire pour les messages MIX"}.
|
||||
{"Attribute 'jid' is not allowed here","L’attribut « jid » n’est pas autorisé ici"}.
|
||||
{"Attribute 'node' is not allowed here","L’attribut « node » n’est pas autorisé ici"}.
|
||||
{"Attribute 'to' of stanza that triggered challenge","L’attribut « 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 n’existe 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 n’existent 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 l’item 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"}.
|
||||
|
@ -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帐户注销成功."}.
|
||||
|
32
rebar.config
32
rebar.config
@ -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/"},
|
||||
|
@ -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,
|
||||
|
@ -89,7 +89,6 @@ Sys = [{lib_dirs, []},
|
||||
|
||||
Overlay = [
|
||||
{mkdir, "var/log/ejabberd"},
|
||||
{mkdir, "var/lock"},
|
||||
{mkdir, "var/lib/ejabberd"},
|
||||
{mkdir, "etc/ejabberd"},
|
||||
{mkdir, "doc"},
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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`);
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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"],
|
||||
|
@ -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),
|
||||
|
@ -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},
|
||||
|
@ -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"},
|
||||
|
@ -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} ->
|
||||
|
@ -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)
|
||||
|
@ -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().
|
||||
|
@ -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 =
|
||||
|
@ -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),
|
||||
|
@ -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()}]}.
|
||||
|
||||
|
@ -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"),
|
||||
|
@ -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
157
src/mod_conversejs.erl
Normal 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.")}}]
|
||||
}.
|
41
src/mod_conversejs_opt.erl
Normal file
41
src/mod_conversejs_opt.erl
Normal 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).
|
||||
|
@ -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").
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 =>
|
||||
|
@ -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
13
src/mod_muc_admin_opt.erl
Normal 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).
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
%%%===================================================================
|
||||
|
@ -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
|
||||
%%%-------------------------
|
||||
|
@ -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 =>
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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 =>
|
||||
|
@ -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);
|
||||
|
@ -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(_) ->
|
||||
[].
|
||||
|
@ -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)};
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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),
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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;
|
||||
|
15
src/rest.erl
15
src/rest.erl
@ -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
|
||||
|
@ -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) ->
|
||||
|
@ -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 \
|
||||
|
@ -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"}.
|
||||
|
Loading…
Reference in New Issue
Block a user