Merge branch 'processone:master' into master
This commit is contained in:
commit
03485f5e94
|
@ -25,12 +25,12 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
otp: ['19.3', '24.0']
|
otp: ['19.3', '24']
|
||||||
include:
|
include:
|
||||||
- otp: '19.3'
|
- otp: '19.3'
|
||||||
rebar: 2
|
rebar: 2
|
||||||
os: ubuntu-18.04
|
os: ubuntu-18.04
|
||||||
- otp: '24.0'
|
- otp: '24'
|
||||||
rebar: 3
|
rebar: 3
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
@ -44,9 +44,17 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
- 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
|
- name: Get previous Erlang/OTP
|
||||||
uses: ErlGang/setup-erlang@master
|
uses: ErlGang/setup-erlang@master
|
||||||
if: matrix.otp != 24.0
|
if: matrix.otp != 24
|
||||||
with:
|
with:
|
||||||
otp-version: ${{ matrix.otp }}
|
otp-version: ${{ matrix.otp }}
|
||||||
|
|
||||||
|
@ -81,7 +89,6 @@ jobs:
|
||||||
libsqlite3-dev libwebp-dev libyaml-dev
|
libsqlite3-dev libwebp-dev libyaml-dev
|
||||||
|
|
||||||
- name: Prepare rebar
|
- name: Prepare rebar
|
||||||
id: rebar
|
|
||||||
run: |
|
run: |
|
||||||
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
|
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
|
||||||
]}.' >>rebar.config
|
]}.' >>rebar.config
|
||||||
|
@ -93,23 +100,26 @@ jobs:
|
||||||
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
|
||||||
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
|
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
|
||||||
echo '{ct_extra_params, "-verbosity 20"}.' >>rebar.config
|
echo '{ct_extra_params, "-verbosity 20"}.' >>rebar.config
|
||||||
|
echo "{ct_opts, [{verbosity, 20}, {keep_logs, 20}]}." >>rebar.config
|
||||||
|
|
||||||
- name: Cache rebar2
|
- name: Cache rebar
|
||||||
if: matrix.rebar == 2
|
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
deps/
|
deps/
|
||||||
dialyzer/
|
dialyzer/
|
||||||
ebin/
|
ebin/
|
||||||
|
~/.cache/rebar3/
|
||||||
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
|
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
|
||||||
|
|
||||||
- name: Cache rebar3
|
- name: Download test logs
|
||||||
if: matrix.rebar == 3
|
if: matrix.otp == 24 && github.repository == 'processone/ejabberd'
|
||||||
uses: actions/cache@v2
|
continue-on-error: true
|
||||||
with:
|
run: |
|
||||||
path: ~/.cache/rebar3/
|
mkdir -p _build/test
|
||||||
key: ${{matrix.otp}}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
|
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
|
- name: Compile
|
||||||
run: |
|
run: |
|
||||||
|
@ -128,46 +138,50 @@ jobs:
|
||||||
- run: make hooks
|
- run: make hooks
|
||||||
- run: make options
|
- run: make options
|
||||||
- run: make xref
|
- run: make xref
|
||||||
- run: make dialyzer
|
- run: |
|
||||||
- run: make test
|
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
|
- name: Check results
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
[[ -d _build ]] && ln -s _build/test/logs/ logs \
|
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
|
||||||
&& ln `find _build/ -name "*dialyzer_warnings"` \
|
|
||||||
logs/dialyzer.log \
|
|
||||||
|| ln dialyzer/error.log logs/dialyzer.log
|
|
||||||
ln `find logs/ -name suite.log` logs/suite.log
|
ln `find logs/ -name suite.log` logs/suite.log
|
||||||
grep 'TEST COMPLETE' logs/suite.log
|
grep 'TEST COMPLETE' logs/suite.log
|
||||||
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
|
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
|
||||||
test $(find logs/ -empty -name error.log)
|
test $(find logs/ -empty -name error.log)
|
||||||
|
|
||||||
- name: View dialyzer report
|
- name: View logs failures
|
||||||
run: cat logs/dialyzer.log
|
|
||||||
|
|
||||||
- name: View full suite.log
|
|
||||||
run: cat logs/suite.log
|
|
||||||
|
|
||||||
- name: View suite.log failures
|
|
||||||
if: failure()
|
if: failure()
|
||||||
run: cat logs/suite.log | awk
|
run: |
|
||||||
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
|
cat logs/suite.log | awk \
|
||||||
|
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
|
||||||
- name: View full ejabberd.log
|
find logs/ -name error.log -exec cat '{}' ';'
|
||||||
if: failure()
|
find logs/ -name exunit.log -exec cat '{}' ';'
|
||||||
run: find logs/ -name ejabberd.log -exec cat '{}' ';'
|
|
||||||
|
|
||||||
- name: View exunit.log
|
|
||||||
if: failure()
|
|
||||||
run: find logs/ -name exunit.log -exec cat '{}' ';'
|
|
||||||
|
|
||||||
- name: Send to coveralls
|
- name: Send to coveralls
|
||||||
if: matrix.otp == 24.0
|
if: matrix.otp == 24
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
rebar3 as test coveralls send
|
DIAGNOSTIC=1 rebar3 as test coveralls send
|
||||||
curl -v -k https://coveralls.io/webhook \
|
curl -v -k https://coveralls.io/webhook \
|
||||||
--header "Content-Type: application/json" \
|
--header "Content-Type: application/json" \
|
||||||
--data '{"repo_name":"$GITHUB_REPOSITORY",
|
--data '{"repo_name":"$GITHUB_REPOSITORY",
|
||||||
|
@ -175,6 +189,61 @@ jobs:
|
||||||
"payload":{"build_num":$GITHUB_RUN_ID,
|
"payload":{"build_num":$GITHUB_RUN_ID,
|
||||||
"status":"done"}}'
|
"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:
|
binaries:
|
||||||
name: Binaries
|
name: Binaries
|
||||||
needs: [tests]
|
needs: [tests]
|
||||||
|
|
|
@ -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
|
# Version 21.07
|
||||||
|
|
||||||
Compilation
|
Compilation
|
||||||
|
|
32
Makefile.in
32
Makefile.in
|
@ -68,12 +68,6 @@ LUADIR = $(PRIVDIR)/lua
|
||||||
# /var/lib/ejabberd/
|
# /var/lib/ejabberd/
|
||||||
SPOOLDIR = $(DESTDIR)@localstatedir@/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/
|
# /var/log/ejabberd/
|
||||||
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
|
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
|
||||||
|
|
||||||
|
@ -100,8 +94,10 @@ endif
|
||||||
|
|
||||||
ifeq "$(MIX)" "mix"
|
ifeq "$(MIX)" "mix"
|
||||||
REBAR_VER:=6
|
REBAR_VER:=6
|
||||||
|
REBAR_VER_318:=0
|
||||||
else
|
else
|
||||||
REBAR_VER:=$(shell $(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}')
|
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
|
endif
|
||||||
|
|
||||||
ifeq "$(REBAR_VER)" "6"
|
ifeq "$(REBAR_VER)" "6"
|
||||||
|
@ -121,7 +117,11 @@ else
|
||||||
ifeq "$(REBAR_VER)" "3"
|
ifeq "$(REBAR_VER)" "3"
|
||||||
SKIPDEPS=
|
SKIPDEPS=
|
||||||
LISTDEPS=tree
|
LISTDEPS=tree
|
||||||
|
ifeq "$(REBAR_VER_318)" "1"
|
||||||
|
UPDATEDEPS=upgrade --all
|
||||||
|
else
|
||||||
UPDATEDEPS=upgrade
|
UPDATEDEPS=upgrade
|
||||||
|
endif
|
||||||
DEPSPATTERN="s/ (.*//; /^ / s/.* \([a-z0-9_]*\).*/\1/p;"
|
DEPSPATTERN="s/ (.*//; /^ / s/.* \([a-z0-9_]*\).*/\1/p;"
|
||||||
DEPSBASE=_build
|
DEPSBASE=_build
|
||||||
DEPSDIR=$(DEPSBASE)/default/lib
|
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)
|
$(call TO_DEST,priv/lua/redis_sm.lua): priv/lua/redis_sm.lua $(call TO_DEST,priv/lua)
|
||||||
$(INSTALL) -m 644 $< $@
|
$(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))
|
.PHONY: $(call TO_DEST,$(DEPS_FILES) $(MAIN_DIRS) $(DEPS_DIRS))
|
||||||
|
|
||||||
|
@ -298,7 +306,8 @@ install: copy-files
|
||||||
chmod 755 ejabberd.init
|
chmod 755 ejabberd.init
|
||||||
#
|
#
|
||||||
# Service script
|
# 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
|
> ejabberd.service
|
||||||
chmod 644 ejabberd.service
|
chmod 644 ejabberd.service
|
||||||
#
|
#
|
||||||
|
@ -306,12 +315,6 @@ install: copy-files
|
||||||
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
|
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
|
||||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
|
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
|
||||||
chmod -R 750 $(SPOOLDIR)
|
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
|
# Log directory
|
||||||
$(INSTALL) -d -m 750 $(O_USER) $(LOGDIR)
|
$(INSTALL) -d -m 750 $(O_USER) $(LOGDIR)
|
||||||
|
@ -361,7 +364,6 @@ uninstall-all: uninstall-binary
|
||||||
rm -rf $(ETCDIR)
|
rm -rf $(ETCDIR)
|
||||||
rm -rf $(EJABBERDDIR)
|
rm -rf $(EJABBERDDIR)
|
||||||
rm -rf $(SPOOLDIR)
|
rm -rf $(SPOOLDIR)
|
||||||
rm -rf $(CTLLOCKDIR)
|
|
||||||
rm -rf $(LOGDIR)
|
rm -rf $(LOGDIR)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
@ -4,8 +4,8 @@ After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
User=ejabberd
|
User=@installuser@
|
||||||
Group=ejabberd
|
Group=@installuser@
|
||||||
LimitNOFILE=65536
|
LimitNOFILE=65536
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
|
@ -12,6 +12,8 @@ ERLANG_NODE=ejabberd@localhost
|
||||||
# define default environment variables
|
# define default environment variables
|
||||||
[ -z "$SCRIPT" ] && SCRIPT=$0
|
[ -z "$SCRIPT" ] && SCRIPT=$0
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd -P)"
|
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd -P)"
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
ERTS_VSN="{{erts_vsn}}"
|
||||||
ERL="{{erl}}"
|
ERL="{{erl}}"
|
||||||
IEX="{{bindir}}/iex"
|
IEX="{{bindir}}/iex"
|
||||||
EPMD="{{epmd}}"
|
EPMD="{{epmd}}"
|
||||||
|
@ -62,6 +64,7 @@ done
|
||||||
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
|
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
|
||||||
# Allows passing extra Erlang command-line arguments in vm.args file
|
# Allows passing extra Erlang command-line arguments in vm.args file
|
||||||
: "${VMARGS:="$ETC_DIR/vm.args"}"
|
: "${VMARGS:="$ETC_DIR/vm.args"}"
|
||||||
|
# shellcheck source=ejabberdctl.cfg.example
|
||||||
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
||||||
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
|
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
|
||||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
||||||
|
@ -81,7 +84,7 @@ if [ -n "$INET_DIST_INTERFACE" ] ; then
|
||||||
fi
|
fi
|
||||||
# if vm.args file exists in config directory, pass it to Erlang VM
|
# if vm.args file exists in config directory, pass it to Erlang VM
|
||||||
[ -f "$VMARGS" ] && ERLANG_OPTS="$ERLANG_OPTS -args_file $VMARGS"
|
[ -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_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
|
||||||
ERL_INETRC="$ETC_DIR"/inetrc
|
ERL_INETRC="$ETC_DIR"/inetrc
|
||||||
|
|
||||||
|
@ -105,6 +108,7 @@ export ERL_MAX_ETS_TABLES
|
||||||
export CONTRIB_MODULES_PATH
|
export CONTRIB_MODULES_PATH
|
||||||
export CONTRIB_MODULES_CONF_DIR
|
export CONTRIB_MODULES_CONF_DIR
|
||||||
export ERL_LIBS
|
export ERL_LIBS
|
||||||
|
export SCRIPT_DIR
|
||||||
|
|
||||||
# run command either directly or via su $INSTALLUSER
|
# run command either directly or via su $INSTALLUSER
|
||||||
exec_cmd()
|
exec_cmd()
|
||||||
|
@ -128,14 +132,6 @@ exec_iex()
|
||||||
# usage
|
# usage
|
||||||
debugwarning()
|
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
|
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||||
echo "--------------------------------------------------------------------"
|
echo "--------------------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
|
@ -153,7 +149,7 @@ debugwarning()
|
||||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||||
echo "Press return to continue"
|
echo "Press return to continue"
|
||||||
read -r input
|
read -r _
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -176,7 +172,7 @@ livewarning()
|
||||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||||
echo "Press return to continue"
|
echo "Press return to continue"
|
||||||
read -r input
|
read -r _
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -206,8 +202,9 @@ help()
|
||||||
uid()
|
uid()
|
||||||
{
|
{
|
||||||
uuid=$(uuidgen 2>/dev/null)
|
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" ] && [ -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/')
|
uuid=$(printf '%s' $uuid | sed 's/^\(...\).*$/\1/')
|
||||||
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
|
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
|
||||||
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
|
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
|
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
|
||||||
mam = false :: boolean(),
|
mam = false :: boolean(),
|
||||||
pubsub = <<"">> :: binary(),
|
pubsub = <<"">> :: binary(),
|
||||||
|
enable_hats = false :: boolean(),
|
||||||
lang = ejabberd_option:language() :: binary()
|
lang = ejabberd_option:language() :: binary()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -124,6 +125,7 @@
|
||||||
history = #lqueue{} :: lqueue(),
|
history = #lqueue{} :: lqueue(),
|
||||||
subject = [] :: [text()],
|
subject = [] :: [text()],
|
||||||
subject_author = <<"">> :: binary(),
|
subject_author = <<"">> :: binary(),
|
||||||
|
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
|
||||||
just_created = erlang:system_time(microsecond) :: true | integer(),
|
just_created = erlang:system_time(microsecond) :: true | integer(),
|
||||||
activity = treap:empty() :: treap:treap(),
|
activity = treap:empty() :: treap:treap(),
|
||||||
room_shaper = none :: ejabberd_shaper:shaper(),
|
room_shaper = none :: ejabberd_shaper:shaper(),
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
-define(ERR_EXTENDED(E, C), mod_pubsub:extended_error(E, C)).
|
-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
|
%% 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.
|
%% this is currently a hard limit.
|
||||||
%% Would be nice to have it configurable.
|
%% Would be nice to have it configurable.
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
.\" Title: ejabberd.yml
|
.\" Title: ejabberd.yml
|
||||||
.\" Author: [see the "AUTHOR" section]
|
.\" Author: [see the "AUTHOR" section]
|
||||||
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
|
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
|
||||||
.\" Date: 07/21/2021
|
.\" Date: 12/08/2021
|
||||||
.\" Manual: \ \&
|
.\" Manual: \ \&
|
||||||
.\" Source: \ \&
|
.\" Source: \ \&
|
||||||
.\" Language: English
|
.\" Language: English
|
||||||
.\"
|
.\"
|
||||||
.TH "EJABBERD\&.YML" "5" "07/21/2021" "\ \&" "\ \&"
|
.TH "EJABBERD\&.YML" "5" "12/08/2021" "\ \&" "\ \&"
|
||||||
.\" -----------------------------------------------------------------
|
.\" -----------------------------------------------------------------
|
||||||
.\" * Define some portability stuff
|
.\" * Define some portability stuff
|
||||||
.\" -----------------------------------------------------------------
|
.\" -----------------------------------------------------------------
|
||||||
|
@ -82,7 +82,7 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
|
||||||
.sp
|
.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\&.
|
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
|
.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
|
.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\&.
|
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"
|
.SH "TOP LEVEL OPTIONS"
|
||||||
|
@ -316,14 +316,46 @@ means that the same username can be taken multiple times in anonymous login mode
|
||||||
.PP
|
.PP
|
||||||
\fBanonymous_protocol\fR: \fIlogin_anon | sasl_anon | both\fR
|
\fBanonymous_protocol\fR: \fIlogin_anon | sasl_anon | both\fR
|
||||||
.RS 4
|
.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
|
\fIlogin_anon\fR
|
||||||
means that the anonymous login method will be used\&.
|
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
|
\fIsasl_anon\fR
|
||||||
means that the SASL Anonymous method will be used\&.
|
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
|
.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
|
.PP
|
||||||
\fBapi_permissions\fR: \fI[Permission, \&.\&.\&.]\fR
|
\fBapi_permissions\fR: \fI[Permission, \&.\&.\&.]\fR
|
||||||
.RS 4
|
.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
|
This is used by the contributed module
|
||||||
\fIejabberd_auth_http\fR
|
\fIejabberd_auth_http\fR
|
||||||
that can be installed from the
|
that can be installed from the
|
||||||
\fIejabberd\-contrib\fR
|
ejabberd\-contrib
|
||||||
Git repository\&. Please refer to that module\(cqs README file for details\&.
|
Git repository\&. Please refer to that module\(cqs README file for details\&.
|
||||||
.RE
|
.RE
|
||||||
.sp
|
.sp
|
||||||
|
@ -383,14 +415,36 @@ Git repository\&. Please refer to that module\(cqs README file for details\&.
|
||||||
.PP
|
.PP
|
||||||
\fBauth_password_format\fR: \fIplain | scram\fR
|
\fBauth_password_format\fR: \fIplain | scram\fR
|
||||||
.RS 4
|
.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\&.
|
\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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
|
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
|
||||||
.RS 4
|
.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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBauth_use_cache\fR: \fItrue | false\fR
|
\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
|
\fBcache_life_time\fR: \fItimeout()\fR
|
||||||
.RS 4
|
.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
|
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,
|
\fIauth_cache_life_time\fR,
|
||||||
\fIoauth_cache_life_time\fR,
|
\fIoauth_cache_life_time\fR,
|
||||||
\fIrouter_cache_life_time\fR, and
|
\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
|
.PP
|
||||||
\fBcaptcha_cmd\fR: \fIPath\fR
|
\fBcaptcha_cmd\fR: \fIPath\fR
|
||||||
.RS 4
|
.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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBcaptcha_host\fR: \fIString\fR
|
\fBcaptcha_host\fR: \fIString\fR
|
||||||
|
@ -519,13 +575,17 @@ instead\&.
|
||||||
.PP
|
.PP
|
||||||
\fBcaptcha_limit\fR: \fIpos_integer() | infinity\fR
|
\fBcaptcha_limit\fR: \fIpos_integer() | infinity\fR
|
||||||
.RS 4
|
.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\&.
|
\fIinfinity\fR\&.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBcaptcha_url\fR: \fIURL\fR
|
\fBcaptcha_url\fR: \fIURL\fR
|
||||||
.RS 4
|
.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
|
\fIrequest_handlers\fR
|
||||||
for
|
for
|
||||||
\fIejabberd_http\fR
|
\fIejabberd_http\fR
|
||||||
|
@ -815,7 +875,8 @@ Path to the file that contains the JWK Key\&. The default value is
|
||||||
.RS 4
|
.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
|
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
|
\fIxml:lang\fR
|
||||||
attribute, the specified language is used\&.
|
attribute, the specified language is used\&. The default value is
|
||||||
|
\fI"en"\fR\&.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBldap_backups\fR: \fI[Host, \&.\&.\&.]\fR
|
\fBldap_backups\fR: \fI[Host, \&.\&.\&.]\fR
|
||||||
|
@ -948,7 +1009,11 @@ section for details\&.
|
||||||
\fBlog_rotate_count\fR: \fINumber\fR
|
\fBlog_rotate_count\fR: \fINumber\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
The number of rotated log files to keep\&. The default value is
|
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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBlog_rotate_size\fR: \fIpos_integer() | infinity\fR
|
\fBlog_rotate_size\fR: \fIpos_integer() | infinity\fR
|
||||||
|
@ -989,8 +1054,7 @@ seconds\&.
|
||||||
.RS 4
|
.RS 4
|
||||||
This option can be used to tune tick time parameter of
|
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
|
\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
|
\fI1 minute\fR\&.
|
||||||
minute\&.
|
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBnew_sql_schema\fR: \fItrue | false\fR
|
\fBnew_sql_schema\fR: \fItrue | false\fR
|
||||||
|
@ -998,7 +1062,7 @@ minute\&.
|
||||||
Whether to use
|
Whether to use
|
||||||
\fInew\fR
|
\fInew\fR
|
||||||
SQL schema\&. All schemas are located at
|
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
|
\fInew\fR
|
||||||
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
|
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
|
||||||
\fInew\fR
|
\fInew\fR
|
||||||
|
@ -1392,8 +1456,7 @@ if the latter is not set\&.
|
||||||
\fBs2s_timeout\fR: \fItimeout()\fR
|
\fBs2s_timeout\fR: \fItimeout()\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
A time to wait before closing an idle s2s connection\&. The default value is
|
A time to wait before closing an idle s2s connection\&. The default value is
|
||||||
\fI10\fR
|
\fI10 minutes\fR\&.
|
||||||
minutes\&.
|
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBs2s_tls_compression\fR: \fItrue | false\fR
|
\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\&.
|
\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
|
.RE
|
||||||
.sp
|
.sp
|
||||||
.it 1 an-trap
|
The module has no options\&.
|
||||||
.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
|
|
||||||
.sp
|
.sp
|
||||||
.it 1 an-trap
|
.it 1 an-trap
|
||||||
.nr an-no-space-flag 1
|
.nr an-no-space-flag 1
|
||||||
|
@ -1835,8 +1881,7 @@ access_rules:
|
||||||
vcard_set:
|
vcard_set:
|
||||||
\- allow: adminextraresource
|
\- allow: adminextraresource
|
||||||
modules:
|
modules:
|
||||||
mod_admin_extra:
|
mod_admin_extra: {}
|
||||||
module_resource: "modadminextraf8x,31ad"
|
|
||||||
mod_vcard:
|
mod_vcard:
|
||||||
access_set: vcard_set
|
access_set: vcard_set
|
||||||
.fi
|
.fi
|
||||||
|
@ -1884,7 +1929,7 @@ ejabberdctl srg\-create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g
|
||||||
.RE
|
.RE
|
||||||
.SS "mod_admin_update_sql"
|
.SS "mod_admin_update_sql"
|
||||||
.sp
|
.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
|
.sp
|
||||||
The module has no options\&.
|
The module has no options\&.
|
||||||
.SS "mod_announce"
|
.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\&.
|
The module provides server configuration functionality via XEP\-0050: Ad\-Hoc Commands\&. This module requires \fImod_adhoc\fR to be loaded\&.
|
||||||
.sp
|
.sp
|
||||||
The module has no options\&.
|
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"
|
.SS "mod_delegation"
|
||||||
.sp
|
.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\&.
|
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
|
modules: all
|
||||||
name: abuse\-addresses
|
name: abuse\-addresses
|
||||||
urls: [mailto:abuse@shakespeare\&.lit]
|
urls: ["mailto:abuse@shakespeare\&.lit"]
|
||||||
\-
|
\-
|
||||||
modules: [mod_muc]
|
modules: [mod_muc]
|
||||||
name: "Web chatroom logs"
|
name: "Web chatroom logs"
|
||||||
urls: [http://www\&.example\&.org/muc\-logs]
|
urls: ["http://www\&.example\&.org/muc\-logs"]
|
||||||
\-
|
\-
|
||||||
modules: [mod_disco]
|
modules: [mod_disco]
|
||||||
name: feedback\-addresses
|
name: feedback\-addresses
|
||||||
|
@ -2563,13 +2680,40 @@ The number of C2S authentication failures to trigger the IP ban\&. The default v
|
||||||
.sp
|
.sp
|
||||||
This module provides a ReST API to call ejabberd commands using JSON data\&.
|
This module provides a ReST API to call ejabberd commands using JSON data\&.
|
||||||
.sp
|
.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
|
.sp
|
||||||
To use a specific API version N, when defining the URL path in the request_handlers, add a \fIvN\fR\&. For example: \fI/api/v2: mod_http_api\fR
|
To use a specific API version N, when defining the URL path in the request_handlers, add a \fIvN\fR\&. For example: \fI/api/v2: mod_http_api\fR
|
||||||
.sp
|
.sp
|
||||||
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/<command_name>\fR
|
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/<command_name>\fR
|
||||||
.sp
|
.sp
|
||||||
The module has no options\&.
|
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"
|
.SS "mod_http_fileserver"
|
||||||
.sp
|
.sp
|
||||||
This simple module serves files from the local disk over HTTP\&.
|
This simple module serves files from the local disk over HTTP\&.
|
||||||
|
@ -2697,7 +2841,7 @@ modules:
|
||||||
.sp
|
.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\&.
|
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
|
.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
|
.sp
|
||||||
.it 1 an-trap
|
.it 1 an-trap
|
||||||
.nr an-no-space-flag 1
|
.nr an-no-space-flag 1
|
||||||
|
@ -2746,7 +2890,9 @@ This option defines the permission bits of uploaded files\&. The bits are specif
|
||||||
.PP
|
.PP
|
||||||
\fBget_url\fR: \fIURL\fR
|
\fBget_url\fR: \fIURL\fR
|
||||||
.RS 4
|
.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
|
\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
|
\fImod_http_upload\fR, the
|
||||||
\fIget_url\fR
|
\fIget_url\fR
|
||||||
|
@ -2795,7 +2941,7 @@ A name of the service in the Service Discovery\&. This will only be displayed by
|
||||||
.PP
|
.PP
|
||||||
\fBput_url\fR: \fIURL\fR
|
\fBput_url\fR: \fIURL\fR
|
||||||
.RS 4
|
.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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBrm_on_unregister\fR: \fItrue | false\fR
|
\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
|
.PP
|
||||||
\fBaccess_create\fR: \fIAccessName\fR
|
\fBaccess_create\fR: \fIAccessName\fR
|
||||||
.RS 4
|
.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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBaccess_mam\fR: \fIAccessName\fR
|
\fBaccess_mam\fR: \fIAccessName\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
To configure who is allowed to modify the
|
To configure who is allowed to modify the
|
||||||
\fImam\fR
|
\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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBaccess_persistent\fR: \fIAccessName\fR
|
\fBaccess_persistent\fR: \fIAccessName\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
To configure who is allowed to modify the
|
To configure who is allowed to modify the
|
||||||
\fIpersistent\fR
|
\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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBaccess_register\fR: \fIAccessName\fR
|
\fBaccess_register\fR: \fIAccessName\fR
|
||||||
|
@ -3825,7 +3974,8 @@ This option defines the number of service admins or room owners allowed to enter
|
||||||
.PP
|
.PP
|
||||||
\fBmax_users_presence\fR: \fINumber\fR
|
\fBmax_users_presence\fR: \fINumber\fR
|
||||||
.RS 4
|
.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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBmin_message_interval\fR: \fINumber\fR
|
\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
|
\fBping_ack_timeout\fR: \fItimeout()\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
How long to wait before deeming that a client has not answered a given server ping request\&. The default value is
|
How long to wait before deeming that a client has not answered a given server ping request\&. The default value is
|
||||||
\fI32\fR
|
\fIundefined\fR\&.
|
||||||
seconds\&.
|
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBping_interval\fR: \fItimeout()\fR
|
\fBping_interval\fR: \fItimeout()\fR
|
||||||
|
@ -4531,7 +4680,7 @@ means destroying the underlying connection,
|
||||||
\fInone\fR
|
\fInone\fR
|
||||||
means to do nothing\&. NOTE: when
|
means to do nothing\&. NOTE: when
|
||||||
\fImod_stream_mgmt\fR
|
\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\&.
|
\fInone\fR\&.
|
||||||
.RE
|
.RE
|
||||||
.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\&.
|
\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
|
.RE
|
||||||
.PP
|
.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
|
.RS 4
|
||||||
Define the maximum number of items that can be stored in a node\&. Default value is:
|
Define the maximum number of items that can be stored in a node\&. Default value is:
|
||||||
\fI10\fR\&.
|
\fI1000\fR\&.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBmax_nodes_discoitems\fR: \fIpos_integer() | infinity\fR
|
\fBmax_nodes_discoitems\fR: \fIpos_integer() | infinity\fR
|
||||||
|
@ -5437,9 +5592,9 @@ The module depends on \fImod_push\fR\&.
|
||||||
.PP
|
.PP
|
||||||
\fBresume_timeout\fR: \fItimeout()\fR
|
\fBresume_timeout\fR: \fItimeout()\fR
|
||||||
.RS 4
|
.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
|
\fImod_stream_mgmt\fR
|
||||||
module is restored\&. The default value is
|
is restored\&. The default value is
|
||||||
\fI72\fR
|
\fI72\fR
|
||||||
hours\&.
|
hours\&.
|
||||||
.RE
|
.RE
|
||||||
|
@ -5499,7 +5654,7 @@ Change the password from an existing account on the server\&.
|
||||||
Delete an existing account on the server\&.
|
Delete an existing account on the server\&.
|
||||||
.RE
|
.RE
|
||||||
.sp
|
.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
|
.sp
|
||||||
.it 1 an-trap
|
.it 1 an-trap
|
||||||
.nr an-no-space-flag 1
|
.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\&.
|
Specify rules to restrict access for user unregistration\&. By default any user is able to unregister their account\&.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.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
|
\fBcaptcha_protected\fR: \fItrue | false\fR
|
||||||
.RS 4
|
.RS 4
|
||||||
Protect registrations with CAPTCHA (see section
|
Protect registrations with
|
||||||
CAPTCHA
|
CAPTCHA\&. The default is
|
||||||
of the Configuration Guide)\&. The default is
|
|
||||||
\fIfalse\fR\&.
|
\fIfalse\fR\&.
|
||||||
.RE
|
.RE
|
||||||
.PP
|
.PP
|
||||||
|
@ -5551,7 +5713,8 @@ This option sets the minimum
|
||||||
Shannon entropy
|
Shannon entropy
|
||||||
for passwords\&. The value
|
for passwords\&. The value
|
||||||
\fIEntropy\fR
|
\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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBredirect_url\fR: \fIURL\fR
|
\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\&.
|
Unregister an existing account on the server\&.
|
||||||
.RE
|
.RE
|
||||||
.sp
|
.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
|
.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
|
.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
|
.sp
|
||||||
The module has no options\&.
|
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"
|
.SS "mod_roster"
|
||||||
.sp
|
.sp
|
||||||
This module implements roster management as defined in RFC6121 Section 2\&. The module also adds support for XEP\-0237: Roster Versioning\&.
|
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
|
.PP
|
||||||
\fBdb_type\fR: \fImnesia | sql\fR
|
\fBdb_type\fR: \fImnesia | sql\fR
|
||||||
.RS 4
|
.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
|
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, or
|
\fIdefault_db\fR
|
||||||
|
option, or
|
||||||
\fImnesia\fR
|
\fImnesia\fR
|
||||||
if omitted\&. If
|
if omitted\&. If
|
||||||
\fIsql\fR
|
\fIsql\fR
|
||||||
|
@ -6521,7 +6712,8 @@ minute\&.
|
||||||
.RS 4
|
.RS 4
|
||||||
Same as top\-level
|
Same as top\-level
|
||||||
\fIcache_life_time\fR
|
\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
|
.RE
|
||||||
.PP
|
.PP
|
||||||
\fBcache_size\fR: \fIpos_integer() | infinity\fR
|
\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
|
\fBcredentials_lifetime\fR: \fItimeout()\fR
|
||||||
.RS 4
|
.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
|
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
|
\fI12 hours\fR\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
|
||||||
hours\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
|
|
||||||
\fIsecret\fR
|
\fIsecret\fR
|
||||||
is specified (see below)\&.
|
is specified (see below)\&.
|
||||||
.RE
|
.RE
|
||||||
|
@ -7121,7 +7312,7 @@ The module depends on \fImod_vcard\fR\&.
|
||||||
.ps -1
|
.ps -1
|
||||||
.br
|
.br
|
||||||
.sp
|
.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
|
.sp .5v
|
||||||
.RE
|
.RE
|
||||||
.sp
|
.sp
|
||||||
|
@ -7189,13 +7380,13 @@ TODO
|
||||||
ProcessOne\&.
|
ProcessOne\&.
|
||||||
.SH "VERSION"
|
.SH "VERSION"
|
||||||
.sp
|
.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"
|
.SH "REPORTING BUGS"
|
||||||
.sp
|
.sp
|
||||||
Report bugs to https://github\&.com/processone/ejabberd/issues
|
Report bugs to https://github\&.com/processone/ejabberd/issues
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
.sp
|
.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
|
.sp
|
||||||
Main site: https://ejabberd\&.im
|
Main site: https://ejabberd\&.im
|
||||||
.sp
|
.sp
|
||||||
|
|
17
mix.exs
17
mix.exs
|
@ -24,6 +24,7 @@ defmodule Ejabberd.MixProject do
|
||||||
case config(:vsn) do
|
case config(:vsn) do
|
||||||
:false -> "0.0.0" # ./configure wasn't run: vars.config not created
|
:false -> "0.0.0" # ./configure wasn't run: vars.config not created
|
||||||
'0.0' -> "0.0.0" # the full git repository wasn't downloaded
|
'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 ->
|
[_, _, ?., _, _] = x ->
|
||||||
head = String.replace(:erlang.list_to_binary(x), ~r/0+([0-9])/, "\\1")
|
head = String.replace(:erlang.list_to_binary(x), ~r/0+([0-9])/, "\\1")
|
||||||
<<head::binary, ".0">>
|
<<head::binary, ".0">>
|
||||||
|
@ -87,6 +88,7 @@ defmodule Ejabberd.MixProject do
|
||||||
if_version_below('23', [{:d, :USE_OLD_PG2}]) ++
|
if_version_below('23', [{:d, :USE_OLD_PG2}]) ++
|
||||||
if_version_below('24', [{:d, :COMPILER_REPORTS_ONLY_LINES}]) ++
|
if_version_below('24', [{:d, :COMPILER_REPORTS_ONLY_LINES}]) ++
|
||||||
if_version_below('24', [{:d, :SYSTOOLS_APP_DEF_WITHOUT_OPTIONAL}]) ++
|
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}])
|
if_function_exported(:erl_error, :format_exception, 6, [{:d, :HAVE_ERL_ERROR}])
|
||||||
defines = for {:d, value} <- result, do: {:d, value}
|
defines = for {:d, value} <- result, do: {:d, value}
|
||||||
result ++ [{:d, :ALL_DEFS, defines}]
|
result ++ [{:d, :ALL_DEFS, defines}]
|
||||||
|
@ -102,7 +104,7 @@ defmodule Ejabberd.MixProject do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp deps do
|
defp deps do
|
||||||
[{:base64url, "~> 0.0.1"},
|
[{:base64url, "~> 1.0"},
|
||||||
{:cache_tab, "~> 1.0"},
|
{:cache_tab, "~> 1.0"},
|
||||||
{:distillery, "~> 2.0"},
|
{:distillery, "~> 2.0"},
|
||||||
{:eimp, "~> 1.0"},
|
{:eimp, "~> 1.0"},
|
||||||
|
@ -113,7 +115,7 @@ defmodule Ejabberd.MixProject do
|
||||||
{:fast_yaml, "~> 1.0"},
|
{:fast_yaml, "~> 1.0"},
|
||||||
{:idna, "~> 6.0"},
|
{:idna, "~> 6.0"},
|
||||||
{:jiffy, "~> 1.0.5"},
|
{:jiffy, "~> 1.0.5"},
|
||||||
{:jose, "~> 1.8"},
|
{:jose, "~> 1.11.1"},
|
||||||
{:lager, "~> 3.9.1"},
|
{:lager, "~> 3.9.1"},
|
||||||
{:mqtree, "~> 1.0"},
|
{:mqtree, "~> 1.0"},
|
||||||
{:p1_acme, "~> 1.0"},
|
{:p1_acme, "~> 1.0"},
|
||||||
|
@ -124,7 +126,7 @@ defmodule Ejabberd.MixProject do
|
||||||
{:pkix, "~> 1.0"},
|
{:pkix, "~> 1.0"},
|
||||||
{:stringprep, ">= 1.0.26"},
|
{:stringprep, ">= 1.0.26"},
|
||||||
{:stun, "~> 1.0"},
|
{:stun, "~> 1.0"},
|
||||||
{:xmpp, git: "https://github.com/processone/xmpp", ref: "e943c0285aa85e3cbd4bfb9259f6b7de32b00395", override: true},
|
{:xmpp, "~> 1.5"},
|
||||||
{:yconf, "~> 1.0"}]
|
{:yconf, "~> 1.0"}]
|
||||||
++ cond_deps()
|
++ cond_deps()
|
||||||
end
|
end
|
||||||
|
@ -145,7 +147,7 @@ defmodule Ejabberd.MixProject do
|
||||||
for {:true, dep} <- [{config(:pam), {:epam, "~> 1.0"}},
|
for {:true, dep} <- [{config(:pam), {:epam, "~> 1.0"}},
|
||||||
{config(:redis), {:eredis, "~> 1.2.0"}},
|
{config(:redis), {:eredis, "~> 1.2.0"}},
|
||||||
{config(:zlib), {:ezlib, "~> 1.0"}},
|
{config(:zlib), {:ezlib, "~> 1.0"}},
|
||||||
{config(:lua), {:luerl, "~> 0.3.1"}},
|
{config(:lua), {:luerl, "~> 1.0"}},
|
||||||
{config(:sqlite), {:sqlite3, "~> 1.1"}}], do:
|
{config(:sqlite), {:sqlite3, "~> 1.1"}}], do:
|
||||||
dep
|
dep
|
||||||
end
|
end
|
||||||
|
@ -213,6 +215,7 @@ defmodule Ejabberd.MixProject do
|
||||||
epmd: config(:epmd),
|
epmd: config(:epmd),
|
||||||
bindir: Path.join([config(:release_dir), "releases", version()]),
|
bindir: Path.join([config(:release_dir), "releases", version()]),
|
||||||
release_dir: config(:release_dir),
|
release_dir: config(:release_dir),
|
||||||
|
erts_dir: config(:erts_dir),
|
||||||
erts_vsn: "erts-#{release.erts_version}"
|
erts_vsn: "erts-#{release.erts_version}"
|
||||||
]
|
]
|
||||||
ro = "rel/overlays"
|
ro = "rel/overlays"
|
||||||
|
@ -238,7 +241,9 @@ defmodule Ejabberd.MixProject do
|
||||||
|
|
||||||
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.template > ejabberdctl.example1")
|
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.template > ejabberdctl.example1")
|
||||||
Mix.Generator.copy_template("ejabberdctl.example1", "ejabberdctl.example2", assigns)
|
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|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")
|
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)
|
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.example1")
|
||||||
File.rm("ejabberdctl.example2")
|
File.rm("ejabberdctl.example2")
|
||||||
|
File.rm("ejabberdctl.example2a")
|
||||||
|
File.rm("ejabberdctl.example2b")
|
||||||
File.rm("ejabberdctl.example3")
|
File.rm("ejabberdctl.example3")
|
||||||
File.rm("ejabberdctl.example4")
|
File.rm("ejabberdctl.example4")
|
||||||
File.rm("ejabberdctl.example5")
|
File.rm("ejabberdctl.example5")
|
||||||
|
|
22
mix.lock
22
mix.lock
|
@ -1,36 +1,36 @@
|
||||||
%{
|
%{
|
||||||
"artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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.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"},
|
"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"},
|
"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_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"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"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": {: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"},
|
"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"},
|
"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"},
|
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
|
||||||
"p1_acme": {:hex, :p1_acme, "1.0.13", "fec71df416004ce49e295f4846fe5ba3478b41fbe4f73a06b4a8fbc967d6e659", [:rebar3], [{:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.12", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "a2ce9d4904304df020c8e92e8577e0fc88f32623540656317c7e25440b4ac8d2"},
|
"p1_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_mysql": {:hex, :p1_mysql, "1.0.19", "22f1be58397780a7d580a954e7af66cde32a29dee1a24ab2aa196272fc654a4a", [:rebar3], [], "hexpm", "88f6cdb510e8959c14b6ae84ccda04967e3de239228f859d8341da67949622b1"},
|
||||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.10", "09ba1fbd447b1f480b223903e36d0415f21be592a1b00db964eea01285749028", [:rebar3], [], "hexpm", "c79cb61ababee4a8c85409b7f4932035797c093aeef1f9f53985e512b26f2a64"},
|
"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_pgsql": {:hex, :p1_pgsql, "1.1.12", "10ae79eeb35ea98c0424a8b6420542fef9e4469eb12ccf41475d10840c291e68", [:rebar3], [], "hexpm", "32203f779e01cf0353270df24833a1d831ad7cb3e3e8e35a7556dfa1f40948d5"},
|
||||||
"p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"},
|
"p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"},
|
||||||
"pkix": {:hex, :pkix, "1.0.8", "98ea05243847fd4504f7c7a0cd82cecd1010ac327a082e1c674c5384006eae75", [:rebar3], [], "hexpm", "399508819501fab9d2e586dfa601b5ee3ef22b5612d3db58204dd2d089ef45d7"},
|
"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"},
|
"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"},
|
"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"},
|
"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)"}.
|
{" (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 : "}.
|
{" has set the subject to: "," a défini le sujet sur : "}.
|
||||||
|
{"# participants","# participants"}.
|
||||||
{"A description of the node","Une description du nœud"}.
|
{"A description of the node","Une description du nœud"}.
|
||||||
{"A friendly name for the node","Un nom convivial pour le 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 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"}.
|
{"Accept","Accepter"}.
|
||||||
{"Access denied by service policy","L'accès au service est refusé"}.
|
{"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"}.
|
{"Action on user","Action sur l'utilisateur"}.
|
||||||
{"Add Jabber ID","Ajouter un Jabber ID"}.
|
{"Add Jabber ID","Ajouter un Jabber ID"}.
|
||||||
{"Add New","Ajouter"}.
|
{"Add New","Ajouter"}.
|
||||||
|
@ -19,7 +28,9 @@
|
||||||
{"Administrator privileges required","Les droits d'administrateur sont nécessaires"}.
|
{"Administrator privileges required","Les droits d'administrateur sont nécessaires"}.
|
||||||
{"All activity","Toute activité"}.
|
{"All activity","Toute activité"}.
|
||||||
{"All Users","Tous les utilisateurs"}.
|
{"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 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 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 query other users","Autoriser les utilisateurs à envoyer des requêtes aux autres utilisateurs"}.
|
||||||
{"Allow users to send invites","Autoriser les utilisateurs à envoyer des invitations"}.
|
{"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 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 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'"}.
|
{"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"}.
|
{"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"}.
|
{"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"}.
|
{"August","Août"}.
|
||||||
{"Automatic node creation is not enabled","La creation implicite de nœud n'est pas disponible"}.
|
{"Automatic node creation is not enabled","La creation implicite de nœud n'est pas disponible"}.
|
||||||
{"Backup Management","Gestion des sauvegardes"}.
|
{"Backup Management","Gestion des sauvegardes"}.
|
||||||
|
@ -43,10 +71,14 @@
|
||||||
{"Cannot remove active list","La liste active ne peut être supprimée"}.
|
{"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"}.
|
{"Cannot remove default list","La liste par défaut ne peut être supprimée"}.
|
||||||
{"CAPTCHA web page","Page web de CAPTCHA"}.
|
{"CAPTCHA web page","Page web de CAPTCHA"}.
|
||||||
|
{"Challenge ID","Identifiant du défi"}.
|
||||||
{"Change Password","Modifier le mot de passe"}.
|
{"Change Password","Modifier le mot de passe"}.
|
||||||
{"Change User Password","Changer le mot de passe de l'utilisateur"}.
|
{"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 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"}.
|
{"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 :"}.
|
{"Characters not allowed:","Caractères non autorisés :"}.
|
||||||
{"Chatroom configuration modified","Configuration du salon modifiée"}.
|
{"Chatroom configuration modified","Configuration du salon modifiée"}.
|
||||||
{"Chatroom is created","Le salon de discussion est créé"}.
|
{"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 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é."}.
|
{"Choose whether to approve this entity's subscription.","Choisissez d'approuver ou non l'abonnement de cette entité."}.
|
||||||
{"City","Ville"}.
|
{"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"}.
|
{"Commands","Commandes"}.
|
||||||
{"Conference room does not exist","Le salon de discussion n'existe pas"}.
|
{"Conference room does not exist","Le salon de discussion n'existe pas"}.
|
||||||
{"Configuration of room ~s","Configuration pour le salon ~s"}.
|
{"Configuration of room ~s","Configuration pour le salon ~s"}.
|
||||||
{"Configuration","Configuration"}.
|
{"Configuration","Configuration"}.
|
||||||
{"Connected Resources:","Ressources connectées :"}.
|
{"Connected Resources:","Ressources connectées :"}.
|
||||||
|
{"Contact Addresses (normally, room owner or owners)","Adresses de contact (normalement les administrateurs du salon)"}.
|
||||||
{"Country","Pays"}.
|
{"Country","Pays"}.
|
||||||
{"CPU Time:","Temps CPU :"}.
|
{"CPU Time:","Temps CPU :"}.
|
||||||
|
{"Current Discussion Topic","Sujet de discussion courant"}.
|
||||||
{"Database failure","Échec sur la base de données"}.
|
{"Database failure","Échec sur la base de données"}.
|
||||||
{"Database Tables at ~p","Tables de base de données sur ~p"}.
|
{"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 Tables Configuration at ","Configuration des tables de base de données sur "}.
|
||||||
{"Database","Base de données"}.
|
{"Database","Base de données"}.
|
||||||
{"December","Décembre"}.
|
{"December","Décembre"}.
|
||||||
{"Default users as participants","Les utilisateurs sont participant par défaut"}.
|
{"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 on all hosts","Supprimer le message du jour sur tous les domaines"}.
|
||||||
{"Delete message of the day","Supprimer le message du jour"}.
|
{"Delete message of the day","Supprimer le message du jour"}.
|
||||||
{"Delete Selected","Suppression des éléments sélectionnés"}.
|
{"Delete Selected","Suppression des éléments sélectionnés"}.
|
||||||
|
{"Delete table","Supprimer la table"}.
|
||||||
{"Delete User","Supprimer l'utilisateur"}.
|
{"Delete User","Supprimer l'utilisateur"}.
|
||||||
{"Deliver event notifications","Envoyer les notifications d'événement"}.
|
{"Deliver event notifications","Envoyer les notifications d'événement"}.
|
||||||
{"Deliver payloads with event notifications","Inclure le contenu du message avec la notification"}.
|
{"Deliver payloads with event notifications","Inclure le contenu du message avec la notification"}.
|
||||||
{"Description:","Description :"}.
|
{"Description:","Description :"}.
|
||||||
{"Disc only copy","Copie sur disque uniquement"}.
|
{"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 :"}.
|
{"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 Backup to Text File at ","Enregistrer la sauvegarde dans un fichier texte sur "}.
|
||||||
{"Dump to Text File","Sauvegarder dans un fichier texte"}.
|
{"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"}.
|
{"Edit Properties","Modifier les propriétés"}.
|
||||||
{"Either approve or decline the voice request.","Accepter ou refuser la demande de voix."}.
|
{"Either approve or decline the voice request.","Accepter ou refuser la demande de voix."}.
|
||||||
{"ejabberd MUC module","Module MUC ejabberd"}.
|
{"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 parse HTTP response","Échec de lecture de la réponse HTTP"}.
|
||||||
{"Failed to process option '~s'","Échec de traitement de l'option '~s'"}.
|
{"Failed to process option '~s'","Échec de traitement de l'option '~s'"}.
|
||||||
{"Family Name","Nom de famille"}.
|
{"Family Name","Nom de famille"}.
|
||||||
|
{"FAQ Entry","Entrée FAQ"}.
|
||||||
{"February","Février"}.
|
{"February","Février"}.
|
||||||
{"File larger than ~w bytes","Taille de fichier suppérieur à ~w octets"}.
|
{"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"}.
|
{"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"}.
|
{"July","Juillet"}.
|
||||||
{"June","Juin"}.
|
{"June","Juin"}.
|
||||||
{"Just created","Vient d'être créé"}.
|
{"Just created","Vient d'être créé"}.
|
||||||
|
{"Label:","Étiquette :"}.
|
||||||
{"Last Activity","Dernière activité"}.
|
{"Last Activity","Dernière activité"}.
|
||||||
{"Last login","Dernière connexion"}.
|
{"Last login","Dernière connexion"}.
|
||||||
{"Last month","Dernier mois"}.
|
{"Last month","Dernier mois"}.
|
||||||
|
@ -192,7 +235,6 @@
|
||||||
{"Make room public searchable","Rendre le salon public"}.
|
{"Make room public searchable","Rendre le salon public"}.
|
||||||
{"Malformed username","Nom d'utilisateur invalide"}.
|
{"Malformed username","Nom d'utilisateur invalide"}.
|
||||||
{"March","Mars"}.
|
{"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"}.
|
{"Max payload size in bytes","Taille maximum pour le contenu du message en octet"}.
|
||||||
{"Maximum file size","Taille maximale du fichier"}.
|
{"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"}.
|
{"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 connectés :"}.
|
||||||
{"Online Users","Utilisateurs en ligne"}.
|
{"Online Users","Utilisateurs en ligne"}.
|
||||||
{"Online","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 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 <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"}.
|
{"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 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 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 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"}.
|
{"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 Name","Nom de l'organisation"}.
|
||||||
{"Organization Unit","Unité de l'organisation"}.
|
{"Organization Unit","Unité de l'organisation"}.
|
||||||
|
@ -290,6 +334,7 @@
|
||||||
{"Outgoing s2s Connections:","Connexions s2s sortantes :"}.
|
{"Outgoing s2s Connections:","Connexions s2s sortantes :"}.
|
||||||
{"Owner privileges required","Les droits de propriétaire sont nécessaires"}.
|
{"Owner privileges required","Les droits de propriétaire sont nécessaires"}.
|
||||||
{"Packet","Paquet"}.
|
{"Packet","Paquet"}.
|
||||||
|
{"Participant","Participant"}.
|
||||||
{"Password Verification","Vérification du mot de passe"}.
|
{"Password Verification","Vérification du mot de passe"}.
|
||||||
{"Password Verification:","Vérification du mot de passe :"}.
|
{"Password Verification:","Vérification du mot de passe :"}.
|
||||||
{"Password","Mot de passe"}.
|
{"Password","Mot de passe"}.
|
||||||
|
@ -323,6 +368,9 @@
|
||||||
{"Remove User","Supprimer l'utilisateur"}.
|
{"Remove User","Supprimer l'utilisateur"}.
|
||||||
{"Remove","Supprimer"}.
|
{"Remove","Supprimer"}.
|
||||||
{"Replaced by new connection","Remplacé par une nouvelle connexion"}.
|
{"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"}.
|
{"Resources","Ressources"}.
|
||||||
{"Restart Service","Redémarrer le service"}.
|
{"Restart Service","Redémarrer le service"}.
|
||||||
{"Restart","Redémarrer"}.
|
{"Restart","Redémarrer"}.
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
|
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
|
||||||
|
|
||||||
{" (Add * to the end of field to match substring)"," (在字段末添加*来匹配子串)"}.
|
{" (Add * to the end of field to match substring)"," (在字段末添加*来匹配子串)"}.
|
||||||
{" has set the subject to: ","已将标题设置为: "}.
|
{" has set the subject to: "," 已将标题设置为: "}.
|
||||||
{"# participants","# 个参与人"}.
|
{"# participants","# 个参与人"}.
|
||||||
{"A description of the node","该节点的描述"}.
|
{"A description of the node","该节点的描述"}.
|
||||||
{"A friendly name for the node","该节点的友好名称"}.
|
{"A friendly name for the node","该节点的友好名称"}.
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
{"Add Jabber ID","添加Jabber ID"}.
|
{"Add Jabber ID","添加Jabber ID"}.
|
||||||
{"Add New","添加新用户"}.
|
{"Add New","添加新用户"}.
|
||||||
{"Add User","添加用户"}.
|
{"Add User","添加用户"}.
|
||||||
{"Administration of ","管理"}.
|
{"Administration of ","管理 "}.
|
||||||
{"Administration","管理"}.
|
{"Administration","管理"}.
|
||||||
{"Administrator privileges required","需要管理员权限"}.
|
{"Administrator privileges required","需要管理员权限"}.
|
||||||
{"All activity","所有活动"}.
|
{"All activity","所有活动"}.
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
{"Automatic node creation is not enabled","未启用自动节点创建"}.
|
{"Automatic node creation is not enabled","未启用自动节点创建"}.
|
||||||
{"Backup Management","备份管理"}.
|
{"Backup Management","备份管理"}.
|
||||||
{"Backup of ~p","~p的备份"}.
|
{"Backup of ~p","~p的备份"}.
|
||||||
{"Backup to File at ","备份文件位于"}.
|
{"Backup to File at ","备份文件位于 "}.
|
||||||
{"Backup","备份"}.
|
{"Backup","备份"}.
|
||||||
{"Bad format","格式错误"}.
|
{"Bad format","格式错误"}.
|
||||||
{"Birthday","出生日期"}.
|
{"Birthday","出生日期"}.
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
{"Current Discussion Topic","当前讨论话题"}.
|
{"Current Discussion Topic","当前讨论话题"}.
|
||||||
{"Database failure","数据库失败"}.
|
{"Database failure","数据库失败"}.
|
||||||
{"Database Tables at ~p","位于~p的数据库表"}.
|
{"Database Tables at ~p","位于~p的数据库表"}.
|
||||||
{"Database Tables Configuration at ","数据库表格配置位于"}.
|
{"Database Tables Configuration at ","数据库表格配置位于 "}.
|
||||||
{"Database","数据库"}.
|
{"Database","数据库"}.
|
||||||
{"December","十二月"}.
|
{"December","十二月"}.
|
||||||
{"Default users as participants","用户默认被视为参与人"}.
|
{"Default users as participants","用户默认被视为参与人"}.
|
||||||
|
@ -119,12 +119,12 @@
|
||||||
{"'Displayed groups' not added (they do not exist!): ","'显示的群组' 未被添加 (它们不存在!): "}.
|
{"'Displayed groups' not added (they do not exist!): ","'显示的群组' 未被添加 (它们不存在!): "}.
|
||||||
{"Displayed:","已显示:"}.
|
{"Displayed:","已显示:"}.
|
||||||
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","不要将密码告诉任何人, 就算是XMPP服务器的管理员也不可以."}.
|
{"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","转储到文本文件"}.
|
{"Dump to Text File","转储到文本文件"}.
|
||||||
{"Duplicated groups are not allowed by RFC6121","按照RFC6121的规则,不允许有重复的群组"}.
|
{"Duplicated groups are not allowed by RFC6121","按照RFC6121的规则,不允许有重复的群组"}.
|
||||||
{"Dynamically specify a replyto of the item publisher","为项目发布者动态指定一个 replyto"}.
|
{"Dynamically specify a replyto of the item publisher","为项目发布者动态指定一个 replyto"}.
|
||||||
{"Edit Properties","编辑属性"}.
|
{"Edit Properties","编辑属性"}.
|
||||||
{"Either approve or decline the voice request.","接受或拒绝声音请求"}.
|
{"Either approve or decline the voice request.","接受或拒绝声音请求."}.
|
||||||
{"ejabberd HTTP Upload service","ejabberd HTTP 上传服务"}.
|
{"ejabberd HTTP Upload service","ejabberd HTTP 上传服务"}.
|
||||||
{"ejabberd MUC module","ejabberd MUC 模块"}.
|
{"ejabberd MUC module","ejabberd MUC 模块"}.
|
||||||
{"ejabberd Multicast service","ejabberd多重映射服务"}.
|
{"ejabberd Multicast service","ejabberd多重映射服务"}.
|
||||||
|
@ -194,10 +194,10 @@
|
||||||
{"Import Directory","导入目录"}.
|
{"Import Directory","导入目录"}.
|
||||||
{"Import File","导入文件"}.
|
{"Import File","导入文件"}.
|
||||||
{"Import user data from jabberd14 spool file:","从 jabberd14 Spool 文件导入用户数据:"}.
|
{"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 a PIEFXIS file (XEP-0227):","从 PIEFXIS 文件 (XEP-0227) 导入用户数据:"}.
|
||||||
{"Import users data from jabberd14 spool directory:","从jabberd14 Spool目录导入用户数据:"}.
|
{"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 文件导入用户"}.
|
{"Import Users From jabberd14 Spool Files","从 jabberd14 Spool 文件导入用户"}.
|
||||||
{"Improper domain part of 'from' attribute","不恰当的'from'属性域名部分"}.
|
{"Improper domain part of 'from' attribute","不恰当的'from'属性域名部分"}.
|
||||||
{"Improper message type","不恰当的消息类型"}.
|
{"Improper message type","不恰当的消息类型"}.
|
||||||
|
@ -249,7 +249,6 @@
|
||||||
{"Malformed username","用户名无效"}.
|
{"Malformed username","用户名无效"}.
|
||||||
{"MAM preference modification denied by service policy","MAM偏好被服务策略拒绝"}.
|
{"MAM preference modification denied by service policy","MAM偏好被服务策略拒绝"}.
|
||||||
{"March","三月"}.
|
{"March","三月"}.
|
||||||
{"Max # of items to persist","允许持久化的最大内容条目数"}.
|
|
||||||
{"Max payload size in bytes","最大有效负载字节数"}.
|
{"Max payload size in bytes","最大有效负载字节数"}.
|
||||||
{"Maximum file size","最大文件大小"}.
|
{"Maximum file size","最大文件大小"}.
|
||||||
{"Maximum Number of History Messages Returned by Room","房间返回的历史消息最大值"}.
|
{"Maximum Number of History Messages Returned by Room","房间返回的历史消息最大值"}.
|
||||||
|
@ -288,7 +287,7 @@
|
||||||
{"Never","从未"}.
|
{"Never","从未"}.
|
||||||
{"New Password:","新密码:"}.
|
{"New Password:","新密码:"}.
|
||||||
{"Nickname can't be empty","昵称不能为空"}.
|
{"Nickname can't be empty","昵称不能为空"}.
|
||||||
{"Nickname Registration at ","昵称注册于"}.
|
{"Nickname Registration at ","昵称注册于 "}.
|
||||||
{"Nickname ~s does not exist in the room","昵称~s不在该房间"}.
|
{"Nickname ~s does not exist in the room","昵称~s不在该房间"}.
|
||||||
{"Nickname","昵称"}.
|
{"Nickname","昵称"}.
|
||||||
{"No address elements found","没有找到地址的各元素"}.
|
{"No address elements found","没有找到地址的各元素"}.
|
||||||
|
@ -326,6 +325,7 @@
|
||||||
{"Node ~p","节点~p"}.
|
{"Node ~p","节点~p"}.
|
||||||
{"Nodeprep has failed","Nodeprep 已失效"}.
|
{"Nodeprep has failed","Nodeprep 已失效"}.
|
||||||
{"Nodes","节点"}.
|
{"Nodes","节点"}.
|
||||||
|
{"Node","节点"}.
|
||||||
{"None","无"}.
|
{"None","无"}.
|
||||||
{"Not allowed","不允许"}.
|
{"Not allowed","不允许"}.
|
||||||
{"Not Found","没有找到"}.
|
{"Not Found","没有找到"}.
|
||||||
|
@ -339,7 +339,6 @@
|
||||||
{"Number of Offline Messages","离线消息数量"}.
|
{"Number of Offline Messages","离线消息数量"}.
|
||||||
{"Number of online users","在线用户数"}.
|
{"Number of online users","在线用户数"}.
|
||||||
{"Number of registered users","注册用户数"}.
|
{"Number of registered users","注册用户数"}.
|
||||||
{"Number of seconds after which to automatically purge items","自动清除项目要等待的秒数"}.
|
|
||||||
{"Occupants are allowed to invite others","允许成员邀请其他人"}.
|
{"Occupants are allowed to invite others","允许成员邀请其他人"}.
|
||||||
{"Occupants May Change the Subject","成员可以修改主题"}.
|
{"Occupants May Change the Subject","成员可以修改主题"}.
|
||||||
{"October","十月"}.
|
{"October","十月"}.
|
||||||
|
@ -428,7 +427,7 @@
|
||||||
{"Resources","资源"}.
|
{"Resources","资源"}.
|
||||||
{"Restart Service","重启服务"}.
|
{"Restart Service","重启服务"}.
|
||||||
{"Restart","重启"}.
|
{"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 after next ejabberd restart (requires less memory):","在下次 ejabberd 重启后恢复二进制备份(需要的内存更少):"}.
|
||||||
{"Restore binary backup immediately:","立即恢复二进制备份:"}.
|
{"Restore binary backup immediately:","立即恢复二进制备份:"}.
|
||||||
{"Restore plain text backup immediately:","立即恢复普通文本备份:"}.
|
{"Restore plain text backup immediately:","立即恢复普通文本备份:"}.
|
||||||
|
@ -455,7 +454,7 @@
|
||||||
{"Search Results for ","搜索结果属于关键词 "}.
|
{"Search Results for ","搜索结果属于关键词 "}.
|
||||||
{"Search the text","搜索文本"}.
|
{"Search the text","搜索文本"}.
|
||||||
{"Search until the date","搜索截至日期"}.
|
{"Search until the date","搜索截至日期"}.
|
||||||
{"Search users in ","搜索用户于"}.
|
{"Search users in ","在以下位置搜索用户 "}.
|
||||||
{"Select All","全选"}.
|
{"Select All","全选"}.
|
||||||
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
|
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
|
||||||
{"Send announcement to all online users","发送通知给所有在线用户"}.
|
{"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 owner","隶属所有人的JID"}.
|
||||||
{"The JIDs of those with an affiliation of publisher","隶属发布人的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 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 minimum number of milliseconds between sending any two notification digests","发送任何两个通知摘要之间的最小毫秒数"}.
|
||||||
{"The name of the node","该节点的名称"}.
|
{"The name of the node","该节点的名称"}.
|
||||||
{"The node is a collection 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 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 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 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 changing the password: ","修改密码出错: "}.
|
||||||
{"There was an error creating the account: ","帐户创建出错: "}.
|
{"There was an error creating the account: ","帐户创建出错: "}.
|
||||||
{"There was an error deleting 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 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 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 room is not anonymous","此房间不是匿名房间"}.
|
||||||
{"This service can not process the address: ~s","此服务无法处理地址: ~s"}.
|
{"This service can not process the address: ~s","此服务无法处理地址: ~s"}.
|
||||||
{"Thursday","星期四"}.
|
{"Thursday","星期四"}.
|
||||||
|
@ -565,7 +562,7 @@
|
||||||
{"Too many child elements","太多子元素"}.
|
{"Too many child elements","太多子元素"}.
|
||||||
{"Too many <item/> elements","太多 <item/> 元素"}.
|
{"Too many <item/> elements","太多 <item/> 元素"}.
|
||||||
{"Too many <list/> elements","太多 <list/> 元素"}.
|
{"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 receiver fields were specified","指定的接收者字段太多"}.
|
||||||
{"Too many unacked stanzas","未被确认的节太多"}.
|
{"Too many unacked stanzas","未被确认的节太多"}.
|
||||||
{"Too many users in this conference","该会议的用户太多"}.
|
{"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 a client that supports x:data to register the nickname","您需要一个支持 x:data 的客户端来注册昵称"}.
|
||||||
{"You need an x:data capable client to search","您需要一个兼容 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 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 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 registered.","你的XMPP帐户注册成功."}.
|
||||||
{"Your XMPP account was successfully unregistered.","你的XMPP帐户注销成功."}.
|
{"Your XMPP account was successfully unregistered.","你的XMPP帐户注销成功."}.
|
||||||
|
|
32
rebar.config
32
rebar.config
|
@ -30,25 +30,31 @@
|
||||||
{if_var_true, redis,
|
{if_var_true, redis,
|
||||||
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
|
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
|
||||||
{if_var_true, sip,
|
{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,
|
{if_var_true, zlib,
|
||||||
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.10"}}}},
|
{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_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"}}},
|
{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"}}},
|
{idna, ".*", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
|
||||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "1.0.5"}}},
|
{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"}}},
|
{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}},
|
||||||
{if_var_true, lua,
|
{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"}}},
|
{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,
|
{if_var_true, mysql,
|
||||||
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.19"}}}},
|
{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"}}},
|
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.10"}}},
|
||||||
{if_var_true, pgsql,
|
{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"}}},
|
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.23"}}},
|
||||||
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.8"}}},
|
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.8"}}},
|
||||||
{if_not_rebar3, %% Needed because modules are not fully migrated to new structure and mix
|
{if_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"}}}},
|
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.13"}}}},
|
||||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.27"}}},
|
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.27"}}},
|
||||||
{if_var_true, stun,
|
{if_var_true, stun,
|
||||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.44"}}}},
|
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.47"}}}},
|
||||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e943c0285aa85e3cbd4bfb9259f6b7de32b00395"}},
|
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.5.6"}}},
|
||||||
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.12"}}}
|
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.12"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{gitonly_deps, [elixir, luerl]}.
|
{gitonly_deps, [elixir]}.
|
||||||
|
|
||||||
{if_var_true, latest_deps,
|
{if_var_true, latest_deps,
|
||||||
{floating_deps, [cache_tab,
|
{floating_deps, [cache_tab,
|
||||||
|
@ -110,6 +116,7 @@
|
||||||
{if_var_true, sip, {d, 'SIP'}},
|
{if_var_true, sip, {d, 'SIP'}},
|
||||||
{if_var_true, stun, {d, 'STUN'}},
|
{if_var_true, stun, {d, 'STUN'}},
|
||||||
{if_have_fun, {erl_error, format_exception, 6}, {d, 'HAVE_ERL_ERROR'}},
|
{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,
|
{src_dirs, [src,
|
||||||
{if_rebar3, sql},
|
{if_rebar3, sql},
|
||||||
{if_var_true, tools, tools},
|
{if_var_true, tools, tools},
|
||||||
|
@ -177,12 +184,10 @@
|
||||||
{sys_config, "./rel/sys.config"},
|
{sys_config, "./rel/sys.config"},
|
||||||
{vm_args, "./rel/vm.args"},
|
{vm_args, "./rel/vm.args"},
|
||||||
{overlay_vars, "vars.config"},
|
{overlay_vars, "vars.config"},
|
||||||
{extended_start_script, true},
|
|
||||||
{overlay, [{mkdir, "var/log/ejabberd"},
|
{overlay, [{mkdir, "var/log/ejabberd"},
|
||||||
{mkdir, "var/lock"},
|
|
||||||
{mkdir, "var/lib/ejabberd"},
|
{mkdir, "var/lib/ejabberd"},
|
||||||
{mkdir, "etc/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"},
|
{template, "ejabberdctl.template", "bin/ejabberdctl"},
|
||||||
{copy, "inetrc", "etc/ejabberd/inetrc"},
|
{copy, "inetrc", "etc/ejabberd/inetrc"},
|
||||||
{copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"},
|
{copy, "tools/captcha*.sh", "lib/ejabberd-\{\{release_version\}\}/priv/bin/"},
|
||||||
|
@ -193,6 +198,7 @@
|
||||||
{dev_mode, false},
|
{dev_mode, false},
|
||||||
{include_erts, true},
|
{include_erts, true},
|
||||||
{include_src, true},
|
{include_src, true},
|
||||||
|
{generate_start_script, false},
|
||||||
{overlay, [{copy, "sql/*", "lib/ejabberd-\{\{release_version\}\}/priv/sql/"},
|
{overlay, [{copy, "sql/*", "lib/ejabberd-\{\{release_version\}\}/priv/sql/"},
|
||||||
{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg"},
|
{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg"},
|
||||||
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml"}]}]}]},
|
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml"}]}]}]},
|
||||||
|
@ -201,6 +207,8 @@
|
||||||
{dev_mode, true},
|
{dev_mode, true},
|
||||||
{include_erts, true},
|
{include_erts, true},
|
||||||
{include_src, false},
|
{include_src, false},
|
||||||
|
{generate_start_script, true},
|
||||||
|
{extended_start_script, true},
|
||||||
{overlay, [{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg.example"},
|
{overlay, [{copy, "ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg.example"},
|
||||||
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml.example"},
|
{copy, "ejabberd.yml.example", "etc/ejabberd/ejabberd.yml.example"},
|
||||||
{copy, "test/ejabberd_SUITE_data/ca.pem", "etc/ejabberd/"},
|
{copy, "test/ejabberd_SUITE_data/ca.pem", "etc/ejabberd/"},
|
||||||
|
|
|
@ -387,8 +387,8 @@ Rules = [
|
||||||
]}]), []},
|
]}]), []},
|
||||||
{[plugins], IsRebar3 and (os:getenv("GITHUB_ACTIONS") == "true"),
|
{[plugins], IsRebar3 and (os:getenv("GITHUB_ACTIONS") == "true"),
|
||||||
AppendList([{coveralls, {git,
|
AppendList([{coveralls, {git,
|
||||||
"https://github.com/RoadRunnr/coveralls-erl.git",
|
"https://github.com/processone/coveralls-erl.git",
|
||||||
{branch, "feature/git-info"}}} ]), []},
|
{branch, "addjsonfile"}}} ]), []},
|
||||||
{[overrides], [post_hook_configure], SystemDeps == false,
|
{[overrides], [post_hook_configure], SystemDeps == false,
|
||||||
AppendList2(GenDepsConfigure), [], []},
|
AppendList2(GenDepsConfigure), [], []},
|
||||||
{[ct_extra_params], [eunit_compile_opts], true,
|
{[ct_extra_params], [eunit_compile_opts], true,
|
||||||
|
|
|
@ -89,7 +89,6 @@ Sys = [{lib_dirs, []},
|
||||||
|
|
||||||
Overlay = [
|
Overlay = [
|
||||||
{mkdir, "var/log/ejabberd"},
|
{mkdir, "var/log/ejabberd"},
|
||||||
{mkdir, "var/lock"},
|
|
||||||
{mkdir, "var/lib/ejabberd"},
|
{mkdir, "var/lib/ejabberd"},
|
||||||
{mkdir, "etc/ejabberd"},
|
{mkdir, "etc/ejabberd"},
|
||||||
{mkdir, "doc"},
|
{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_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 UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers(host, room, jid);
|
||||||
|
|
||||||
CREATE TABLE motd (
|
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_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 UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers(host, room, jid);
|
||||||
|
|
||||||
CREATE TABLE motd (
|
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 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_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] (
|
CREATE TABLE [dbo].[privacy_default_list] (
|
||||||
[username] [varchar] (250) NOT NULL,
|
[username] [varchar] (250) NOT NULL,
|
||||||
|
|
|
@ -347,6 +347,7 @@ CREATE TABLE muc_room_subscribers (
|
||||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) 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_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 (
|
CREATE TABLE motd (
|
||||||
username varchar(191) NOT NULL,
|
username varchar(191) NOT NULL,
|
||||||
|
|
|
@ -77,6 +77,7 @@ BEGIN
|
||||||
ALTER TABLE `last` ADD PRIMARY KEY (`server_host`, `username`);
|
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` 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` 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 `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` 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;
|
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` 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` 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` 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_jid` (`server_host`, `jid`);
|
||||||
ALTER TABLE `sr_user` ADD INDEX `i_sr_user_sh_grp` (`server_host`, `grp`);
|
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`);
|
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;
|
) 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_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 (
|
CREATE TABLE motd (
|
||||||
username varchar(191) PRIMARY KEY,
|
username varchar(191) PRIMARY KEY,
|
||||||
|
|
|
@ -311,7 +311,7 @@ CREATE TABLE vcard_search (
|
||||||
lorgname text NOT NULL,
|
lorgname text NOT NULL,
|
||||||
orgunit text NOT NULL,
|
orgunit text NOT NULL,
|
||||||
lorgunit 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);
|
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_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 UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers USING btree (host, room, jid);
|
||||||
|
|
||||||
CREATE TABLE motd (
|
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_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 UNIQUE INDEX i_muc_room_subscribers_host_room_jid ON muc_room_subscribers USING btree (host, room, jid);
|
||||||
|
|
||||||
CREATE TABLE motd (
|
CREATE TABLE motd (
|
||||||
|
|
|
@ -120,7 +120,10 @@ get_commands_spec() ->
|
||||||
module = init, function = restart,
|
module = init, function = restart,
|
||||||
args = [], result = {res, rescode}},
|
args = [], result = {res, rescode}},
|
||||||
#ejabberd_commands{name = reopen_log, tags = [logs],
|
#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,
|
policy = admin,
|
||||||
module = ?MODULE, function = reopen_log,
|
module = ?MODULE, function = reopen_log,
|
||||||
args = [], result = {res, rescode}},
|
args = [], result = {res, rescode}},
|
||||||
|
@ -178,6 +181,8 @@ get_commands_spec() ->
|
||||||
result = {res, restuple}},
|
result = {res, restuple}},
|
||||||
#ejabberd_commands{name = unregister, tags = [accounts],
|
#ejabberd_commands{name = unregister, tags = [accounts],
|
||||||
desc = "Unregister a user",
|
desc = "Unregister a user",
|
||||||
|
longdesc = "This deletes the authentication and all the "
|
||||||
|
"data associated to the account (roster, vcard...).",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = unregister,
|
module = ?MODULE, function = unregister,
|
||||||
args_desc = ["Username", "Local vhost served by ejabberd"],
|
args_desc = ["Username", "Local vhost served by ejabberd"],
|
||||||
|
@ -345,31 +350,41 @@ get_commands_spec() ->
|
||||||
{oldbackup, string}, {newbackup, string}],
|
{oldbackup, string}, {newbackup, string}],
|
||||||
result = {res, restuple}},
|
result = {res, restuple}},
|
||||||
#ejabberd_commands{name = backup, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = backup_mnesia,
|
||||||
args_desc = ["Full path for the destination backup file"],
|
args_desc = ["Full path for the destination backup file"],
|
||||||
args_example = ["/var/lib/ejabberd/database.backup"],
|
args_example = ["/var/lib/ejabberd/database.backup"],
|
||||||
args = [{file, string}], result = {res, restuple}},
|
args = [{file, string}], result = {res, restuple}},
|
||||||
#ejabberd_commands{name = restore, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = restore_mnesia,
|
||||||
args_desc = ["Full path to the backup file"],
|
args_desc = ["Full path to the backup file"],
|
||||||
args_example = ["/var/lib/ejabberd/database.backup"],
|
args_example = ["/var/lib/ejabberd/database.backup"],
|
||||||
args = [{file, string}], result = {res, restuple}},
|
args = [{file, string}], result = {res, restuple}},
|
||||||
#ejabberd_commands{name = dump, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = dump_mnesia,
|
||||||
args_desc = ["Full path for the text file"],
|
args_desc = ["Full path for the text file"],
|
||||||
args_example = ["/var/lib/ejabberd/database.txt"],
|
args_example = ["/var/lib/ejabberd/database.txt"],
|
||||||
args = [{file, string}], result = {res, restuple}},
|
args = [{file, string}], result = {res, restuple}},
|
||||||
#ejabberd_commands{name = dump_table, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = dump_table,
|
||||||
args_desc = ["Full path for the text file", "Table name"],
|
args_desc = ["Full path for the text file", "Table name"],
|
||||||
args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
|
args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
|
||||||
args = [{file, string}, {table, string}], result = {res, restuple}},
|
args = [{file, string}, {table, string}], result = {res, restuple}},
|
||||||
#ejabberd_commands{name = load, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = load_mnesia,
|
||||||
args_desc = ["Full path to the text file"],
|
args_desc = ["Full path to the text file"],
|
||||||
args_example = ["/var/lib/ejabberd/database.txt"],
|
args_example = ["/var/lib/ejabberd/database.txt"],
|
||||||
|
@ -385,7 +400,14 @@ get_commands_spec() ->
|
||||||
args_example = ["roster"],
|
args_example = ["roster"],
|
||||||
args = [{table, string}], result = {res, string}},
|
args = [{table, string}], result = {res, string}},
|
||||||
#ejabberd_commands{name = install_fallback, tags = [mnesia],
|
#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,
|
module = ?MODULE, function = install_fallback_mnesia,
|
||||||
args_desc = ["Full path to the fallback file"],
|
args_desc = ["Full path to the fallback file"],
|
||||||
args_example = ["/var/lib/ejabberd/database.fallback"],
|
args_example = ["/var/lib/ejabberd/database.fallback"],
|
||||||
|
|
|
@ -299,6 +299,20 @@ export(_Server) ->
|
||||||
["username=%(LUser)s",
|
["username=%(LUser)s",
|
||||||
"server_host=%(LServer)s",
|
"server_host=%(LServer)s",
|
||||||
"password=%(Password)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})
|
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
|
||||||
when LServer == Host ->
|
when LServer == Host ->
|
||||||
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),
|
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),
|
||||||
|
|
|
@ -94,6 +94,7 @@ get_commands_spec() ->
|
||||||
result_example = ok},
|
result_example = ok},
|
||||||
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation],
|
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation],
|
||||||
desc = "Generates markdown documentation for ejabberd_commands",
|
desc = "Generates markdown documentation for ejabberd_commands",
|
||||||
|
note = "added in 21.12",
|
||||||
module = ejabberd_commands_doc, function = generate_tags_md,
|
module = ejabberd_commands_doc, function = generate_tags_md,
|
||||||
args = [{file, binary}],
|
args = [{file, binary}],
|
||||||
result = {res, rescode},
|
result = {res, rescode},
|
||||||
|
|
|
@ -378,7 +378,11 @@ format_arg("", string) ->
|
||||||
format_arg(Arg, string) ->
|
format_arg(Arg, string) ->
|
||||||
NumChars = integer_to_list(length(Arg)),
|
NumChars = integer_to_list(length(Arg)),
|
||||||
Parse = "~" ++ NumChars ++ "c",
|
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)->
|
format_arg2(Arg, Parse)->
|
||||||
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
|
{ok, [Arg2], _RemainingArguments} = io_lib:fread(Parse, Arg),
|
||||||
|
@ -525,6 +529,7 @@ print_usage(Version) ->
|
||||||
print_usage(HelpMode, MaxC, ShCode, Version) ->
|
print_usage(HelpMode, MaxC, ShCode, Version) ->
|
||||||
AllCommands =
|
AllCommands =
|
||||||
[
|
[
|
||||||
|
{"help", ["[arguments]"], "Get help"},
|
||||||
{"status", [], "Get ejabberd status"},
|
{"status", [], "Get ejabberd status"},
|
||||||
{"stop", [], "Stop ejabberd"},
|
{"stop", [], "Stop ejabberd"},
|
||||||
{"restart", [], "Restart ejabberd"},
|
{"restart", [], "Restart ejabberd"},
|
||||||
|
|
|
@ -24,17 +24,15 @@
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
%%% Not implemented:
|
%%% Not implemented:
|
||||||
|
%%% - PEP nodes export/import
|
||||||
|
%%% - message archives export/import
|
||||||
%%% - write mod_piefxis with ejabberdctl commands
|
%%% - 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.
|
%%% - 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.
|
%%% - If a host has many users, split that host in XML files with 50 users each.
|
||||||
%%%% Headers
|
|
||||||
|
|
||||||
-module(ejabberd_piefxis).
|
-module(ejabberd_piefxis).
|
||||||
|
|
||||||
-protocol({xep, 227, '1.0'}).
|
-protocol({xep, 227, '1.1'}).
|
||||||
|
|
||||||
-export([import_file/1, export_server/1, export_host/2]).
|
-export([import_file/1, export_server/1, export_host/2]).
|
||||||
|
|
||||||
|
@ -166,33 +164,66 @@ export_users([], _Server, _Fd) ->
|
||||||
export_user(User, Server, Fd) ->
|
export_user(User, Server, Fd) ->
|
||||||
Password = ejabberd_auth:get_password_s(User, Server),
|
Password = ejabberd_auth:get_password_s(User, Server),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
Pass = case ejabberd_auth:password_format(LServer) of
|
{PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of
|
||||||
scram -> format_scram_password(Password);
|
scram -> {[], [format_scram_password(Password)]};
|
||||||
_ -> Password
|
_ -> {[{<<"password">>, Password}], []}
|
||||||
end,
|
end,
|
||||||
Els = get_offline(User, Server) ++
|
Els =
|
||||||
|
PassScram ++
|
||||||
|
get_offline(User, Server) ++
|
||||||
get_vcard(User, Server) ++
|
get_vcard(User, Server) ++
|
||||||
get_privacy(User, Server) ++
|
get_privacy(User, Server) ++
|
||||||
get_roster(User, Server) ++
|
get_roster(User, Server) ++
|
||||||
get_private(User, Server),
|
get_private(User, Server),
|
||||||
print(Fd, fxml:element_to_binary(
|
print(Fd, fxml:element_to_binary(
|
||||||
#xmlel{name = <<"user">>,
|
#xmlel{name = <<"user">>,
|
||||||
attrs = [{<<"name">>, User},
|
attrs = [{<<"name">>, User} | PassPlain],
|
||||||
{<<"password">>, Pass}],
|
|
||||||
children = Els})).
|
children = Els})).
|
||||||
|
|
||||||
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
|
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
|
||||||
salt = Salt, iterationcount = IterationCount}) ->
|
salt = Salt, iterationcount = IterationCount}) ->
|
||||||
StoredKeyB64 = base64:encode(StoredKey),
|
StoredKeyB64 = base64:encode(StoredKey),
|
||||||
ServerKeyB64 = base64:encode(ServerKey),
|
ServerKeyB64 = base64:encode(ServerKey),
|
||||||
SaltB64 = base64:encode(Salt),
|
SaltB64 = base64:encode(Salt),
|
||||||
IterationCountBin = (integer_to_binary(IterationCount)),
|
IterationCountBin = (integer_to_binary(IterationCount)),
|
||||||
Hash2 = case Hash of
|
MechanismB = case Hash of
|
||||||
sha -> <<>>;
|
sha -> <<"SCRAM-SHA-1">>;
|
||||||
sha256 -> <<"sha256,">>;
|
sha256 -> <<"SCRAM-SHA-256">>;
|
||||||
sha512 -> <<"sha512,">>
|
sha512 -> <<"SCRAM-SHA-512">>
|
||||||
end,
|
end,
|
||||||
<<"scram:", Hash2/binary, StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
|
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) ->
|
parse_scram_password(PassData) ->
|
||||||
Split = binary:split(PassData, <<",">>, [global]),
|
Split = binary:split(PassData, <<",">>, [global]),
|
||||||
|
@ -214,26 +245,30 @@ parse_scram_password(PassData) ->
|
||||||
get_vcard(User, Server) ->
|
get_vcard(User, Server) ->
|
||||||
LUser = jid:nodeprep(User),
|
LUser = jid:nodeprep(User),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
case mod_vcard:get_vcard(LUser, LServer) of
|
try mod_vcard:get_vcard(LUser, LServer) of
|
||||||
error -> [];
|
error -> [];
|
||||||
Els -> Els
|
Els -> Els
|
||||||
|
catch
|
||||||
|
error:{module_not_loaded, _, _} -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_offline(binary(), binary()) -> [xmlel()].
|
-spec get_offline(binary(), binary()) -> [xmlel()].
|
||||||
get_offline(User, Server) ->
|
get_offline(User, Server) ->
|
||||||
LUser = jid:nodeprep(User),
|
LUser = jid:nodeprep(User),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
case mod_offline:get_offline_els(LUser, LServer) of
|
try mod_offline:get_offline_els(LUser, LServer) of
|
||||||
[] ->
|
[] ->
|
||||||
[];
|
[];
|
||||||
Els ->
|
Els ->
|
||||||
NewEls = lists:map(fun xmpp:encode/1, Els),
|
NewEls = lists:map(fun xmpp:encode/1, Els),
|
||||||
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
|
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
|
||||||
|
catch
|
||||||
|
error:{module_not_loaded, _, _} -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_privacy(binary(), binary()) -> [xmlel()].
|
-spec get_privacy(binary(), binary()) -> [xmlel()].
|
||||||
get_privacy(User, Server) ->
|
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,
|
{ok, #privacy{default = Default,
|
||||||
lists = [_|_] = Lists}} ->
|
lists = [_|_] = Lists}} ->
|
||||||
XLists = lists:map(
|
XLists = lists:map(
|
||||||
|
@ -246,12 +281,14 @@ get_privacy(User, Server) ->
|
||||||
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
|
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
|
catch
|
||||||
|
error:{module_not_loaded, _, _} -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_roster(binary(), binary()) -> [xmlel()].
|
-spec get_roster(binary(), binary()) -> [xmlel()].
|
||||||
get_roster(User, Server) ->
|
get_roster(User, Server) ->
|
||||||
JID = jid:make(User, Server),
|
JID = jid:make(User, Server),
|
||||||
case mod_roster:get_roster(User, Server) of
|
try mod_roster:get_roster(User, Server) of
|
||||||
[_|_] = Items ->
|
[_|_] = Items ->
|
||||||
Subs =
|
Subs =
|
||||||
lists:flatmap(
|
lists:flatmap(
|
||||||
|
@ -278,15 +315,19 @@ get_roster(User, Server) ->
|
||||||
[xmpp:encode(#roster_query{items = Rs}) | Subs];
|
[xmpp:encode(#roster_query{items = Rs}) | Subs];
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
|
catch
|
||||||
|
error:{module_not_loaded, _, _} -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_private(binary(), binary()) -> [xmlel()].
|
-spec get_private(binary(), binary()) -> [xmlel()].
|
||||||
get_private(User, Server) ->
|
get_private(User, Server) ->
|
||||||
case mod_private:get_data(User, Server) of
|
try mod_private:get_data(User, Server) of
|
||||||
[_|_] = Els ->
|
[_|_] = Els ->
|
||||||
[xmpp:encode(#private{sub_els = Els})];
|
[xmpp:encode(#private{sub_els = Els})];
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
|
catch
|
||||||
|
error:{module_not_loaded, _, _} -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
|
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
|
||||||
|
@ -398,21 +439,10 @@ process_users([_|Els], State) ->
|
||||||
process_users([], State) ->
|
process_users([], State) ->
|
||||||
{ok, 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) ->
|
#state{server = LServer} = State) ->
|
||||||
Name = fxml:get_attr_s(<<"name">>, Attrs),
|
Name = fxml:get_attr_s(<<"name">>, Attrs),
|
||||||
Password = fxml:get_attr_s(<<"password">>, Attrs),
|
Pass = process_password(El, LServer),
|
||||||
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,
|
|
||||||
|
|
||||||
case jid:nodeprep(Name) of
|
case jid:nodeprep(Name) of
|
||||||
error ->
|
error ->
|
||||||
stop("Invalid 'user': ~ts", [Name]);
|
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
|
case ejabberd_auth:try_register(LUser, LServer, Pass) of
|
||||||
ok ->
|
ok ->
|
||||||
process_user_els(Els, State#state{user = LUser});
|
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});
|
process_user_els(Els, State#state{user = LUser});
|
||||||
{error, Err} ->
|
{error, Err} ->
|
||||||
stop("Failed to create user '~ts': ~p", [Name, Err])
|
stop("Failed to create user '~ts': ~p", [Name, Err])
|
||||||
end
|
end
|
||||||
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) ->
|
process_user_els([#xmlel{} = El|Els], State) ->
|
||||||
case process_user_el(El, State) of
|
case process_user_el(El, State) of
|
||||||
{ok, NewState} ->
|
{ok, NewState} ->
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/0, stop/0, route/1, have_connection/1,
|
-export([start_link/0, stop/0, route/1, have_connection/1,
|
||||||
get_connections_pids/1, try_register/1,
|
get_connections_pids/1,
|
||||||
remove_connection/2, start_connection/2, start_connection/3,
|
start_connection/2, start_connection/3,
|
||||||
dirty_get_connections/0, allow_host/2,
|
dirty_get_connections/0, allow_host/2,
|
||||||
incoming_s2s_number/0, outgoing_s2s_number/0,
|
incoming_s2s_number/0, outgoing_s2s_number/0,
|
||||||
stop_s2s_connections/0,
|
stop_s2s_connections/0,
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
%% once a server is temporary blocked, it stay blocked for 60 seconds
|
%% once a server is temporary blocked, it stay blocked for 60 seconds
|
||||||
|
|
||||||
-record(s2s, {fromto :: {binary(), binary()},
|
-record(s2s, {fromto :: {binary(), binary()} | '_',
|
||||||
pid :: pid()}).
|
pid :: pid()}).
|
||||||
|
|
||||||
-record(state, {}).
|
-record(state, {}).
|
||||||
|
@ -112,24 +112,6 @@ is_temporarly_blocked(Host) ->
|
||||||
end
|
end
|
||||||
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().
|
-spec have_connection({binary(), binary()}) -> boolean().
|
||||||
have_connection(FromTo) ->
|
have_connection(FromTo) ->
|
||||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||||
|
@ -148,31 +130,6 @@ get_connections_pids(FromTo) ->
|
||||||
[]
|
[]
|
||||||
end.
|
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()}].
|
-spec dirty_get_connections() -> [{binary(), binary()}].
|
||||||
dirty_get_connections() ->
|
dirty_get_connections() ->
|
||||||
mnesia:dirty_all_keys(s2s).
|
mnesia:dirty_all_keys(s2s).
|
||||||
|
@ -269,6 +226,8 @@ init([]) ->
|
||||||
{stop, Reason}
|
{stop, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
handle_call({new_connection, Args}, _From, State) ->
|
||||||
|
{reply, erlang:apply(fun new_connection_int/7, Args), State};
|
||||||
handle_call(Request, From, State) ->
|
handle_call(Request, From, State) ->
|
||||||
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
@ -289,6 +248,21 @@ handle_info({route, Packet}, State) ->
|
||||||
misc:format_exception(2, Class, Reason, StackTrace)])
|
misc:format_exception(2, Class, Reason, StackTrace)])
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{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) ->
|
handle_info(Info, State) ->
|
||||||
?WARNING_MSG("Unexpected info: ~p", [Info]),
|
?WARNING_MSG("Unexpected info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
@ -458,6 +432,18 @@ open_several_connections(N, MyServer, Server, From,
|
||||||
integer(), integer(), [proplists:property()]) -> [pid()].
|
integer(), integer(), [proplists:property()]) -> [pid()].
|
||||||
new_connection(MyServer, Server, From, FromTo,
|
new_connection(MyServer, Server, From, FromTo,
|
||||||
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
|
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),
|
{ok, Pid} = ejabberd_s2s_out:start(MyServer, Server, Opts),
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
L = mnesia:read({s2s, FromTo}),
|
L = mnesia:read({s2s, FromTo}),
|
||||||
|
@ -474,6 +460,7 @@ new_connection(MyServer, Server, From, FromTo,
|
||||||
case TRes of
|
case TRes of
|
||||||
{atomic, Pid1} ->
|
{atomic, Pid1} ->
|
||||||
if Pid1 == Pid ->
|
if Pid1 == Pid ->
|
||||||
|
erlang:monitor(process, Pid),
|
||||||
ejabberd_s2s_out:connect(Pid);
|
ejabberd_s2s_out:connect(Pid);
|
||||||
true ->
|
true ->
|
||||||
ejabberd_s2s_out:stop_async(Pid)
|
ejabberd_s2s_out:stop_async(Pid)
|
||||||
|
|
|
@ -318,7 +318,6 @@ handle_info(Info, #{server_host := ServerHost} = State) ->
|
||||||
|
|
||||||
terminate(Reason, #{server := LServer,
|
terminate(Reason, #{server := LServer,
|
||||||
remote_server := RServer} = State) ->
|
remote_server := RServer} = State) ->
|
||||||
ejabberd_s2s:remove_connection({LServer, RServer}, self()),
|
|
||||||
State1 = case Reason of
|
State1 = case Reason of
|
||||||
normal -> State;
|
normal -> State;
|
||||||
_ -> State#{stop_reason => internal_failure}
|
_ -> State#{stop_reason => internal_failure}
|
||||||
|
@ -351,21 +350,12 @@ bounce_queue(State) ->
|
||||||
end, State).
|
end, State).
|
||||||
|
|
||||||
-spec bounce_message_queue({binary(), binary()}, state()) -> state().
|
-spec bounce_message_queue({binary(), binary()}, state()) -> state().
|
||||||
bounce_message_queue({LServer, RServer} = FromTo, State) ->
|
bounce_message_queue(FromTo, State) ->
|
||||||
Pids = ejabberd_s2s:get_connections_pids(FromTo),
|
receive {route, Pkt} ->
|
||||||
case lists:member(self(), Pids) of
|
State1 = bounce_packet(Pkt, State),
|
||||||
true ->
|
bounce_message_queue(FromTo, State1)
|
||||||
?WARNING_MSG("Outgoing s2s connection ~ts -> ~ts is supposed "
|
after 0 ->
|
||||||
"to be unregistered, but pid ~p still presents "
|
State
|
||||||
"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
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec bounce_packet(xmpp_element(), state()) -> state().
|
-spec bounce_packet(xmpp_element(), state()) -> state().
|
||||||
|
|
|
@ -564,15 +564,23 @@ make_sql_upsert(Table, ParseRes, Pos) ->
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
erl_syntax:fun_expr(
|
erl_syntax:fun_expr(
|
||||||
[erl_syntax:clause(
|
[erl_syntax:clause(
|
||||||
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
|
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
|
||||||
[erl_syntax:infix_expr(
|
[erl_syntax:infix_expr(
|
||||||
erl_syntax:variable("__Version"),
|
erl_syntax:variable("__Version"),
|
||||||
erl_syntax:operator('>='),
|
erl_syntax:operator('>='),
|
||||||
erl_syntax:integer(90100))],
|
erl_syntax:integer(90500))],
|
||||||
[make_sql_upsert_pgsql901(Table, ParseRes),
|
[make_sql_upsert_pgsql905(Table, ParseRes),
|
||||||
erl_syntax:atom(ok)])] ++
|
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(90100))],
|
||||||
|
[make_sql_upsert_pgsql901(Table, ParseRes),
|
||||||
|
erl_syntax:atom(ok)])] ++
|
||||||
|
MySqlReplace ++
|
||||||
[erl_syntax:clause(
|
[erl_syntax:clause(
|
||||||
[erl_syntax:underscore(), erl_syntax:underscore()],
|
[erl_syntax:underscore(), erl_syntax:underscore()],
|
||||||
none,
|
none,
|
||||||
|
@ -713,6 +721,57 @@ make_sql_upsert_pgsql901(Table, ParseRes0) ->
|
||||||
erl_syntax:atom(sql_query_t),
|
erl_syntax:atom(sql_query_t),
|
||||||
[Upsert]).
|
[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) ->
|
check_upsert(ParseRes, Pos) ->
|
||||||
Set =
|
Set =
|
||||||
|
|
|
@ -73,11 +73,16 @@ export(Server, Output) ->
|
||||||
end, Modules),
|
end, Modules),
|
||||||
close_output(Output, IO).
|
close_output(Output, IO).
|
||||||
|
|
||||||
export(Server, Output, Module1) ->
|
export(Server, Output, mod_mam = M1) ->
|
||||||
Module = case Module1 of
|
MucServices = gen_mod:get_module_opt_hosts(Server, mod_muc),
|
||||||
mod_pubsub -> pubsub_db;
|
[export2(MucService, Output, M1, M1) || MucService <- MucServices],
|
||||||
_ -> Module1
|
export2(Server, Output, M1, M1);
|
||||||
end,
|
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),
|
SQLMod = gen_mod:db_mod(sql, Module),
|
||||||
LServer = jid:nameprep(iolist_to_binary(Server)),
|
LServer = jid:nameprep(iolist_to_binary(Server)),
|
||||||
IO = prepare_output(Output),
|
IO = prepare_output(Output),
|
||||||
|
|
|
@ -133,6 +133,10 @@
|
||||||
{result, {[itemId()], [itemId()]}
|
{result, {[itemId()], [itemId()]}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
-callback remove_expired_items(NodeIdx :: nodeIdx(),
|
||||||
|
Seconds :: infinity | non_neg_integer()) ->
|
||||||
|
{result, [itemId()]}.
|
||||||
|
|
||||||
-callback get_node_affiliations(NodeIdx :: nodeIdx()) ->
|
-callback get_node_affiliations(NodeIdx :: nodeIdx()) ->
|
||||||
{result, [{ljid(), affiliation()}]}.
|
{result, [{ljid(), affiliation()}]}.
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@ update_tables(State) ->
|
||||||
|
|
||||||
add_sh_column(State, "sr_group"),
|
add_sh_column(State, "sr_group"),
|
||||||
add_pkey(State, "sr_group", ["server_host", "name"]),
|
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"),
|
drop_sh_default(State, "sr_group"),
|
||||||
|
|
||||||
add_sh_column(State, "sr_user"),
|
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_jid"),
|
||||||
drop_index(State, "i_sr_user_grp"),
|
drop_index(State, "i_sr_user_grp"),
|
||||||
add_pkey(State, "sr_user", ["server_host", "jid", "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_jid", ["server_host", "jid"]),
|
||||||
create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
|
create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
|
||||||
drop_sh_default(State, "sr_user"),
|
drop_sh_default(State, "sr_user"),
|
||||||
|
|
|
@ -49,7 +49,8 @@
|
||||||
handle_cast/2, terminate/2, code_change/3]).
|
handle_cast/2, terminate/2, code_change/3]).
|
||||||
|
|
||||||
-export([user_send_packet/1, user_receive_packet/1,
|
-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").
|
-include("logger.hrl").
|
||||||
|
|
||||||
|
@ -274,6 +275,13 @@ c2s_presence_in(C2SState,
|
||||||
C2SState#{caps_resources => NewRs}
|
C2SState#{caps_resources => NewRs}
|
||||||
end.
|
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}].
|
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||||
depends(_Host, _Opts) ->
|
depends(_Host, _Opts) ->
|
||||||
[].
|
[].
|
||||||
|
@ -304,6 +312,8 @@ init([Host|_]) ->
|
||||||
caps_stream_features, 75),
|
caps_stream_features, 75),
|
||||||
ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE,
|
ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE,
|
||||||
caps_stream_features, 75),
|
caps_stream_features, 75),
|
||||||
|
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||||
|
c2s_copy_session, 75),
|
||||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||||
disco_features, 75),
|
disco_features, 75),
|
||||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||||
|
@ -341,6 +351,8 @@ terminate(_Reason, State) ->
|
||||||
?MODULE, caps_stream_features, 75),
|
?MODULE, caps_stream_features, 75),
|
||||||
ejabberd_hooks:delete(s2s_in_post_auth_features, Host,
|
ejabberd_hooks:delete(s2s_in_post_auth_features, Host,
|
||||||
?MODULE, caps_stream_features, 75),
|
?MODULE, caps_stream_features, 75),
|
||||||
|
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||||
|
c2s_copy_session, 75),
|
||||||
ejabberd_hooks:delete(disco_local_features, Host,
|
ejabberd_hooks:delete(disco_local_features, Host,
|
||||||
?MODULE, disco_features, 75),
|
?MODULE, disco_features, 75),
|
||||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||||
|
|
|
@ -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.")}}]
|
||||||
|
}.
|
|
@ -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).
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
-export([start/2, stop/1, reload/3, process/2, depends/2,
|
-export([start/2, stop/1, reload/3, process/2, depends/2,
|
||||||
|
format_arg/2,
|
||||||
mod_options/1, mod_doc/0]).
|
mod_options/1, mod_doc/0]).
|
||||||
|
|
||||||
-include_lib("xmpp/include/xmpp.hrl").
|
-include_lib("xmpp/include/xmpp.hrl").
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
-protocol({xep, 313, '0.6.1'}).
|
-protocol({xep, 313, '0.6.1'}).
|
||||||
-protocol({xep, 334, '0.2'}).
|
-protocol({xep, 334, '0.2'}).
|
||||||
-protocol({xep, 359, '0.5.0'}).
|
-protocol({xep, 359, '0.5.0'}).
|
||||||
|
-protocol({xep, 441, '0.2.0'}).
|
||||||
|
|
||||||
-behaviour(gen_mod).
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
|
|
@ -1134,8 +1134,8 @@ is_expired(#publish{meta = Meta, properties = Props} = Pkt) ->
|
||||||
%%% Authentication
|
%%% Authentication
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
-spec parse_credentials(connect()) -> {ok, jid:jid()} | {error, reason_code()}.
|
-spec parse_credentials(connect()) -> {ok, jid:jid()} | {error, reason_code()}.
|
||||||
parse_credentials(#connect{client_id = <<>>}) ->
|
parse_credentials(#connect{client_id = <<>>} = C) ->
|
||||||
parse_credentials(#connect{client_id = p1_rand:get_string()});
|
parse_credentials(C#connect{client_id = p1_rand:get_string()});
|
||||||
parse_credentials(#connect{username = <<>>, client_id = ClientID}) ->
|
parse_credentials(#connect{username = <<>>, client_id = ClientID}) ->
|
||||||
Host = ejabberd_config:get_myname(),
|
Host = ejabberd_config:get_myname(),
|
||||||
JID = case jid:make(ClientID, Host) of
|
JID = case jid:make(ClientID, Host) of
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
get_online_rooms_by_user/3,
|
get_online_rooms_by_user/3,
|
||||||
can_use_nick/4,
|
can_use_nick/4,
|
||||||
get_subscribed_rooms/2,
|
get_subscribed_rooms/2,
|
||||||
|
remove_user/2,
|
||||||
procname/2,
|
procname/2,
|
||||||
route/1, unhibernate_room/3]).
|
route/1, unhibernate_room/3]).
|
||||||
|
|
||||||
|
@ -122,6 +123,8 @@
|
||||||
start(Host, Opts) ->
|
start(Host, Opts) ->
|
||||||
case mod_muc_sup:start(Host) of
|
case mod_muc_sup:start(Host) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
|
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||||
|
remove_user, 50),
|
||||||
MyHosts = gen_mod:get_opt_hosts(Opts),
|
MyHosts = gen_mod:get_opt_hosts(Opts),
|
||||||
Mod = gen_mod:db_mod(Opts, ?MODULE),
|
Mod = gen_mod:db_mod(Opts, ?MODULE),
|
||||||
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
|
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
|
||||||
|
@ -133,6 +136,8 @@ start(Host, Opts) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||||
|
remove_user, 50),
|
||||||
Proc = mod_muc_sup:procname(Host),
|
Proc = mod_muc_sup:procname(Host),
|
||||||
supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
|
supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
|
||||||
supervisor:delete_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 = gen_mod:ram_db_mod(ServerHost, ?MODULE),
|
||||||
RMod:count_online_rooms(ServerHost, Host).
|
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) ->
|
opts_to_binary(Opts) ->
|
||||||
lists:map(
|
lists:map(
|
||||||
fun({title, Title}) ->
|
fun({title, Title}) ->
|
||||||
|
@ -1225,6 +1256,8 @@ mod_opt_type(user_message_shaper) ->
|
||||||
econf:atom();
|
econf:atom();
|
||||||
mod_opt_type(user_presence_shaper) ->
|
mod_opt_type(user_presence_shaper) ->
|
||||||
econf:atom();
|
econf:atom();
|
||||||
|
mod_opt_type(cleanup_affiliations_on_start) ->
|
||||||
|
econf:bool();
|
||||||
mod_opt_type(default_room_options) ->
|
mod_opt_type(default_room_options) ->
|
||||||
econf:options(
|
econf:options(
|
||||||
#{allow_change_subj => econf:bool(),
|
#{allow_change_subj => econf:bool(),
|
||||||
|
@ -1302,6 +1335,7 @@ mod_options(Host) ->
|
||||||
{preload_rooms, true},
|
{preload_rooms, true},
|
||||||
{hibernation_timeout, infinity},
|
{hibernation_timeout, infinity},
|
||||||
{vcard, undefined},
|
{vcard, undefined},
|
||||||
|
{cleanup_affiliations_on_start, false},
|
||||||
{default_room_options,
|
{default_room_options,
|
||||||
[{allow_change_subj,true},
|
[{allow_change_subj,true},
|
||||||
{allow_private_messages,true},
|
{allow_private_messages,true},
|
||||||
|
@ -1580,6 +1614,11 @@ mod_doc() ->
|
||||||
" -",
|
" -",
|
||||||
" work: true",
|
" work: true",
|
||||||
" street: Elm Street"]}]}},
|
" 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,
|
{default_room_options,
|
||||||
#{value => ?T("Options"),
|
#{value => ?T("Options"),
|
||||||
desc =>
|
desc =>
|
||||||
|
|
|
@ -40,8 +40,11 @@
|
||||||
change_room_option/4, get_room_options/2,
|
change_room_option/4, get_room_options/2,
|
||||||
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
|
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
|
||||||
web_menu_main/2, web_page_main/2, web_menu_host/3,
|
web_menu_main/2, web_page_main/2, web_menu_host/3,
|
||||||
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
|
subscribe_room/4, subscribe_room_many/3,
|
||||||
web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]).
|
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("logger.hrl").
|
||||||
-include_lib("xmpp/include/xmpp.hrl").
|
-include_lib("xmpp/include/xmpp.hrl").
|
||||||
|
@ -281,7 +284,7 @@ get_commands_spec() ->
|
||||||
|
|
||||||
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
|
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
|
||||||
desc = "Send a direct invitation to several destinations",
|
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 "
|
"asynchronous: the API call may return before the "
|
||||||
"server has send all the invitations.\n\n"
|
"server has send all the invitations.\n\n"
|
||||||
"Password and Message can also be: none. "
|
"Password and Message can also be: none. "
|
||||||
|
@ -331,6 +334,26 @@ get_commands_spec() ->
|
||||||
args = [{user, binary}, {nick, binary}, {room, binary},
|
args = [{user, binary}, {nick, binary}, {room, binary},
|
||||||
{nodes, binary}],
|
{nodes, binary}],
|
||||||
result = {nodes, {list, {node, string}}}},
|
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],
|
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
|
||||||
desc = "Unsubscribe from a MUC conference",
|
desc = "Unsubscribe from a MUC conference",
|
||||||
module = ?MODULE, function = unsubscribe_room,
|
module = ?MODULE, function = unsubscribe_room,
|
||||||
|
@ -710,7 +733,7 @@ create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
|
||||||
maybe_store_room(ServerHost, Host, Name, RoomOpts) ->
|
maybe_store_room(ServerHost, Host, Name, RoomOpts) ->
|
||||||
case proplists:get_bool(persistent, RoomOpts) of
|
case proplists:get_bool(persistent, RoomOpts) of
|
||||||
true ->
|
true ->
|
||||||
{atomic, ok} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
|
{atomic, _} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
|
||||||
ok;
|
ok;
|
||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
|
@ -860,7 +883,14 @@ get_online_rooms(ServiceArg) ->
|
||||||
|| {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
|
|| {RoomName, RoomHost, Pid} <- mod_muc:get_online_rooms(Host)]
|
||||||
end, Hosts).
|
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),
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
OnlineRooms = get_online_rooms(Host),
|
OnlineRooms = get_online_rooms(Host),
|
||||||
OnlineMap = lists:foldl(
|
OnlineMap = lists:foldl(
|
||||||
|
@ -1324,6 +1354,18 @@ subscribe_room(User, Nick, Room, Nodes) ->
|
||||||
throw({error, "Malformed room JID"})
|
throw({error, "Malformed room JID"})
|
||||||
end.
|
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) ->
|
unsubscribe_room(User, Room) ->
|
||||||
try jid:decode(Room) of
|
try jid:decode(Room) of
|
||||||
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
|
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
|
||||||
|
@ -1406,11 +1448,22 @@ find_hosts(ServerHost) ->
|
||||||
[]
|
[]
|
||||||
end.
|
end.
|
||||||
|
|
||||||
mod_options(_) -> [].
|
mod_opt_type(subscribe_room_many_max_users) ->
|
||||||
|
econf:int().
|
||||||
|
|
||||||
|
mod_options(_) ->
|
||||||
|
[{subscribe_room_many_max_users, 50}].
|
||||||
|
|
||||||
mod_doc() ->
|
mod_doc() ->
|
||||||
#{desc =>
|
#{desc =>
|
||||||
[?T("This module provides commands to administer local MUC "
|
[?T("This module provides commands to administer local MUC "
|
||||||
"services and their MUC rooms. It also provides simple "
|
"services and their MUC rooms. It also provides simple "
|
||||||
"WebAdmin pages to view the existing rooms."), "",
|
"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'.")}}]}.
|
||||||
|
|
|
@ -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_mam/1]).
|
||||||
-export([access_persistent/1]).
|
-export([access_persistent/1]).
|
||||||
-export([access_register/1]).
|
-export([access_register/1]).
|
||||||
|
-export([cleanup_affiliations_on_start/1]).
|
||||||
-export([db_type/1]).
|
-export([db_type/1]).
|
||||||
-export([default_room_options/1]).
|
-export([default_room_options/1]).
|
||||||
-export([hibernation_timeout/1]).
|
-export([hibernation_timeout/1]).
|
||||||
|
@ -73,6 +74,12 @@ access_register(Opts) when is_map(Opts) ->
|
||||||
access_register(Host) ->
|
access_register(Host) ->
|
||||||
gen_mod:get_module_opt(Host, mod_muc, access_register).
|
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().
|
-spec db_type(gen_mod:opts() | global | binary()) -> atom().
|
||||||
db_type(Opts) when is_map(Opts) ->
|
db_type(Opts) when is_map(Opts) ->
|
||||||
gen_mod:get_opt(db_type, Opts);
|
gen_mod:get_opt(db_type, Opts);
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
|
-protocol({xep, 317, '0.1'}).
|
||||||
|
|
||||||
-behaviour(p1_fsm).
|
-behaviour(p1_fsm).
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
|
@ -48,6 +50,7 @@
|
||||||
set_config/2,
|
set_config/2,
|
||||||
get_state/1,
|
get_state/1,
|
||||||
change_item/5,
|
change_item/5,
|
||||||
|
change_item_async/5,
|
||||||
config_reloaded/1,
|
config_reloaded/1,
|
||||||
subscribe/4,
|
subscribe/4,
|
||||||
unsubscribe/2,
|
unsubscribe/2,
|
||||||
|
@ -76,6 +79,12 @@
|
||||||
|
|
||||||
-define(DEFAULT_MAX_USERS_PRESENCE,1000).
|
-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).
|
%-define(DBGFSM, true).
|
||||||
|
|
||||||
-ifdef(DBGFSM).
|
-ifdef(DBGFSM).
|
||||||
|
@ -194,6 +203,11 @@ change_item(Pid, JID, Type, AffiliationOrRole, Reason) ->
|
||||||
{error, notfound}
|
{error, notfound}
|
||||||
end.
|
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}.
|
-spec get_state(pid()) -> {ok, state()} | {error, notfound | timeout}.
|
||||||
get_state(Pid) ->
|
get_state(Pid) ->
|
||||||
try p1_fsm:sync_send_all_state_event(Pid, get_state)
|
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}),
|
room_shaper = Shaper}),
|
||||||
add_to_log(room_existence, started, State),
|
add_to_log(room_existence, started, State),
|
||||||
ejabberd_hooks:run(start_room, ServerHost, [ServerHost, Room, Host]),
|
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, <<"">>,
|
normal_state({route, <<"">>,
|
||||||
#message{from = From, type = Type, lang = Lang} = Packet},
|
#message{from = From, type = Type, lang = Lang} = Packet},
|
||||||
|
@ -446,6 +461,8 @@ normal_state({route, <<"">>,
|
||||||
process_iq_mucsub(From, IQ, StateData);
|
process_iq_mucsub(From, IQ, StateData);
|
||||||
#xcaptcha{} ->
|
#xcaptcha{} ->
|
||||||
process_iq_captcha(From, IQ, StateData);
|
process_iq_captcha(From, IQ, StateData);
|
||||||
|
#adhoc_command{} ->
|
||||||
|
process_iq_adhoc(From, IQ, StateData);
|
||||||
_ ->
|
_ ->
|
||||||
Txt = ?T("The feature requested is not "
|
Txt = ?T("The feature requested is not "
|
||||||
"supported by the conference"),
|
"supported by the conference"),
|
||||||
|
@ -664,6 +681,16 @@ handle_event({set_affiliations, Affiliations},
|
||||||
StateName, StateData) ->
|
StateName, StateData) ->
|
||||||
NewStateData = set_affiliations(Affiliations, StateData),
|
NewStateData = set_affiliations(Affiliations, StateData),
|
||||||
{next_state, StateName, NewStateData};
|
{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) ->
|
handle_event(_Event, StateName, StateData) ->
|
||||||
{next_state, 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
|
case process_item_change(Item, StateData, UJID) of
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
{reply, Err, StateName, StateData};
|
{reply, Err, StateName, StateData};
|
||||||
|
StateData ->
|
||||||
|
{reply, {ok, StateData}, StateName, StateData};
|
||||||
NSD ->
|
NSD ->
|
||||||
store_room(NSD),
|
store_room(NSD),
|
||||||
{reply, {ok, NSD}, StateName, NSD}
|
{reply, {ok, NSD}, StateName, NSD}
|
||||||
|
@ -1405,6 +1434,12 @@ is_occupant_or_admin(JID, StateData) ->
|
||||||
_ -> false
|
_ -> false
|
||||||
end.
|
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
|
%% Decide the fate of the message and its sender
|
||||||
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
|
%% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
|
||||||
-spec decide_fate_message(message(), jid(), state()) ->
|
-spec decide_fate_message(message(), jid(), state()) ->
|
||||||
|
@ -1602,7 +1637,7 @@ do_get_affiliation_fallback(JID, StateData) ->
|
||||||
|
|
||||||
-spec get_affiliations(state()) -> affiliations().
|
-spec get_affiliations(state()) -> affiliations().
|
||||||
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
|
get_affiliations(#state{config = #config{persistent = false}} = StateData) ->
|
||||||
get_affiliations_callback(StateData);
|
get_affiliations_fallback(StateData);
|
||||||
get_affiliations(StateData) ->
|
get_affiliations(StateData) ->
|
||||||
Room = StateData#state.room,
|
Room = StateData#state.room,
|
||||||
Host = StateData#state.host,
|
Host = StateData#state.host,
|
||||||
|
@ -1610,13 +1645,13 @@ get_affiliations(StateData) ->
|
||||||
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
Mod = gen_mod:db_mod(ServerHost, mod_muc),
|
||||||
case Mod:get_affiliations(ServerHost, Room, Host) of
|
case Mod:get_affiliations(ServerHost, Room, Host) of
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
get_affiliations_callback(StateData);
|
get_affiliations_fallback(StateData);
|
||||||
{ok, Affiliations} ->
|
{ok, Affiliations} ->
|
||||||
Affiliations
|
Affiliations
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_affiliations_callback(state()) -> affiliations().
|
-spec get_affiliations_fallback(state()) -> affiliations().
|
||||||
get_affiliations_callback(StateData) ->
|
get_affiliations_fallback(StateData) ->
|
||||||
StateData#state.affiliations.
|
StateData#state.affiliations.
|
||||||
|
|
||||||
-spec get_service_affiliation(jid(), state()) -> owner | none.
|
-spec get_service_affiliation(jid(), state()) -> owner | none.
|
||||||
|
@ -1935,7 +1970,7 @@ filter_presence(Presence) ->
|
||||||
XMLNS = xmpp:get_ns(El),
|
XMLNS = xmpp:get_ns(El),
|
||||||
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
|
case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
|
||||||
?NS_MUC -> false;
|
?NS_MUC -> false;
|
||||||
_ -> true
|
_ -> XMLNS /= ?NS_HATS
|
||||||
end
|
end
|
||||||
end, xmpp:get_els(Presence)),
|
end, xmpp:get_els(Presence)),
|
||||||
xmpp:set_els(Presence, Els).
|
xmpp:set_els(Presence, Els).
|
||||||
|
@ -2485,9 +2520,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
|
||||||
Pres = if Presence == undefined -> #presence{};
|
Pres = if Presence == undefined -> #presence{};
|
||||||
true -> Presence
|
true -> Presence
|
||||||
end,
|
end,
|
||||||
Packet = xmpp:set_subtag(
|
Packet = xmpp:set_subtag(
|
||||||
Pres, #muc_user{items = [Item],
|
add_presence_hats(NJID, Pres, StateData),
|
||||||
status_codes = StatusCodes}),
|
#muc_user{items = [Item],
|
||||||
|
status_codes = StatusCodes}),
|
||||||
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
|
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
|
||||||
Info#user.jid, Packet, Node1, StateData),
|
Info#user.jid, Packet, Node1, StateData),
|
||||||
Type = xmpp:get_type(Packet),
|
Type = xmpp:get_type(Packet),
|
||||||
|
@ -2536,7 +2572,9 @@ send_existing_presences1(ToJID, StateData) ->
|
||||||
false -> Item0
|
false -> Item0
|
||||||
end,
|
end,
|
||||||
Packet = xmpp:set_subtag(
|
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),
|
send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
|
||||||
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
|
RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
|
||||||
end
|
end
|
||||||
|
@ -3579,7 +3617,8 @@ get_config(Lang, StateData, From) ->
|
||||||
{allow_voice_requests, Config#config.allow_voice_requests},
|
{allow_voice_requests, Config#config.allow_voice_requests},
|
||||||
{allow_subscription, Config#config.allow_subscription},
|
{allow_subscription, Config#config.allow_subscription},
|
||||||
{voice_request_min_interval, Config#config.voice_request_min_interval},
|
{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
|
case ejabberd_captcha:is_feature_available() of
|
||||||
true ->
|
true ->
|
||||||
|
@ -3667,6 +3706,7 @@ set_config(Opts, Config, ServerHost, Lang) ->
|
||||||
({maxusers, V}, C) -> C#config{max_users = V};
|
({maxusers, V}, C) -> C#config{max_users = V};
|
||||||
({enablelogging, V}, C) -> C#config{logging = V};
|
({enablelogging, V}, C) -> C#config{logging = V};
|
||||||
({pubsub, V}, C) -> C#config{pubsub = V};
|
({pubsub, V}, C) -> C#config{pubsub = V};
|
||||||
|
({enable_hats, V}, C) -> C#config{enable_hats = V};
|
||||||
({lang, L}, C) -> C#config{lang = L};
|
({lang, L}, C) -> C#config{lang = L};
|
||||||
({captcha_whitelist, Js}, C) ->
|
({captcha_whitelist, Js}, C) ->
|
||||||
LJIDs = [jid:tolower(J) || J <- Js],
|
LJIDs = [jid:tolower(J) || J <- Js],
|
||||||
|
@ -3897,6 +3937,9 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
||||||
allow_subscription ->
|
allow_subscription ->
|
||||||
StateData#state{config =
|
StateData#state{config =
|
||||||
(StateData#state.config)#config{allow_subscription = Val}};
|
(StateData#state.config)#config{allow_subscription = Val}};
|
||||||
|
enable_hats ->
|
||||||
|
StateData#state{config =
|
||||||
|
(StateData#state.config)#config{enable_hats = Val}};
|
||||||
lang ->
|
lang ->
|
||||||
StateData#state{config =
|
StateData#state{config =
|
||||||
(StateData#state.config)#config{lang = Val}};
|
(StateData#state.config)#config{lang = Val}};
|
||||||
|
@ -3927,6 +3970,11 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
||||||
end,
|
end,
|
||||||
StateData#state{subject = Subj};
|
StateData#state{subject = Subj};
|
||||||
subject_author -> StateData#state{subject_author = Val};
|
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
|
_ -> StateData
|
||||||
end,
|
end,
|
||||||
set_opts(Opts, NSD).
|
set_opts(Opts, NSD).
|
||||||
|
@ -3983,6 +4031,7 @@ make_opts(StateData) ->
|
||||||
?MAKE_CONFIG_OPT(#config.vcard),
|
?MAKE_CONFIG_OPT(#config.vcard),
|
||||||
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
|
?MAKE_CONFIG_OPT(#config.vcard_xupdate),
|
||||||
?MAKE_CONFIG_OPT(#config.pubsub),
|
?MAKE_CONFIG_OPT(#config.pubsub),
|
||||||
|
?MAKE_CONFIG_OPT(#config.enable_hats),
|
||||||
?MAKE_CONFIG_OPT(#config.lang),
|
?MAKE_CONFIG_OPT(#config.lang),
|
||||||
{captcha_whitelist,
|
{captcha_whitelist,
|
||||||
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
|
(?SETS):to_list((StateData#state.config)#config.captcha_whitelist)},
|
||||||
|
@ -3990,6 +4039,9 @@ make_opts(StateData) ->
|
||||||
maps:to_list(StateData#state.affiliations)},
|
maps:to_list(StateData#state.affiliations)},
|
||||||
{subject, StateData#state.subject},
|
{subject, StateData#state.subject},
|
||||||
{subject_author, StateData#state.subject_author},
|
{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)},
|
{hibernation_time, erlang:system_time(microsecond)},
|
||||||
{subscribers, Subscribers}].
|
{subscribers, Subscribers}].
|
||||||
|
|
||||||
|
@ -4080,6 +4132,7 @@ maybe_forget_room(StateData) ->
|
||||||
make_disco_info(_From, StateData) ->
|
make_disco_info(_From, StateData) ->
|
||||||
Config = StateData#state.config,
|
Config = StateData#state.config,
|
||||||
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
Feats = [?NS_VCARD, ?NS_MUC, ?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||||
|
?NS_COMMANDS,
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
||||||
<<"muc_public">>, <<"muc_hidden">>),
|
<<"muc_public">>, <<"muc_hidden">>),
|
||||||
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
|
?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),
|
DiscoInfo = make_disco_info(From, StateData),
|
||||||
Extras = iq_disco_info_extras(Lang, StateData, false),
|
Extras = iq_disco_info_extras(Lang, StateData, false),
|
||||||
{result, DiscoInfo#disco_info{xdata = [Extras]}};
|
{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,
|
process_iq_disco_info(From, #iq{type = get, lang = Lang,
|
||||||
sub_els = [#disco_info{node = Node}]},
|
sub_els = [#disco_info{node = Node}]},
|
||||||
StateData) ->
|
StateData) ->
|
||||||
|
@ -4199,6 +4323,46 @@ process_iq_disco_items(From, #iq{type = get, sub_els = [#disco_items{node = <<>>
|
||||||
{result, #disco_items{}}
|
{result, #disco_items{}}
|
||||||
end
|
end
|
||||||
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) ->
|
process_iq_disco_items(_From, #iq{lang = Lang}, _StateData) ->
|
||||||
Txt = ?T("Node not found"),
|
Txt = ?T("Node not found"),
|
||||||
{error, xmpp:err_item_not_found(Txt, Lang)}.
|
{error, xmpp:err_item_not_found(Txt, Lang)}.
|
||||||
|
@ -4441,6 +4605,271 @@ get_mucroom_disco_items(StateData) ->
|
||||||
end, [], StateData#state.nicks),
|
end, [], StateData#state.nicks),
|
||||||
#disco_items{items = Items}.
|
#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
|
% Voice request support
|
||||||
|
|
||||||
|
@ -4687,7 +5116,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
|
||||||
id = p1_rand:get_string(),
|
id = p1_rand:get_string(),
|
||||||
sub_els = [Payload1]}]}}]},
|
sub_els = [Payload1]}]}}]},
|
||||||
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
|
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
|
||||||
WJ, Packet1, true);
|
WJ, Packet1, false);
|
||||||
true -> ok
|
true -> ok
|
||||||
end,
|
end,
|
||||||
if WN /= [] ->
|
if WN /= [] ->
|
||||||
|
@ -4703,7 +5132,7 @@ send_subscriptions_change_notifications(From, Nick, Type, State) ->
|
||||||
id = p1_rand:get_string(),
|
id = p1_rand:get_string(),
|
||||||
sub_els = [Payload2]}]}}]},
|
sub_els = [Payload2]}]}}]},
|
||||||
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
|
ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
|
||||||
WN, Packet2, true);
|
WN, Packet2, false);
|
||||||
true -> ok
|
true -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -4927,6 +5356,23 @@ muc_subscribers_put(Subscriber, MUCSubscribers) ->
|
||||||
subscriber_nodes = NewSubNodes}.
|
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
|
%% Detect messange stanzas that don't have meaningful content
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
register_online_user/4, unregister_online_user/4,
|
register_online_user/4, unregister_online_user/4,
|
||||||
count_online_rooms_by_user/3, get_online_rooms_by_user/3,
|
count_online_rooms_by_user/3, get_online_rooms_by_user/3,
|
||||||
get_subscribed_rooms/3, get_rooms_without_subscribers/2,
|
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,
|
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
|
||||||
get_affiliations/3, search_affiliation/4]).
|
get_affiliations/3, search_affiliation/4]).
|
||||||
|
|
||||||
|
@ -465,6 +465,13 @@ get_subscribed_rooms(LServer, Host, Jid) ->
|
||||||
{error, db_failure}
|
{error, db_failure}
|
||||||
end.
|
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
|
%%% Internal functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start/2, stop/1, reload/3,
|
-export([start/2, stop/1, reload/3,
|
||||||
user_send_packet/1]).
|
user_send_packet/1]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_info/2, handle_call/3,
|
-export([init/1, handle_info/2, handle_call/3,
|
||||||
|
@ -51,11 +51,6 @@
|
||||||
response,
|
response,
|
||||||
ts :: integer()}).
|
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()}.
|
-type limit_value() :: {default | custom, integer()}.
|
||||||
-record(limits, {message :: limit_value(),
|
-record(limits, {message :: limit_value(),
|
||||||
presence :: limit_value()}).
|
presence :: limit_value()}).
|
||||||
|
@ -63,14 +58,6 @@
|
||||||
-record(service_limits, {local :: #limits{},
|
-record(service_limits, {local :: #limits{},
|
||||||
remote :: #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(),
|
-record(state, {lserver :: binary(),
|
||||||
lservice :: binary(),
|
lservice :: binary(),
|
||||||
access :: atom(),
|
access :: atom(),
|
||||||
|
@ -117,7 +104,7 @@ reload(LServerS, NewOpts, OldOpts) ->
|
||||||
user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
|
user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
|
||||||
case xmpp:get_subtag(Packet, #addresses{}) of
|
case xmpp:get_subtag(Packet, #addresses{}) of
|
||||||
#addresses{list = Addresses} ->
|
#addresses{list = Addresses} ->
|
||||||
{ToDeliver, _Delivereds} = split_addresses_todeliver(Addresses),
|
{CC, BCC, _Invalid, _Delivered} = partition_addresses(Addresses),
|
||||||
NewState =
|
NewState =
|
||||||
lists:foldl(
|
lists:foldl(
|
||||||
fun(Address, St) ->
|
fun(Address, St) ->
|
||||||
|
@ -138,7 +125,7 @@ user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
|
||||||
undefined ->
|
undefined ->
|
||||||
St
|
St
|
||||||
end
|
end
|
||||||
end, C2SState, ToDeliver),
|
end, C2SState, CC ++ BCC),
|
||||||
{Packet, NewState};
|
{Packet, NewState};
|
||||||
false ->
|
false ->
|
||||||
Acc
|
Acc
|
||||||
|
@ -308,19 +295,10 @@ iq_vcard(Lang, State) ->
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
||||||
-spec route_trusted(binary(), binary(), jid(), [jid()], stanza()) -> 'ok'.
|
-spec route_trusted(binary(), binary(), jid(), [jid()], stanza()) -> 'ok'.
|
||||||
route_trusted(LServiceS, LServerS, FromJID,
|
route_trusted(LServiceS, LServerS, FromJID, Destinations, Packet) ->
|
||||||
Destinations, Packet) ->
|
Addresses = [#address{type = bcc, jid = D} || D <- Destinations],
|
||||||
Packet_stripped = Packet,
|
Groups = group_by_destinations(Addresses, #{}),
|
||||||
Delivereds = [],
|
route_grouped(LServerS, LServiceS, FromJID, Groups, [], Packet).
|
||||||
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).
|
|
||||||
|
|
||||||
-spec route_untrusted(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
|
-spec route_untrusted(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
|
||||||
route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
|
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) ->
|
route_untrusted2(LServiceS, LServerS, Access, SLimits, Packet) ->
|
||||||
FromJID = xmpp:get_from(Packet),
|
FromJID = xmpp:get_from(Packet),
|
||||||
ok = check_access(LServerS, Access, FromJID),
|
ok = check_access(LServerS, Access, FromJID),
|
||||||
{ok, Packet_stripped, Addresses} = strip_addresses_element(Packet),
|
{ok, PacketStripped, Addresses} = strip_addresses_element(Packet),
|
||||||
{To_deliver, Delivereds} = split_addresses_todeliver(Addresses),
|
{CC, BCC, NotJids, Rest} = partition_addresses(Addresses),
|
||||||
Dests = convert_dest_record(To_deliver),
|
report_not_jid(FromJID, Packet, NotJids),
|
||||||
{Dests2, Not_jids} = split_dests_jid(Dests),
|
ok = check_limit_dests(SLimits, FromJID, Packet, length(CC) + length(BCC)),
|
||||||
report_not_jid(FromJID, Packet, Not_jids),
|
Groups0 = group_by_destinations(CC, #{}),
|
||||||
ok = check_limit_dests(SLimits, FromJID, Packet, Dests2),
|
Groups = group_by_destinations(BCC, Groups0),
|
||||||
Groups = group_dests(Dests2),
|
|
||||||
ok = check_relay(FromJID#jid.server, LServerS, Groups),
|
ok = check_relay(FromJID#jid.server, LServerS, Groups),
|
||||||
route_common(LServerS, LServiceS, FromJID, Groups,
|
route_grouped(LServerS, LServiceS, FromJID, Groups, Rest, PacketStripped).
|
||||||
Delivereds, Packet_stripped).
|
|
||||||
|
|
||||||
-spec route_common(binary(), binary(), jid(), [#group{}],
|
-spec mark_as_delivered([address()]) -> [address()].
|
||||||
[address()], stanza()) -> 'ok'.
|
mark_as_delivered(Addresses) ->
|
||||||
route_common(LServerS, LServiceS, FromJID, Groups,
|
[A#address{delivered = true} || A <- Addresses].
|
||||||
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 act_groups(jid(), stanza(), binary(), [{routing(), #group{}}]) -> 'ok'.
|
-spec route_individual(jid(), [address()], [address()], [address()], stanza()) -> ok.
|
||||||
act_groups(FromJID, Packet_stripped, LServiceS, AGroups) ->
|
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(
|
lists:foreach(
|
||||||
fun(AGroup) ->
|
fun(#address{jid = To}) ->
|
||||||
perform(FromJID, Packet_stripped, LServiceS,
|
ejabberd_router:route(xmpp:set_from_to(PacketWithAddresses, From, To))
|
||||||
AGroup)
|
end, CC),
|
||||||
end, AGroups).
|
|
||||||
|
|
||||||
-spec perform(jid(), stanza(), binary(),
|
|
||||||
{routing(), #group{}}) -> 'ok'.
|
|
||||||
perform(From, Packet, _,
|
|
||||||
{route_single, Group}) ->
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(ToUser) ->
|
fun(#address{jid = To} = Address) ->
|
||||||
Group_others = strip_other_bcc(ToUser, Group#group.others),
|
Packet2 = case Addresses of
|
||||||
route_packet(From, ToUser, Packet,
|
[] ->
|
||||||
Group_others, Group#group.addresses)
|
Packet;
|
||||||
end, Group#group.dests);
|
_ ->
|
||||||
perform(From, Packet, _,
|
xmpp:append_subtags(Packet, [#addresses{list = [Address | Addresses]}])
|
||||||
{{route_multicast, JID, RLimits}, Group}) ->
|
end,
|
||||||
route_packet_multicast(From, JID, Packet,
|
ejabberd_router:route(xmpp:set_from_to(Packet2, From, To))
|
||||||
Group#group.dests, Group#group.addresses, RLimits).
|
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
|
%%% Check access permission
|
||||||
|
@ -425,245 +441,89 @@ strip_addresses_element(Packet) ->
|
||||||
throw(eadsele)
|
throw(eadsele)
|
||||||
end.
|
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
|
%%% Split Addresses
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
||||||
-spec split_addresses_todeliver([address()]) -> {[address()], [address()]}.
|
partition_addresses(Addresses) ->
|
||||||
split_addresses_todeliver(Addresses) ->
|
lists:foldl(
|
||||||
lists:partition(
|
fun(#address{delivered = true} = A, {C, B, I, D}) ->
|
||||||
fun(#address{delivered = true}) ->
|
{C, B, I, [A | D]};
|
||||||
false;
|
(#address{type = T, jid = undefined} = A, {C, B, I, D})
|
||||||
(#address{type = Type}) ->
|
when T == to; T == cc; T == bcc ->
|
||||||
case Type of
|
{C, B, [A | I], D};
|
||||||
to -> true;
|
(#address{type = T} = A, {C, B, I, D})
|
||||||
cc -> true;
|
when T == to; T == cc ->
|
||||||
bcc -> true;
|
{[A | C], B, I, D};
|
||||||
_ -> false
|
(#address{type = bcc} = A, {C, B, I, D}) ->
|
||||||
end
|
{C, [A | B], I, D};
|
||||||
end, Addresses).
|
(A, {C, B, I, D}) ->
|
||||||
|
{C, B, I, [A | D]}
|
||||||
|
end, {[], [], [], []}, Addresses).
|
||||||
|
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
%%% Check does not exceed limit of destinations
|
%%% Check does not exceed limit of destinations
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
||||||
-spec check_limit_dests(#service_limits{}, jid(), stanza(), [address()]) -> ok.
|
-spec check_limit_dests(#service_limits{}, jid(), stanza(), integer()) -> ok.
|
||||||
check_limit_dests(SLimits, FromJID, Packet,
|
check_limit_dests(SLimits, FromJID, Packet, NumOfAddresses) ->
|
||||||
Addresses) ->
|
|
||||||
SenderT = sender_type(FromJID),
|
SenderT = sender_type(FromJID),
|
||||||
Limits = get_slimit_group(SenderT, SLimits),
|
Limits = get_slimit_group(SenderT, SLimits),
|
||||||
Type_of_stanza = type_of_stanza(Packet),
|
StanzaType = type_of_stanza(Packet),
|
||||||
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
|
{_Type, Limit} = get_limit_number(StanzaType,
|
||||||
Limits),
|
Limits),
|
||||||
case length(Addresses) > Limit_number of
|
case NumOfAddresses > Limit of
|
||||||
false -> ok;
|
false -> ok;
|
||||||
true -> throw(etoorec)
|
true -> throw(etoorec)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%-------------------------
|
|
||||||
%%% Convert Destination XML to record
|
|
||||||
%%%-------------------------
|
|
||||||
|
|
||||||
-spec convert_dest_record([address()]) -> [#dest{}].
|
-spec report_not_jid(jid(), stanza(), [address()]) -> any().
|
||||||
convert_dest_record(Addrs) ->
|
report_not_jid(From, Packet, Addresses) ->
|
||||||
lists:map(
|
lists:foreach(
|
||||||
fun(#address{jid = undefined, type = Type} = Addr) ->
|
fun(Address) ->
|
||||||
#dest{jid_string = none,
|
route_error(
|
||||||
type = Type, address = Addr};
|
xmpp:set_from_to(Packet, From, From), jid_malformed,
|
||||||
(#address{jid = JID, type = Type} = Addr) ->
|
str:format(?T("This service can not process the address: ~s"),
|
||||||
#dest{jid_string = jid:encode(JID), jid_jid = JID,
|
[fxml:element_to_binary(xmpp:encode(Address))]))
|
||||||
type = Type, address = Addr}
|
end, Addresses).
|
||||||
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].
|
|
||||||
|
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
%%% Group destinations by their servers
|
%%% Group destinations by their servers
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
||||||
-spec group_dests([#dest{}]) -> [#group{}].
|
group_by_destinations(Addrs, Map) ->
|
||||||
group_dests(Dests) ->
|
lists:foldl(
|
||||||
D = lists:foldl(fun (Dest, Dict) ->
|
fun
|
||||||
ServerS = (Dest#dest.jid_jid)#jid.server,
|
(#address{type = Type, jid = #jid{lserver = Server}} = Addr, Map2) when Type == to; Type == cc ->
|
||||||
dict:append(ServerS, Dest, Dict)
|
maps:update_with(Server,
|
||||||
end,
|
fun({CC, BCC}) ->
|
||||||
dict:new(), Dests),
|
{[Addr | CC], BCC}
|
||||||
Keys = dict:fetch_keys(D),
|
end, {[Addr], []}, Map2);
|
||||||
[#group{server = Key, dests = dict:fetch(Key, D),
|
(#address{type = bcc, jid = #jid{lserver = Server}} = Addr, Map2) ->
|
||||||
addresses = [], others = []}
|
maps:update_with(Server,
|
||||||
|| Key <- Keys].
|
fun({CC, BCC}) ->
|
||||||
|
{CC, [Addr | BCC]}
|
||||||
%%%-------------------------
|
end, {[], [Addr]}, Map2)
|
||||||
%%% Look for cached responses
|
end, Map, Addrs).
|
||||||
%%%-------------------------
|
|
||||||
|
|
||||||
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].
|
|
||||||
|
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
%%% Route packet
|
%%% 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
|
%%% Check relay
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
||||||
-spec check_relay(binary(), binary(), [#group{}]) -> ok.
|
-spec check_relay(binary(), binary(), #{}) -> ok.
|
||||||
check_relay(RS, LS, Gs) ->
|
check_relay(RS, LS, Gs) ->
|
||||||
case check_relay_required(RS, LS, Gs) of
|
case lists:suffix(str:tokens(LS, <<".">>),
|
||||||
false -> ok;
|
str:tokens(RS, <<".">>)) orelse
|
||||||
true -> throw(edrelay)
|
(maps:is_key(LS, Gs) andalso maps:size(Gs) == 1) of
|
||||||
|
true -> ok;
|
||||||
|
_ -> throw(edrelay)
|
||||||
end.
|
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
|
%%% Check protocol support: Send request
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
@ -1060,20 +920,6 @@ get_slimit_group(local, SLimits) ->
|
||||||
get_slimit_group(remote, SLimits) ->
|
get_slimit_group(remote, SLimits) ->
|
||||||
SLimits#service_limits.remote.
|
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
|
%%% Limits: XEP-0128 Service Discovery Extensions
|
||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
|
terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
|
||||||
|
|
||||||
%% ejabberd commands
|
%% 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]).
|
-export([route/1]).
|
||||||
|
|
||||||
|
@ -3431,6 +3431,14 @@ max_items(Host, Options) ->
|
||||||
end
|
end
|
||||||
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(),
|
-spec get_configure_xfields(_, pubsub_node_config:result(),
|
||||||
binary(), [binary()]) -> [xdata_field()].
|
binary(), [binary()]) -> [xdata_field()].
|
||||||
get_configure_xfields(_Type, Options, Lang, Groups) ->
|
get_configure_xfields(_Type, Options, Lang, Groups) ->
|
||||||
|
@ -3504,17 +3512,24 @@ decode_node_config(undefined, _, _) ->
|
||||||
decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
|
decode_node_config(#xdata{fields = Fs}, Host, Lang) ->
|
||||||
try
|
try
|
||||||
Config = pubsub_node_config:decode(Fs),
|
Config = pubsub_node_config:decode(Fs),
|
||||||
Max = get_max_items_node(Host),
|
MaxItems = get_max_items_node(Host),
|
||||||
case {check_opt_range(max_items, Config, Max),
|
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
|
check_opt_range(max_payload_size, Config, ?MAX_PAYLOAD_SIZE)} of
|
||||||
{true, true} ->
|
{true, true, true} ->
|
||||||
Config;
|
Config;
|
||||||
{true, false} ->
|
{true, true, false} ->
|
||||||
erlang:error(
|
erlang:error(
|
||||||
{pubsub_node_config,
|
{pubsub_node_config,
|
||||||
{bad_var_value, <<"pubsub#max_payload_size">>,
|
{bad_var_value, <<"pubsub#max_payload_size">>,
|
||||||
?NS_PUBSUB_NODE_CONFIG}});
|
?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(
|
erlang:error(
|
||||||
{pubsub_node_config,
|
{pubsub_node_config,
|
||||||
{bad_var_value, <<"pubsub#max_items">>,
|
{bad_var_value, <<"pubsub#max_items">>,
|
||||||
|
@ -3560,20 +3575,24 @@ decode_get_pending(#xdata{fields = Fs}, Lang) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec check_opt_range(atom(), [proplists:property()],
|
-spec check_opt_range(atom(), [proplists:property()],
|
||||||
non_neg_integer() | unlimited | undefined) -> boolean().
|
non_neg_integer() | unlimited | infinity) -> boolean().
|
||||||
check_opt_range(_Opt, _Opts, undefined) ->
|
|
||||||
true;
|
|
||||||
check_opt_range(_Opt, _Opts, unlimited) ->
|
check_opt_range(_Opt, _Opts, unlimited) ->
|
||||||
true;
|
true;
|
||||||
|
check_opt_range(_Opt, _Opts, infinity) ->
|
||||||
|
true;
|
||||||
check_opt_range(Opt, Opts, Max) ->
|
check_opt_range(Opt, Opts, Max) ->
|
||||||
case proplists:get_value(Opt, Opts, Max) of
|
case proplists:get_value(Opt, Opts, Max) of
|
||||||
max -> true;
|
max -> true;
|
||||||
Val -> Val =< Max
|
Val -> Val =< Max
|
||||||
end.
|
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) ->
|
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().
|
-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer().
|
||||||
get_max_subscriptions_node(Host) ->
|
get_max_subscriptions_node(Host) ->
|
||||||
|
@ -4181,16 +4200,63 @@ delete_old_items(N) ->
|
||||||
ok
|
ok
|
||||||
end.
|
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()].
|
-spec get_commands_spec() -> [ejabberd_commands()].
|
||||||
get_commands_spec() ->
|
get_commands_spec() ->
|
||||||
[#ejabberd_commands{name = delete_old_pubsub_items, tags = [purge],
|
[#ejabberd_commands{name = delete_old_pubsub_items, tags = [purge],
|
||||||
desc = "Keep only NUMBER of PubSub items per node",
|
desc = "Keep only NUMBER of PubSub items per node",
|
||||||
|
note = "added in 21.12",
|
||||||
module = ?MODULE, function = delete_old_items,
|
module = ?MODULE, function = delete_old_items,
|
||||||
args_desc = ["Number of items to keep per node"],
|
args_desc = ["Number of items to keep per node"],
|
||||||
args = [{number, integer}],
|
args = [{number, integer}],
|
||||||
result = {res, rescode},
|
result = {res, rescode},
|
||||||
result_desc = "0 if command failed, 1 when succeeded",
|
result_desc = "0 if command failed, 1 when succeeded",
|
||||||
args_example = [1000],
|
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}].
|
result_example = ok}].
|
||||||
|
|
||||||
-spec mod_opt_type(atom()) -> econf:validator().
|
-spec mod_opt_type(atom()) -> econf:validator().
|
||||||
|
@ -4204,6 +4270,8 @@ mod_opt_type(last_item_cache) ->
|
||||||
econf:bool();
|
econf:bool();
|
||||||
mod_opt_type(max_items_node) ->
|
mod_opt_type(max_items_node) ->
|
||||||
econf:non_neg_int(unlimited);
|
econf:non_neg_int(unlimited);
|
||||||
|
mod_opt_type(max_item_expire_node) ->
|
||||||
|
econf:timeout(second, infinity);
|
||||||
mod_opt_type(max_nodes_discoitems) ->
|
mod_opt_type(max_nodes_discoitems) ->
|
||||||
econf:non_neg_int(infinity);
|
econf:non_neg_int(infinity);
|
||||||
mod_opt_type(max_subscriptions_node) ->
|
mod_opt_type(max_subscriptions_node) ->
|
||||||
|
@ -4251,6 +4319,7 @@ mod_options(Host) ->
|
||||||
{ignore_pep_from_offline, true},
|
{ignore_pep_from_offline, true},
|
||||||
{last_item_cache, false},
|
{last_item_cache, false},
|
||||||
{max_items_node, ?MAXITEMS},
|
{max_items_node, ?MAXITEMS},
|
||||||
|
{max_item_expire_node, infinity},
|
||||||
{max_nodes_discoitems, 100},
|
{max_nodes_discoitems, 100},
|
||||||
{nodetree, ?STDTREE},
|
{nodetree, ?STDTREE},
|
||||||
{pep_mapping, []},
|
{pep_mapping, []},
|
||||||
|
@ -4329,11 +4398,17 @@ mod_doc() ->
|
||||||
" so many nodes, caching last items speeds up pubsub "
|
" so many nodes, caching last items speeds up pubsub "
|
||||||
"and allows to raise user connection rate. The cost "
|
"and allows to raise user connection rate. The cost "
|
||||||
"is memory usage, as every item is stored in memory.")}},
|
"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,
|
{max_items_node,
|
||||||
#{value => "non_neg_integer() | infinity",
|
#{value => "non_neg_integer() | infinity",
|
||||||
desc =>
|
desc =>
|
||||||
?T("Define the maximum number of items that can be "
|
?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,
|
{max_nodes_discoitems,
|
||||||
#{value => "pos_integer() | infinity",
|
#{value => "pos_integer() | infinity",
|
||||||
desc =>
|
desc =>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
-export([hosts/1]).
|
-export([hosts/1]).
|
||||||
-export([ignore_pep_from_offline/1]).
|
-export([ignore_pep_from_offline/1]).
|
||||||
-export([last_item_cache/1]).
|
-export([last_item_cache/1]).
|
||||||
|
-export([max_item_expire_node/1]).
|
||||||
-export([max_items_node/1]).
|
-export([max_items_node/1]).
|
||||||
-export([max_nodes_discoitems/1]).
|
-export([max_nodes_discoitems/1]).
|
||||||
-export([max_subscriptions_node/1]).
|
-export([max_subscriptions_node/1]).
|
||||||
|
@ -68,7 +69,13 @@ last_item_cache(Opts) when is_map(Opts) ->
|
||||||
last_item_cache(Host) ->
|
last_item_cache(Host) ->
|
||||||
gen_mod:get_module_opt(Host, mod_pubsub, last_item_cache).
|
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) ->
|
max_items_node(Opts) when is_map(Opts) ->
|
||||||
gen_mod:get_opt(max_items_node, Opts);
|
gen_mod:get_opt(max_items_node, Opts);
|
||||||
max_items_node(Host) ->
|
max_items_node(Host) ->
|
||||||
|
|
|
@ -52,7 +52,7 @@ store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
|
||||||
case ?SQL_UPSERT(LServer, "push_session",
|
case ?SQL_UPSERT(LServer, "push_session",
|
||||||
["!username=%(LUser)s",
|
["!username=%(LUser)s",
|
||||||
"!server_host=%(LServer)s",
|
"!server_host=%(LServer)s",
|
||||||
"!timestamp=%(TS)d",
|
"timestamp=%(TS)d",
|
||||||
"!service=%(Service)s",
|
"!service=%(Service)s",
|
||||||
"!node=%(Node)s",
|
"!node=%(Node)s",
|
||||||
"xml=%(XML)s"]) of
|
"xml=%(XML)s"]) of
|
||||||
|
|
|
@ -32,11 +32,13 @@
|
||||||
-behaviour(gen_mod).
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
-export([start/2, stop/1, reload/3, stream_feature_register/2,
|
-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,
|
process_iq/1, send_registration_notifications/3,
|
||||||
mod_opt_type/1, mod_options/1, depends/2,
|
mod_opt_type/1, mod_options/1, depends/2,
|
||||||
format_error/1, mod_doc/0]).
|
format_error/1, mod_doc/0]).
|
||||||
|
|
||||||
|
-deprecated({try_register, 4}).
|
||||||
|
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include_lib("xmpp/include/xmpp.hrl").
|
-include_lib("xmpp/include/xmpp.hrl").
|
||||||
-include("translate.hrl").
|
-include("translate.hrl").
|
||||||
|
@ -283,7 +285,7 @@ try_register_or_set_password(User, Server, Password,
|
||||||
_ when CaptchaSucceed ->
|
_ when CaptchaSucceed ->
|
||||||
case check_from(From, Server) of
|
case check_from(From, Server) of
|
||||||
allow ->
|
allow ->
|
||||||
case try_register(User, Server, Password, Source, Lang) of
|
case try_register(User, Server, Password, Source, ?MODULE, Lang) of
|
||||||
ok ->
|
ok ->
|
||||||
xmpp:make_iq_result(IQ);
|
xmpp:make_iq_result(IQ);
|
||||||
{error, Error} ->
|
{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))
|
xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang))
|
||||||
end.
|
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) ->
|
try_register(User, Server, Password, SourceRaw) ->
|
||||||
case jid:is_nodename(User) of
|
case jid:is_nodename(User) of
|
||||||
false ->
|
false ->
|
||||||
|
@ -363,8 +372,8 @@ try_register(User, Server, Password, SourceRaw) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
try_register(User, Server, Password, SourceRaw, Lang) ->
|
try_register(User, Server, Password, SourceRaw, Module, Lang) ->
|
||||||
case try_register(User, Server, Password, SourceRaw) of
|
case try_register(User, Server, Password, SourceRaw, Module) of
|
||||||
ok ->
|
ok ->
|
||||||
JID = jid:make(User, Server),
|
JID = jid:make(User, Server),
|
||||||
Source = may_remove_resource(SourceRaw),
|
Source = may_remove_resource(SourceRaw),
|
||||||
|
@ -597,6 +606,8 @@ mod_opt_type(access_from) ->
|
||||||
econf:acl();
|
econf:acl();
|
||||||
mod_opt_type(access_remove) ->
|
mod_opt_type(access_remove) ->
|
||||||
econf:acl();
|
econf:acl();
|
||||||
|
mod_opt_type(allow_modules) ->
|
||||||
|
econf:either(all, econf:list(econf:atom()));
|
||||||
mod_opt_type(captcha_protected) ->
|
mod_opt_type(captcha_protected) ->
|
||||||
econf:bool();
|
econf:bool();
|
||||||
mod_opt_type(ip_access) ->
|
mod_opt_type(ip_access) ->
|
||||||
|
@ -623,6 +634,7 @@ mod_options(_Host) ->
|
||||||
[{access, all},
|
[{access, all},
|
||||||
{access_from, none},
|
{access_from, none},
|
||||||
{access_remove, all},
|
{access_remove, all},
|
||||||
|
{allow_modules, all},
|
||||||
{captcha_protected, false},
|
{captcha_protected, false},
|
||||||
{ip_access, all},
|
{ip_access, all},
|
||||||
{password_strength, 0},
|
{password_strength, 0},
|
||||||
|
@ -661,6 +673,13 @@ mod_doc() ->
|
||||||
desc =>
|
desc =>
|
||||||
?T("Specify rules to restrict access for user unregistration. "
|
?T("Specify rules to restrict access for user unregistration. "
|
||||||
"By default any user is able to unregister their account.")}},
|
"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,
|
{captcha_protected,
|
||||||
#{value => "true | false",
|
#{value => "true | false",
|
||||||
desc =>
|
desc =>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
-export([access/1]).
|
-export([access/1]).
|
||||||
-export([access_from/1]).
|
-export([access_from/1]).
|
||||||
-export([access_remove/1]).
|
-export([access_remove/1]).
|
||||||
|
-export([allow_modules/1]).
|
||||||
-export([captcha_protected/1]).
|
-export([captcha_protected/1]).
|
||||||
-export([ip_access/1]).
|
-export([ip_access/1]).
|
||||||
-export([password_strength/1]).
|
-export([password_strength/1]).
|
||||||
|
@ -31,6 +32,12 @@ access_remove(Opts) when is_map(Opts) ->
|
||||||
access_remove(Host) ->
|
access_remove(Host) ->
|
||||||
gen_mod:get_module_opt(Host, mod_register, access_remove).
|
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().
|
-spec captcha_protected(gen_mod:opts() | global | binary()) -> boolean().
|
||||||
captcha_protected(Opts) when is_map(Opts) ->
|
captcha_protected(Opts) when is_map(Opts) ->
|
||||||
gen_mod:get_opt(captcha_protected, Opts);
|
gen_mod:get_opt(captcha_protected, Opts);
|
||||||
|
|
|
@ -85,7 +85,7 @@ process([Section],
|
||||||
process([<<"new">>],
|
process([<<"new">>],
|
||||||
#request{method = 'POST', q = Q, ip = {Ip, _Port},
|
#request{method = 'POST', q = Q, ip = {Ip, _Port},
|
||||||
lang = Lang, host = _HTTPHost}) ->
|
lang = Lang, host = _HTTPHost}) ->
|
||||||
case form_new_post(Q) of
|
case form_new_post(Q, Ip) of
|
||||||
{success, ok, {Username, Host, _Password}} ->
|
{success, ok, {Username, Host, _Password}} ->
|
||||||
Jid = jid:make(Username, Host),
|
Jid = jid:make(Username, Host),
|
||||||
mod_register:send_registration_notifications(?MODULE, Jid, Ip),
|
mod_register:send_registration_notifications(?MODULE, Jid, Ip),
|
||||||
|
@ -290,10 +290,10 @@ form_new_get2(Host, Lang, CaptchaEls) ->
|
||||||
%%% Formulary new POST
|
%%% Formulary new POST
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
form_new_post(Q) ->
|
form_new_post(Q, Ip) ->
|
||||||
case catch get_register_parameters(Q) of
|
case catch get_register_parameters(Q) of
|
||||||
[Username, Host, Password, Password, Id, Key] ->
|
[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] ->
|
[_Username, _Host, _Password, _Password2, false, false] ->
|
||||||
{error, passwords_not_identical};
|
{error, passwords_not_identical};
|
||||||
[_Username, _Host, _Password, _Password2, Id, Key] ->
|
[_Username, _Host, _Password, _Password2, Id, Key] ->
|
||||||
|
@ -312,13 +312,12 @@ get_register_parameters(Q) ->
|
||||||
[<<"username">>, <<"host">>, <<"password">>, <<"password2">>,
|
[<<"username">>, <<"host">>, <<"password">>, <<"password2">>,
|
||||||
<<"id">>, <<"key">>]).
|
<<"id">>, <<"key">>]).
|
||||||
|
|
||||||
form_new_post(Username, Host, Password,
|
form_new_post(Username, Host, Password, {false, false}, Ip) ->
|
||||||
{false, false}) ->
|
register_account(Username, Host, Password, Ip);
|
||||||
register_account(Username, Host, Password);
|
form_new_post(Username, Host, Password, {Id, Key}, Ip) ->
|
||||||
form_new_post(Username, Host, Password, {Id, Key}) ->
|
|
||||||
case ejabberd_captcha:check_captcha(Id, Key) of
|
case ejabberd_captcha:check_captcha(Id, Key) of
|
||||||
captcha_valid ->
|
captcha_valid ->
|
||||||
register_account(Username, Host, Password);
|
register_account(Username, Host, Password, Ip);
|
||||||
captcha_non_valid -> {error, captcha_non_valid};
|
captcha_non_valid -> {error, captcha_non_valid};
|
||||||
captcha_not_found -> {error, captcha_non_valid}
|
captcha_not_found -> {error, captcha_non_valid}
|
||||||
end.
|
end.
|
||||||
|
@ -502,11 +501,11 @@ form_del_get(Host, Lang) ->
|
||||||
{<<"Content-Type">>, <<"text/html">>}],
|
{<<"Content-Type">>, <<"text/html">>}],
|
||||||
ejabberd_web:make_xhtml(HeadEls, Els)}.
|
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}} |
|
%% {success, exists, {Username, Host, Password}} |
|
||||||
%% {error, not_allowed} |
|
%% {error, not_allowed} |
|
||||||
%% {error, invalid_jid}
|
%% {error, invalid_jid}
|
||||||
register_account(Username, Host, Password) ->
|
register_account(Username, Host, Password, Ip) ->
|
||||||
try mod_register_opt:access(Host) of
|
try mod_register_opt:access(Host) of
|
||||||
Access ->
|
Access ->
|
||||||
case jid:make(Username, Host) of
|
case jid:make(Username, Host) of
|
||||||
|
@ -514,16 +513,15 @@ register_account(Username, Host, Password) ->
|
||||||
JID ->
|
JID ->
|
||||||
case acl:match_rule(Host, Access, JID) of
|
case acl:match_rule(Host, Access, JID) of
|
||||||
deny -> {error, not_allowed};
|
deny -> {error, not_allowed};
|
||||||
allow -> register_account2(Username, Host, Password)
|
allow -> register_account2(Username, Host, Password, Ip)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
catch _:{module_not_loaded, mod_register, _Host} ->
|
catch _:{module_not_loaded, mod_register, _Host} ->
|
||||||
{error, host_unknown}
|
{error, host_unknown}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
register_account2(Username, Host, Password) ->
|
register_account2(Username, Host, Password, Ip) ->
|
||||||
case ejabberd_auth:try_register(Username, Host,
|
case mod_register:try_register(Username, Host, Password, Ip, ?MODULE)
|
||||||
Password)
|
|
||||||
of
|
of
|
||||||
ok ->
|
ok ->
|
||||||
{success, ok, {Username, Host, Password}};
|
{success, ok, {Username, Host, Password}};
|
||||||
|
@ -579,12 +577,8 @@ get_error_text({error, exists}) ->
|
||||||
?T("The account already exists");
|
?T("The account already exists");
|
||||||
get_error_text({error, password_incorrect}) ->
|
get_error_text({error, password_incorrect}) ->
|
||||||
?T("Incorrect password");
|
?T("Incorrect password");
|
||||||
get_error_text({error, invalid_jid}) ->
|
|
||||||
?T("The username is not valid");
|
|
||||||
get_error_text({error, host_unknown}) ->
|
get_error_text({error, host_unknown}) ->
|
||||||
?T("Host unknown");
|
?T("Host unknown");
|
||||||
get_error_text({error, not_allowed}) ->
|
|
||||||
?T("Not allowed");
|
|
||||||
get_error_text({error, account_doesnt_exist}) ->
|
get_error_text({error, account_doesnt_exist}) ->
|
||||||
?T("Account doesn't exist");
|
?T("Account doesn't exist");
|
||||||
get_error_text({error, account_exists}) ->
|
get_error_text({error, account_exists}) ->
|
||||||
|
@ -594,7 +588,9 @@ get_error_text({error, password_not_changed}) ->
|
||||||
get_error_text({error, passwords_not_identical}) ->
|
get_error_text({error, passwords_not_identical}) ->
|
||||||
?T("The passwords are different");
|
?T("The passwords are different");
|
||||||
get_error_text({error, wrong_parameters}) ->
|
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(_) ->
|
mod_options(_) ->
|
||||||
[].
|
[].
|
||||||
|
|
|
@ -80,9 +80,10 @@ get_roster(LUser, LServer) ->
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
GroupsDict = lists:foldl(fun({J, G}, Acc) ->
|
GroupsDict = lists:foldl(fun({J, G}, Acc) ->
|
||||||
dict:append(J, G, Acc)
|
Gs = maps:get(J, Acc, []),
|
||||||
|
maps:put(J, [G | Gs], Acc)
|
||||||
end,
|
end,
|
||||||
dict:new(), JIDGroups),
|
maps:new(), JIDGroups),
|
||||||
{ok, lists:flatmap(
|
{ok, lists:flatmap(
|
||||||
fun(I) ->
|
fun(I) ->
|
||||||
case raw_to_record(LServer, I) of
|
case raw_to_record(LServer, I) of
|
||||||
|
@ -90,10 +91,7 @@ get_roster(LUser, LServer) ->
|
||||||
error -> [];
|
error -> [];
|
||||||
R ->
|
R ->
|
||||||
SJID = jid:encode(R#roster.jid),
|
SJID = jid:encode(R#roster.jid),
|
||||||
Groups = case dict:find(SJID, GroupsDict) of
|
Groups = maps:get(SJID, GroupsDict, []),
|
||||||
{ok, Gs} -> Gs;
|
|
||||||
error -> []
|
|
||||||
end,
|
|
||||||
[R#roster{groups = Groups}]
|
[R#roster{groups = Groups}]
|
||||||
end
|
end
|
||||||
end, Items)};
|
end, Items)};
|
||||||
|
|
|
@ -870,12 +870,15 @@ c2s_self_presence(Acc) ->
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
-spec unset_presence(binary(), binary(), binary(), binary()) -> ok.
|
-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,
|
Resources = ejabberd_sm:get_user_resources(LUser,
|
||||||
LServer),
|
LServer),
|
||||||
?DEBUG("Unset_presence for ~p @ ~p / ~p -> ~p "
|
?DEBUG("Unset_presence for ~p @ ~p / ~p -> ~p "
|
||||||
"(~p resources)",
|
"(~p resources)",
|
||||||
[LUser, LServer, Resource, Status, length(Resources)]),
|
[LUser, LServer, LResource, Status, length(Resources)]),
|
||||||
case length(Resources) of
|
case length(Resources) of
|
||||||
0 ->
|
0 ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
|
|
|
@ -689,9 +689,9 @@ mod_doc() ->
|
||||||
?T("- Connection parameters: The module also accepts the "
|
?T("- Connection parameters: The module also accepts the "
|
||||||
"connection parameters, all of which default to the top-level "
|
"connection parameters, all of which default to the top-level "
|
||||||
"parameter of the same name, if unspecified. "
|
"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."), "",
|
"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 "
|
"[Configuration examples] section to get details about "
|
||||||
"retrieving the roster, "
|
"retrieving the roster, "
|
||||||
"and configuration examples including Flat DIT and Deep DIT.")],
|
"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 "
|
"name of roster entries (usually full names of people in "
|
||||||
"the roster). See also the parameters 'ldap_userdesc' and "
|
"the roster). See also the parameters 'ldap_userdesc' and "
|
||||||
"'ldap_useruid'. For more information check the LDAP "
|
"'ldap_useruid'. For more information check the LDAP "
|
||||||
"http://../database-ldap/#filters[Filters] section.")}},
|
"http://../ldap/#filters[Filters] section.")}},
|
||||||
{ldap_filter,
|
{ldap_filter,
|
||||||
#{desc =>
|
#{desc =>
|
||||||
?T("Additional filter which is AND-ed together "
|
?T("Additional filter which is AND-ed together "
|
||||||
"with \"User Filter\" and \"Group Filter\". "
|
"with \"User Filter\" and \"Group Filter\". "
|
||||||
"For more information check the LDAP "
|
"For more information check the LDAP "
|
||||||
"http://../database-ldap/#filters[Filters] section.")}},
|
"http://../ldap/#filters[Filters] section.")}},
|
||||||
%% Attributes:
|
%% Attributes:
|
||||||
{ldap_groupattr,
|
{ldap_groupattr,
|
||||||
#{desc =>
|
#{desc =>
|
||||||
|
@ -785,7 +785,7 @@ mod_doc() ->
|
||||||
#{desc =>
|
#{desc =>
|
||||||
?T("A regex for extracting user ID from the value of the "
|
?T("A regex for extracting user ID from the value of the "
|
||||||
"attribute named by 'ldap_memberattr'. Check the LDAP "
|
"attribute named by 'ldap_memberattr'. Check the LDAP "
|
||||||
"http://../database-ldap/#control-parameters"
|
"http://../ldap/#control-parameters"
|
||||||
"[Control Parameters] section.")}},
|
"[Control Parameters] section.")}},
|
||||||
{ldap_auth_check,
|
{ldap_auth_check,
|
||||||
#{value => "true | false",
|
#{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)};
|
{undefined, get_turn_ipv6_addr(Opts)};
|
||||||
get_listener_ips(#{ip := {_, _, _, _} = IP}) ->
|
get_listener_ips(#{ip := {_, _, _, _} = IP}) ->
|
||||||
{IP, undefined};
|
{IP, undefined};
|
||||||
get_listener_ips(#{ip := {_, _, _, _, _,_, _, _, _} = IP}) ->
|
get_listener_ips(#{ip := {_, _, _, _, _, _, _, _} = IP}) ->
|
||||||
{undefined, IP}.
|
{undefined, IP}.
|
||||||
|
|
||||||
-spec get_turn_ipv4_addr(map()) -> inet:ip4_address() | undefined.
|
-spec get_turn_ipv4_addr(map()) -> inet:ip4_address() | undefined.
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
create_node_permission/6, create_node/2, delete_node/1,
|
create_node_permission/6, create_node/2, delete_node/1,
|
||||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||||
publish_item/7, delete_item/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_entity_affiliations/2, get_node_affiliations/1,
|
||||||
get_affiliation/2, set_affiliation/3,
|
get_affiliation/2, set_affiliation/3,
|
||||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||||
|
@ -432,6 +432,22 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||||
del_items(Nidx, OldItems),
|
del_items(Nidx, OldItems),
|
||||||
{result, {NewItems, 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>
|
%% @doc <p>Triggers item deletion.</p>
|
||||||
%% <p>Default plugin: The user performing the deletion must be the node owner
|
%% <p>Default plugin: The user performing the deletion must be the node owner
|
||||||
%% or a publisher, or PublishModel being open.</p>
|
%% 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,
|
create_node_permission/6, create_node/2, delete_node/1, purge_node/2,
|
||||||
subscribe_node/8, unsubscribe_node/4,
|
subscribe_node/8, unsubscribe_node/4,
|
||||||
publish_item/7, delete_item/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_entity_affiliations/2, get_node_affiliations/1,
|
||||||
get_affiliation/2, set_affiliation/3,
|
get_affiliation/2, set_affiliation/3,
|
||||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||||
|
@ -285,6 +285,23 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||||
del_items(Nidx, OldItems),
|
del_items(Nidx, OldItems),
|
||||||
{result, {NewItems, 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) ->
|
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
|
||||||
SubKey = jid:tolower(Publisher),
|
SubKey = jid:tolower(Publisher),
|
||||||
GenKey = jid:remove_resource(SubKey),
|
GenKey = jid:remove_resource(SubKey),
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
create_node_permission/6, create_node/2, delete_node/1,
|
create_node_permission/6, create_node/2, delete_node/1,
|
||||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||||
publish_item/7, delete_item/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_entity_affiliations/2, get_node_affiliations/1,
|
||||||
get_affiliation/2, set_affiliation/3,
|
get_affiliation/2, set_affiliation/3,
|
||||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||||
|
@ -81,10 +81,12 @@ features() ->
|
||||||
[<<"create-nodes">>,
|
[<<"create-nodes">>,
|
||||||
<<"auto-create">>,
|
<<"auto-create">>,
|
||||||
<<"auto-subscribe">>,
|
<<"auto-subscribe">>,
|
||||||
|
<<"config-node">>,
|
||||||
<<"delete-nodes">>,
|
<<"delete-nodes">>,
|
||||||
<<"delete-items">>,
|
<<"delete-items">>,
|
||||||
<<"filtered-notifications">>,
|
<<"filtered-notifications">>,
|
||||||
<<"modify-affiliations">>,
|
<<"modify-affiliations">>,
|
||||||
|
<<"multi-items">>,
|
||||||
<<"outcast-affiliation">>,
|
<<"outcast-affiliation">>,
|
||||||
<<"persistent-items">>,
|
<<"persistent-items">>,
|
||||||
<<"publish">>,
|
<<"publish">>,
|
||||||
|
@ -142,6 +144,9 @@ remove_extra_items(Nidx, MaxItems) ->
|
||||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||||
node_flat: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) ->
|
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
|
||||||
node_flat: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,
|
create_node_permission/6, create_node/2, delete_node/1,
|
||||||
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
purge_node/2, subscribe_node/8, unsubscribe_node/4,
|
||||||
publish_item/7, delete_item/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_entity_affiliations/2, get_node_affiliations/1,
|
||||||
get_affiliation/2, set_affiliation/3,
|
get_affiliation/2, set_affiliation/3,
|
||||||
get_entity_subscriptions/2, get_node_subscriptions/1,
|
get_entity_subscriptions/2, get_node_subscriptions/1,
|
||||||
|
@ -99,6 +99,9 @@ remove_extra_items(Nidx, MaxItems) ->
|
||||||
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
remove_extra_items(Nidx, MaxItems, ItemIds) ->
|
||||||
node_flat_sql: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) ->
|
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
|
||||||
node_flat_sql: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
|
case luerl:eval(NewData, State1) of
|
||||||
{ok, _} = Res ->
|
{ok, _} = Res ->
|
||||||
Res;
|
Res;
|
||||||
{error, Why} = Err ->
|
{error, Why, _} = Err ->
|
||||||
?ERROR_MSG("Failed to eval ~ts: ~p", [Path, Why]),
|
?ERROR_MSG("Failed to eval ~ts: ~p", [Path, Why]),
|
||||||
Err
|
Err
|
||||||
end;
|
end;
|
||||||
|
|
15
src/rest.erl
15
src/rest.erl
|
@ -191,13 +191,26 @@ base_url(Server, Path) ->
|
||||||
_ -> Url
|
_ -> Url
|
||||||
end.
|
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;
|
Url;
|
||||||
url(Url, Params) ->
|
url(Url, Params) ->
|
||||||
L = [<<"&", (iolist_to_binary(Key))/binary, "=",
|
L = [<<"&", (iolist_to_binary(Key))/binary, "=",
|
||||||
(misc:url_encode(Value))/binary>>
|
(misc:url_encode(Value))/binary>>
|
||||||
|| {Key, Value} <- Params],
|
|| {Key, Value} <- Params],
|
||||||
<<$&, Encoded/binary>> = iolist_to_binary(L),
|
<<$&, Encoded0/binary>> = iolist_to_binary(L),
|
||||||
|
Encoded = uri_hack(Encoded0),
|
||||||
<<Url/binary, $?, Encoded/binary>>.
|
<<Url/binary, $?, Encoded/binary>>.
|
||||||
url(Server, Path, Params) ->
|
url(Server, Path, Params) ->
|
||||||
case binary:split(base_url(Server, Path), <<"?">>) of
|
case binary:split(base_url(Server, Path), <<"?">>) of
|
||||||
|
|
|
@ -224,13 +224,21 @@ get_items(Config, Version) ->
|
||||||
sub_els = [#roster_query{ver = Version}]}) of
|
sub_els = [#roster_query{ver = Version}]}) of
|
||||||
#iq{type = result,
|
#iq{type = result,
|
||||||
sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
|
sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
|
||||||
{NewVersion, Items};
|
{NewVersion, normalize_items(Items)};
|
||||||
#iq{type = result, sub_els = []} ->
|
#iq{type = result, sub_els = []} ->
|
||||||
{empty, []};
|
{empty, []};
|
||||||
#iq{type = error} = Err ->
|
#iq{type = error} = Err ->
|
||||||
xmpp:get_error(Err)
|
xmpp:get_error(Err)
|
||||||
end.
|
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) ->
|
get_item(Config, JID) ->
|
||||||
case get_items(Config) of
|
case get_items(Config) of
|
||||||
{_Ver, Items} when is_list(Items) ->
|
{_Ver, Items} when is_list(Items) ->
|
||||||
|
|
|
@ -15,69 +15,62 @@
|
||||||
|
|
||||||
INPUT=$1
|
INPUT=$1
|
||||||
|
|
||||||
if test -n ${BASH_VERSION:-''} ; then
|
for n in $(od -A n -t u2 -N 48 /dev/urandom); do RL="$RL$n "; done
|
||||||
get_random ()
|
get_random ()
|
||||||
{
|
{
|
||||||
R=$RANDOM
|
R=${RL%% *}
|
||||||
}
|
RL=${RL#* }
|
||||||
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
|
|
||||||
|
|
||||||
get_random
|
get_random
|
||||||
WAVE1_AMPLITUDE=$((2 + $R % 5))
|
WAVE1_AMPLITUDE=$((2 + R % 5))
|
||||||
get_random
|
get_random
|
||||||
WAVE1_LENGTH=$((50 + $R % 25))
|
WAVE1_LENGTH=$((50 + R % 25))
|
||||||
get_random
|
get_random
|
||||||
WAVE2_AMPLITUDE=$((2 + $R % 5))
|
WAVE2_AMPLITUDE=$((2 + R % 5))
|
||||||
get_random
|
get_random
|
||||||
WAVE2_LENGTH=$((50 + $R % 25))
|
WAVE2_LENGTH=$((50 + R % 25))
|
||||||
get_random
|
get_random
|
||||||
WAVE3_AMPLITUDE=$((2 + $R % 5))
|
WAVE3_AMPLITUDE=$((2 + R % 5))
|
||||||
get_random
|
get_random
|
||||||
WAVE3_LENGTH=$((50 + $R % 25))
|
WAVE3_LENGTH=$((50 + R % 25))
|
||||||
get_random
|
get_random
|
||||||
W1_LINE_START_Y=$((10 + $R % 40))
|
W1_LINE_START_Y=$((10 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
W1_LINE_STOP_Y=$((10 + $R % 40))
|
W1_LINE_STOP_Y=$((10 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
W2_LINE_START_Y=$((10 + $R % 40))
|
W2_LINE_START_Y=$((10 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
W2_LINE_STOP_Y=$((10 + $R % 40))
|
W2_LINE_STOP_Y=$((10 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
W3_LINE_START_Y=$((10 + $R % 40))
|
W3_LINE_START_Y=$((10 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
W3_LINE_STOP_Y=$((10 + $R % 40))
|
W3_LINE_STOP_Y=$((10 + R % 40))
|
||||||
|
|
||||||
get_random
|
get_random
|
||||||
B1_LINE_START_Y=$(($R % 40))
|
B1_LINE_START_Y=$((R % 40))
|
||||||
get_random
|
get_random
|
||||||
B1_LINE_STOP_Y=$(($R % 40))
|
B1_LINE_STOP_Y=$((R % 40))
|
||||||
get_random
|
get_random
|
||||||
B2_LINE_START_Y=$(($R % 40))
|
B2_LINE_START_Y=$((R % 40))
|
||||||
get_random
|
get_random
|
||||||
B2_LINE_STOP_Y=$(($R % 40))
|
B2_LINE_STOP_Y=$((R % 40))
|
||||||
#B3_LINE_START_Y=$(($R % 40))
|
#B3_LINE_START_Y=$((R % 40))
|
||||||
#B3_LINE_STOP_Y=$(($R % 40))
|
#B3_LINE_STOP_Y=$((R % 40))
|
||||||
|
|
||||||
get_random
|
get_random
|
||||||
B1_LINE_START_X=$(($R % 20))
|
B1_LINE_START_X=$((R % 20))
|
||||||
get_random
|
get_random
|
||||||
B1_LINE_STOP_X=$((100 + $R % 40))
|
B1_LINE_STOP_X=$((100 + R % 40))
|
||||||
get_random
|
get_random
|
||||||
B2_LINE_START_X=$(($R % 20))
|
B2_LINE_START_X=$((R % 20))
|
||||||
get_random
|
get_random
|
||||||
B2_LINE_STOP_X=$((100 + $R % 40))
|
B2_LINE_STOP_X=$((100 + R % 40))
|
||||||
#B3_LINE_START_X=$(($R % 20))
|
#B3_LINE_START_X=$((R % 20))
|
||||||
#B3_LINE_STOP_X=$((100 + $R % 40))
|
#B3_LINE_STOP_X=$((100 + R % 40))
|
||||||
|
|
||||||
get_random
|
get_random
|
||||||
ROLL_X=$(($R % 40))
|
ROLL_X=$((R % 40))
|
||||||
|
|
||||||
convert -size 180x60 xc:none -pointsize 40 \
|
convert -size 180x60 xc:none -pointsize 40 \
|
||||||
\( -clone 0 -fill white \
|
\( -clone 0 -fill white \
|
||||||
|
|
|
@ -51,9 +51,10 @@
|
||||||
{release, true}.
|
{release, true}.
|
||||||
{release_dir, "${SCRIPT_DIR%/*}"}.
|
{release_dir, "${SCRIPT_DIR%/*}"}.
|
||||||
{sysconfdir, "{{release_dir}}/etc"}.
|
{sysconfdir, "{{release_dir}}/etc"}.
|
||||||
|
{erts_dir, "{{release_dir}}/erts-${ERTS_VSN#erts-}"}.
|
||||||
{installuser, "@INSTALLUSER@"}.
|
{installuser, "@INSTALLUSER@"}.
|
||||||
{erl, "{{release_dir}}/{{erts_vsn}}/bin/erl"}.
|
{erl, "{{erts_dir}}/bin/erl"}.
|
||||||
{epmd, "{{release_dir}}/{{erts_vsn}}/bin/epmd"}.
|
{epmd, "{{erts_dir}}/bin/epmd"}.
|
||||||
{localstatedir, "{{release_dir}}/var"}.
|
{localstatedir, "{{release_dir}}/var"}.
|
||||||
{libdir, "{{release_dir}}/lib"}.
|
{libdir, "{{release_dir}}/lib"}.
|
||||||
{docdir, "{{release_dir}}/doc"}.
|
{docdir, "{{release_dir}}/doc"}.
|
||||||
|
|
Loading…
Reference in New Issue