Compare commits

...

114 Commits

Author SHA1 Message Date
Paweł Chmielowski a94209a0e0 Update stun and esip dependencies
CI / Tests (ubuntu-18.04, 19.3, 2) (push) Has been cancelled
CI / Tests (ubuntu-20.04, 24, 3) (push) Has been cancelled
CI / Binaries (ubuntu-20.04, 21.3, 3) (push) Has been cancelled
2021-12-09 12:13:48 +01:00
Badlop ed5ba1f645 Update CHANGELOG.md to 21.12 2021-12-09 11:07:43 +01:00
Badlop 1915f29d4b Update Chinese translation (thanks to Eric and 52871299hzy) 2021-12-09 10:55:26 +01:00
Badlop 333eaef6dc Update French translation (thanks to Éfrit and ButterflyOfFire) 2021-12-09 10:55:23 +01:00
Badlop d1bfd6c90d Annotate modules, options and command major changes in 21.12 2021-12-09 10:55:21 +01:00
Badlop ca143c1873 Update man page 2021-12-09 10:55:19 +01:00
Badlop e10f2a9e47 OTP 24 means whatever version provided by Actions... nowadays it's 24.1 2021-12-09 10:55:16 +01:00
Badlop 0f9a0156c6 Handle mix version when running docker-ejabberd/ecs/build.sh latest 2021-12-08 16:31:36 +01:00
Badlop 68ea5834c8 Relax strictness in mix dependency versions 2021-12-08 16:31:34 +01:00
Holger Weiss 4a52042435 Merge remote-tracking branch 'processone/pr/3724'
* processone/pr/3724:
  Remove CTLLOCKDIR (/var/lock/ejabberdctl) from Makefile.in
2021-12-07 10:19:59 +01:00
Florian Schmaus 6b1f78e87a Remove CTLLOCKDIR (/var/lock/ejabberdctl) from Makefile.in
Flock'ing /var/lock/ejabberdctl by ejabberdctl was removed with
f7d4aae64d ("Use UUID for ctl node name (#1021)"), however the
according recipies in the Makefile where never removed. This commit
does that.
2021-12-07 10:15:47 +01:00
Badlop 3f4423e996 Use P1's coveralls-erl fork to handle unicode in git commit author 2021-12-07 01:33:16 +01:00
Paweł Chmielowski 8d8a3177e1 Eliminate xref warning from last commit 2021-12-06 15:46:52 +01:00
Paweł Chmielowski 7897c3d0e1 Add workaround for bug in older erlang version in rest module 2021-12-06 15:08:10 +01:00
badlop b173ec0a78 Merge pull request #3652 from weiss/bump-max-items
PubSub: Bump default value for 'max_items' limit
2021-12-03 16:11:18 +01:00
Badlop 7fd0eefa30 Run make options 2021-12-03 16:09:58 +01:00
Badlop dab4c0cc10 New allow_modules option to restrict registration modules 2021-12-03 16:09:55 +01:00
Badlop 0372878ba5 Minor improvements in conversejs documentation 2021-12-03 16:09:10 +01:00
Paweł Chmielowski ad3c91b86e Update mix deps 2021-11-29 15:36:57 +01:00
Paweł Chmielowski 75b133d968 Update deps 2021-11-29 15:20:10 +01:00
Alexey Shchepin 89ad8a5502 Add mod_conversejs 2021-11-23 08:43:54 +03:00
Badlop 15d3ebb842 Fix Dialyzer warning, old passwd tuple don't match current tuple definition 2021-11-17 17:26:44 +01:00
Badlop 689749a563 Update Jose to 1.11.1 (the last in hex.pm correctly versioned) 2021-11-17 13:22:34 +01:00
Badlop a590e81922 Add DIAGNOSTIC to rebar3 coveralls, this fails since some days ago 2021-11-17 13:20:33 +01:00
Paweł Chmielowski 405a5172d5 Improve mod_multicast 2021-11-17 11:32:42 +01:00
Paweł Chmielowski 97b8373fd2 Better version of dialyzer fix 2021-11-16 10:59:53 +01:00
Paweł Chmielowski bdd4e52699 Make dialyzer happy 2021-11-16 10:57:15 +01:00
Paweł Chmielowski 03817de827 Make s2s connection table cleanup more robust
Using monitors instead of doint that from terminate() makes us immune to
s2s handler processes being forcefully killed.
2021-11-16 10:25:03 +01:00
Paweł Chmielowski 132ebb8f2d Fix exception in mucsub {un}subscription events multicast handler
While those event are wrapped in mucsub envelope they doesn't
contain regular messages that require updating 'to' attribute,
so don't process in that same way as events with wrapped
message in them.
2021-11-10 17:04:50 +01:00
Holger Weiss 2cdda4cf49 mod_caps: Don't forget caps on XEP-0198 resumption
Many thanks to Thilo Molitor for spotting the issue and testing the fix.
2021-11-06 23:48:49 +01:00
Badlop 4e014d23bd Improve documentation of some commands 2021-11-05 15:58:00 +01:00
Badlop b6a2eeebeb Mention "help" as an available ejabberdctl command 2021-11-05 15:57:57 +01:00
Badlop 684ef60ec3 Annotate support for XEP-0317: Hats, since commit 5d0e599f1 2021-11-05 15:57:53 +01:00
Holger Weiss 6e3df8e80b Update 'stun' dependency
The new 'stun' release should improve UDP performance quite a bit.
2021-11-02 12:34:19 +01:00
Holger Weiss c4f6c9dfe7 mod_muc_room.hrl: Work around old Dialyzer bug
On Erlang/OTP versions older than 21, Dialyzer stumbles over non-empty
map type specifications for record fields (OTP-15098).
2021-10-31 21:59:00 +01:00
Holger Weiss 13cbd7c35d mod_pubsub: Remove unused check_opt_range/3 clause 2021-10-31 21:38:49 +01:00
Holger Weiss 65a900668c node_pep: Fix remove_expired_items/2 argument name 2021-10-31 21:32:45 +01:00
Holger Weiss 2f1611f918 mod_pubsub: Fix get_max_items_node/1 specification
Make it explicit that the get_max_items_node/1 function returns
?MAXITEMS if the 'max_items_node' option isn't specified.  The function
didn't actually fall back to 'undefined' (but to the 'max_items_node'
default; i.e., ?MAXITEMS) anyway.  This change just clarifies the
behavior and adjusts the function specification accordingly.
2021-10-30 13:45:10 +02:00
Holger Weiss 29dcc9b94c PubSub: Add delete_expired_pubsub_items command
Support XEP-0060's pubsub#item_expire feature by adding a command for
deleting expired PubSub items.

Thanks to Ammonit Measurement GmbH for sponsoring this work.
2021-10-30 13:19:30 +02:00
Alexey Shchepin 5d48329a3f Update 'xmpp' dependency 2021-10-29 05:48:03 +03:00
Alexey Shchepin 5d0e599f17 Support MUC hats (XEP-0317, conversejs/prosody compatible) 2021-10-29 05:35:16 +03:00
Badlop 5462a26a0a Remove obsolete cookie preparation in spool dir, it's now stored in HOME 2021-10-21 16:12:41 +02:00
Badlop 5f3c8dcca4 Use the configured user in systemd's ejabberd.service 2021-10-21 16:12:38 +02:00
Badlop 54c23a65db Fix create_room_with_opts when using SQL storage (#3700) 2021-10-21 12:44:51 +02:00
Badlop 4d384b6bf5 If tests succeed, test also new SQL schema 2021-10-15 16:59:32 +02:00
Badlop c2db003431 When tests fail, show also error.log as it may have meaningful content 2021-10-15 16:59:28 +02:00
Badlop d3aa329769 Fix vcard_search definition in pgsql new schema (thanks to Stu Tomlinson)(#3695)
How to update an existing database:
ALTER TABLE vcard_search DROP CONSTRAINT vcard_search_pkey;
ALTER TABLE vcard_search ADD PRIMARY KEY (server_host, lusername);
2021-10-14 15:28:00 +02:00
Badlop db920b7d7b Only install some files when option enabled in configure (#3633) 2021-10-14 15:26:41 +02:00
Holger Weiss 6b0fa44386 Update 'xmpp' dependency
Fixes #3529.
2021-10-09 10:56:30 +02:00
Badlop 964cb3aaca Github Actions: use MD5 pass encryption to support PostgreSQL 14 (#3691) 2021-10-07 16:54:27 +02:00
Holger Weiss 1377dcf6d2 mod_mam: Declare XEP-0441 support 2021-10-06 01:13:11 +02:00
Badlop 595b016019 Use mod_register in web register form, so its restrictions are used (#3688) 2021-10-04 12:08:59 +02:00
Badlop 85408662ff Use mod_register to format some common error messages 2021-10-04 12:08:56 +02:00
Badlop d205e6ff1f Support old scram records before xmpp's 651050f9 and ejabberd's e5cad9be6 (#3680) 2021-09-27 17:12:17 +02:00
Badlop dd359a7328 Add indexes from 95fa43aa to the old-to-new MySQL schema update script 2021-09-27 16:33:23 +02:00
Badlop f74a715713 Add indexes from 95fa43aa to the old-to-new schema update function 2021-09-27 13:05:18 +02:00
Badlop af4b49f720 Update export/import of scram password to XEP-0227 1.1 (#3676) 2021-09-22 16:15:22 +02:00
Badlop ceeba3eea1 Don't crash when exporting a module that is not enabled 2021-09-22 15:30:11 +02:00
Badlop cfc393a12e When exporting mod_mam, MUC entries are assigned to the MUC service (#3680) 2021-09-22 11:12:28 +02:00
Alexey Shchepin c9c5839da4 Fix roster_tests:get_items 2021-09-21 13:30:52 +03:00
Alexey Shchepin bf068f5659 Small optimization in mod_roster_sql:get_roster 2021-09-21 12:10:00 +03:00
Alexey Shchepin 32cf44827d Use INSERT ... ON CONFLICT in SQL_UPSERT for PostgreSQL >= 9.5 2021-09-19 06:20:20 +03:00
Alexey Shchepin 0c403c0f0e Fix SQL_UPSERT in mod_push_sql:store_session 2021-09-19 06:20:20 +03:00
Badlop 2f5b15129a Fix previous commit: add forgotten endline blankspaces 2021-09-14 15:13:37 +02:00
Badlop f8167fc5d0 Update documentation to match the implemented options values (#3675) 2021-09-14 13:47:41 +02:00
Alexey Shchepin 5abc03ff8f Optimize MucSub processing 2021-09-13 08:20:47 +03:00
Holger Weiss 3114ce4ed2 ejabberd_admin: Fix ejabberd_piefxis commands
These days, the ejabberd_piefxis commands expect their arguments to be
handed over as binary strings.
2021-09-08 18:34:20 +02:00
Holger Weiss 868387a405 mod_http_upload_quota: Avoid 'max_days' race
Try to spread clean-up runs for multiple hosts, rather than scheduling
them in parallel.  This should reduce I/O spikes, and avoid race
conditions where multiple processes detect and then try to delete the
same old files (if multiple hosts have the same 'docroot').

Fixes #3497.
2021-09-05 20:00:05 +02:00
Holger Weiss caf07692db mod_register_web: Handle unknown host gracefully
Return a proper error message on registration attempts against unknown
hosts, rather than crashing.

Thanks to Ingo Jrgensmann for reporting the bug.
2021-09-05 13:24:51 +02:00
Badlop 91350ad472 Fix WebAdmin recent change 2021-08-27 13:39:06 +02:00
badlop d967103d89 Merge pull request #3668 from longlene/master
rebar version get minor fix
2021-08-27 13:23:56 +02:00
Badlop f77686481a Add internal links in WebAdmin Vhosts page 2021-08-27 13:23:24 +02:00
Badlop b0da69f050 Send ping from server, not bare user JID (#3658) 2021-08-27 13:23:15 +02:00
loong0 9643e18be7 minor fix 2021-08-26 20:33:37 +08:00
Holger Weiss ebf03a3745 node_flat: Avoid catch-all clauses for RSM
Apply the change made in the previous commit to Mnesia storage as well.
2021-08-23 22:04:03 +02:00
Holger Weiss c952cc420b node_flat_sql: Avoid catch-all clauses for RSM
Explicitly catch invalid <before/> and <after/> timestamps specified by
clients in RSM queries, but crash on other errors, rather than silently
ignoring those.
2021-08-23 21:28:15 +02:00
Badlop 4d0503b6b3 Fix syntax in mod_disco example configuration 2021-08-23 15:49:52 +02:00
Badlop 8b6c90c2d9 Tell dialyzer that gen_tags only cares about markdown output, not html 2021-08-23 15:39:01 +02:00
Badlop 655dcbcb74 New command to produce markdown with tags and their associated commands 2021-08-23 14:04:54 +02:00
Badlop ac4f240261 Produce module names with specific syntax, docs Makefile will convert to links 2021-08-23 14:04:52 +02:00
Badlop 506e2f3b97 Use specific syntax so modules and top-level will be links
If we use _`whatever`_ here in ejabberd man pages,
it is converted to *`whatever`* in markdown,
and docs.ejabberd.im/Makefile converts to the proper links
2021-08-23 14:04:49 +02:00
Badlop 30ae66e99e Improve formatting and add sections links 2021-08-23 14:04:46 +02:00
Badlop f5038b86f8 Copy log_rotate_count explanation from docs site 2021-08-23 14:04:44 +02:00
Badlop 9446b251fd Export function, so ACME API commands are listed in the documentation 2021-08-23 14:04:42 +02:00
Badlop 94fb0a65b0 Change set_master command tag from mnesia to cluster 2021-08-23 14:04:40 +02:00
Badlop 69d362595e Remove obsolete mod_register_web ideas and improve documentation 2021-08-23 14:04:37 +02:00
Badlop b7f7713fae Add example config to mod_http_api documentation 2021-08-23 14:04:35 +02:00
Badlop 8af66b0831 Update API Reference page menu name and order 2021-08-23 14:04:31 +02:00
Holger Weiss 7e9c9703dd Merge remote-tracking branch 'processone/pr/3666'
* processone/pr/3666:
  PubSub: Add delete_old_pubsub_items command
  PubSub: Optimize publishing on large nodes (SQL)
  PubSub: Support unlimited number of items
  PubSub: Support 'max_items=max' node configuration
2021-08-22 15:17:02 +02:00
Holger Weiss 8d5025076f PubSub: Add delete_old_pubsub_items command
Add a command for keeping only the specified number of items on each
node and removing all older items.  This might be especially useful if
nodes may be configured to have no 'max_items' limit.

Thanks to Ammonit Measurement GmbH for sponsoring this work.
2021-08-22 12:44:50 +02:00
Holger Weiss 29751a6174 PubSub: Optimize publishing on large nodes (SQL)
Avoid an unnecessary SQL query while publishing an item on a PubSub node
without 'max_items' limit.  The query in question can be expensive if
the node has a large number of items.

Thanks to Ammonit Measurement GmbH for sponsoring this work.
2021-08-21 20:02:58 +02:00
Holger Weiss 1b0e59bb13 PubSub: Support unlimited number of items
Allow for setting the mod_pubsub option 'max_items_node' to 'unlimited'.
If clients then request a 'max_items' limit of 'max', old items aren't
deleted when publishing new ones.

Thanks to Ammonit Measurement GmbH for sponsoring this work.
2021-08-21 12:29:37 +02:00
Holger Weiss 8f8de0403b PubSub: Support 'max_items=max' node configuration
Let clients request the maximum limit for the node configuration option
'max_items' by specifying the special value 'max' instead of an integer.
This was added to XEP-0060, revision 1.17.0 (and clarified in revision
1.20.0).

Thanks to Ammonit Measurement GmbH for sponsoring this work.
2021-08-20 20:30:11 +02:00
badlop 8afc320aba Merge pull request #3660 from ballerburg9005/patch-1
"sort -R" command in captcha-ng.sh is not POSIX - added "shuf" and "cat" as fallback
2021-08-16 13:16:43 +02:00
badlop 545a2f0097 Merge pull request #3656 from pitchum/master
Add missing pgsql migration for table push_session
2021-08-16 12:47:45 +02:00
ballerburg9005 14bf197be1 "sort -R" command not POSIX, added "shuf" and "cat" as fallback 2021-08-12 15:14:31 +02:00
Holger Weiss 3e942bf4ac mod_mam_sql: Remove duplicated functions 2021-08-07 12:57:57 +02:00
Badlop fdfd202a30 Determine the default handlerid at runtime
Apparently Elixir's default is not called 'default'
2021-08-05 13:53:12 +02:00
Badlop 5b0a28bbc9 Fix CHANGELOG: rebar2's elixir workaround was problematic and was reverted
Problems:
- That workaround required running "make" twice
- CEAN (used to build installers) doesn't support that make && make
- Support for Elixir in rebar3 doesn't yet exist
- Preferable path for Elixir usage: install Elixir and use --with-rebar=mix
2021-08-05 13:52:52 +02:00
Badlop aefc374317 Fix typo when creating index (thanks to Millesimus)
Reference: https://github.com/processone/ejabberd/commit/95fa43aa96514b7e8b77fa7c29d2c0b5b1c1331a#r54157330
2021-08-05 13:52:38 +02:00
Paweł Chmielowski 4c61ea9091 Update tests after last commit 2021-08-04 17:52:23 +02:00
Paweł Chmielowski d7e330c8ef Allow storing non-composing x:events in offline 2021-08-04 15:30:29 +02:00
Holger Weiss 99ffd9bb95 mod_pubsub: Fix check_opt_range/3 spec 2021-08-02 21:09:55 +02:00
pitchum 76c49f314f Add missing SQL migration for table push_session 2021-08-01 09:53:07 +02:00
Badlop ab5e726176 Use the most specific tag for ejabberd commands with several ones 2021-07-30 01:14:36 +02:00
Badlop b22779f018 Show tags and definer module in generated API document when it's a gen_mod 2021-07-30 01:14:34 +02:00
Badlop 41808a63a0 Show definer module in "ejabberdctl help" when it's a gen_mod 2021-07-30 01:14:31 +02:00
Badlop ccb4328d06 Store who defines a command, specially when defined by ejabberd modules 2021-07-30 01:14:27 +02:00
Holger Weiss 2050cdffb4 PubSub: Use configured 'max_items' by default
If clients don't ask for a specific 'max_items' limit, use the value of
mod_pubsub's 'max_items_node' option as default, rather than the
hard-coded ?MAXITEMS value.  This makes sure clients cannot circumvent a
smaller, configured limit.
2021-07-28 18:53:15 +02:00
Holger Weiss fce7fe8558 PubSub: Bump default value for 'max_items' limit
Bump the default value for mod_pubsub's 'max_items_node' option, which
hard-limits the 'max_items' value requested by clients.

These days, use cases such as microblogging or XEP-0402 may need a large
number of items per node.  Bumping the limit makes sure such
functionality is properly supported with the default configuration.
2021-07-28 18:29:19 +02:00
Holger Weiss 103e98b8da mod_push: Fix handling of MUC/Sub messages
Don't fail to include the sender/body of MUC/Sub messages if the
recipient is offline.

Closes #3651.
2021-07-28 18:22:39 +02:00
Badlop 8e553decb0 When vsn is an exact tag, append .0 to satisfy SemVer 2021-07-26 16:36:21 +02:00
Paweł Chmielowski e3875482ba Remove stringprep override from mix.exs 2021-07-26 16:06:43 +02:00
Paweł Chmielowski 6e4e5a0190 Add missing fields from config inside mod_muc_admin:change_options 2021-07-23 10:14:45 +02:00
95 changed files with 2604 additions and 1119 deletions
+58 -5
View File
@@ -25,12 +25,12 @@ jobs:
strategy:
fail-fast: false
matrix:
otp: ['19.3', '24.0']
otp: ['19.3', '24']
include:
- otp: '19.3'
rebar: 2
os: ubuntu-18.04
- otp: '24.0'
- otp: '24'
rebar: 3
os: ubuntu-20.04
runs-on: ${{ matrix.os }}
@@ -46,12 +46,14 @@ jobs:
- name: Get previous Erlang/OTP
uses: ErlGang/setup-erlang@master
if: matrix.otp != 24.0
if: matrix.otp != 24
with:
otp-version: ${{ matrix.otp }}
- name: Prepare databases
run: |
sudo sed -i 's|#password_encryption.*|password_encryption = md5|g' /etc/postgresql/14/main/postgresql.conf
sudo sed -i 's|scram-sha-256|md5|g' /etc/postgresql/14/main/pg_hba.conf
sudo systemctl start mysql.service
sudo systemctl start postgresql.service
mysql -u root -proot -e "CREATE USER 'ejabberd_test'@'localhost'
@@ -158,16 +160,20 @@ jobs:
if: failure()
run: find logs/ -name ejabberd.log -exec cat '{}' ';'
- name: View error.log
if: failure()
run: find logs/ -name error.log -exec cat '{}' ';'
- name: View exunit.log
if: failure()
run: find logs/ -name exunit.log -exec cat '{}' ';'
- name: Send to coveralls
if: matrix.otp == 24.0
if: matrix.otp == 24
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
rebar3 as test coveralls send
DIAGNOSTIC=1 rebar3 as test coveralls send
curl -v -k https://coveralls.io/webhook \
--header "Content-Type: application/json" \
--data '{"repo_name":"$GITHUB_REPOSITORY",
@@ -175,6 +181,53 @@ jobs:
"payload":{"build_num":$GITHUB_RUN_ID,
"status":"done"}}'
- name: Prepare new schema
run: |
[[ -d logs ]] && rm -rf logs
[[ -d _build/test/logs ]] && rm -rf _build/test/logs
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
- name: Check results
if: always()
run: |
[[ -d _build ]] && ln -s _build/test/logs/ logs \
|| ln dialyzer/error.log logs/dialyzer.log
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 full suite.log
run: cat logs/suite.log
- name: View suite.log failures
if: failure()
run: cat logs/suite.log | awk
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
- name: View full ejabberd.log
if: failure()
run: find logs/ -name ejabberd.log -exec cat '{}' ';'
- name: View error.log
if: failure()
run: find logs/ -name error.log -exec cat '{}' ';'
- name: View exunit.log
if: failure()
run: find logs/ -name exunit.log -exec cat '{}' ';'
binaries:
name: Binaries
needs: [tests]
+47 -1
View File
@@ -1,9 +1,55 @@
# Version 21.12
Commands
- `create_room_with_opts`: Fixed when using SQL storage
- `change_room_option`: Add missing fields from config inside `mod_muc_admin:change_options`
- piefxis: Fixed arguments of all commands
Modules
- mod_caps: Don't forget caps on XEP-0198 resumption
- mod_conversejs: New module to serve a simple page for Converse.js
- mod_http_upload_quota: Avoid `max_days` race
- mod_muc: Support MUC hats (XEP-0317, conversejs/prosody compatible)
- mod_muc: Optimize MucSub processing
- mod_muc: Fix exception in mucsub {un}subscription events multicast handler
- mod_multicast: Improve and optimize multicast routing code
- mod_offline: Allow storing non-composing x:events in offline
- mod_ping: Send ping from server, not bare user JID
- mod_push: Fix handling of MUC/Sub messages
- mod_register: New allow_modules option to restrict registration modules
- mod_register_web: Handle unknown host gracefully
- mod_register_web: Use mod_register configured restrictions
PubSub
- Add `delete_expired_pubsub_items` command
- Add `delete_old_pubsub_items` command
- Optimize publishing on large nodes (SQL)
- Support unlimited number of items
- Support `max_items=max` node configuration
- Bump default value for `max_items` limit from 10 to 1000
- Use configured `max_items` by default
- node_flat: Avoid catch-all clauses for RSM
- node_flat_sql: Avoid catch-all clauses for RSM
SQL
- Use `INSERT ... ON CONFLICT` in SQL_UPSERT for PostgreSQL >= 9.5
- mod_mam export: assign MUC entries to the MUC service
- MySQL: Fix typo when creating index
- PgSQL: Add SASL auth support, PostgreSQL 14
- PgSQL: Add missing SQL migration for table `push_session`
- PgSQL: Fix `vcard_search` definition in pgsql new schema
Other
- `captcha-ng.sh`: "sort -R" command not POSIX, added "shuf" and "cat" as fallback
- Make s2s connection table cleanup more robust
- Update export/import of scram password to XEP-0227 1.1
- Update Jose to 1.11.1 (the last in hex.pm correctly versioned)
# Version 21.07
Compilation
- Add rebar3 3.15.2 binary
- Add support for mix to: `./configure --enable-rebar=mix`
- Add workaround so rebar2 can use Elixir 1.12.0
- Improved `make rel` to work with rebar3 and mix
- Add `make dev` to build a development release with rebar3 or mix
- Hex: Add `sql/` and `vars.config` to Hex package files
+16 -20
View File
@@ -68,12 +68,6 @@ LUADIR = $(PRIVDIR)/lua
# /var/lib/ejabberd/
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
# /var/lock/ejabberdctl
CTLLOCKDIR = $(DESTDIR)@localstatedir@/lock/ejabberdctl
# /var/lib/ejabberd/.erlang.cookie
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
# /var/log/ejabberd/
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
@@ -99,12 +93,12 @@ ifneq ($(INSTALLGROUP),)
endif
ifeq "$(MIX)" "mix"
IS_REBAR:=6
REBAR_VER:=6
else
IS_REBAR:=$(shell expr `$(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}'`)
REBAR_VER:=$(shell $(REBAR) --version | awk -F '[ .]' '/rebar / {print $$2}')
endif
ifeq "$(IS_REBAR)" "6"
ifeq "$(REBAR_VER)" "6"
REBAR=$(MIX)
SKIPDEPS=
LISTDEPS=deps.tree
@@ -118,7 +112,7 @@ ifeq "$(IS_REBAR)" "6"
REBARREL=MIX_ENV=prod $(REBAR) release --overwrite
REBARDEV=MIX_ENV=dev $(REBAR) release --overwrite
else
ifeq "$(IS_REBAR)" "3"
ifeq "$(REBAR_VER)" "3"
SKIPDEPS=
LISTDEPS=tree
UPDATEDEPS=upgrade
@@ -249,7 +243,15 @@ $(call TO_DEST,priv/bin/captcha.sh): tools/captcha.sh $(call TO_DEST,priv/bin)
$(call TO_DEST,priv/lua/redis_sm.lua): priv/lua/redis_sm.lua $(call TO_DEST,priv/lua)
$(INSTALL) -m 644 $< $@
copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh priv/sql/lite.sql priv/sql/lite.new.sql priv/lua/redis_sm.lua)
ifeq (@sqlite@,true)
SQLITE_FILES = priv/sql/lite.sql priv/sql/lite.new.sql
endif
ifeq (@redis@,true)
REDIS_FILES = priv/lua/redis_sm.lua
endif
copy-files-sub2: $(call TO_DEST,$(DEPS_FILES) $(MAIN_FILES) priv/bin/captcha.sh $(SQLITE_FILES) $(REDIS_FILES))
.PHONY: $(call TO_DEST,$(DEPS_FILES) $(MAIN_DIRS) $(DEPS_DIRS))
@@ -298,7 +300,8 @@ install: copy-files
chmod 755 ejabberd.init
#
# Service script
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" ejabberd.service.template \
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*g" \
-e "s*@installuser@*$(INIT_USER)*g" ejabberd.service.template \
> ejabberd.service
chmod 644 ejabberd.service
#
@@ -306,12 +309,6 @@ install: copy-files
$(INSTALL) -d -m 750 $(O_USER) $(SPOOLDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(SPOOLDIR)
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
#
# ejabberdctl lock directory
$(INSTALL) -d -m 750 $(O_USER) $(CTLLOCKDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(CTLLOCKDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(CTLLOCKDIR)
#
# Log directory
$(INSTALL) -d -m 750 $(O_USER) $(LOGDIR)
@@ -361,7 +358,6 @@ uninstall-all: uninstall-binary
rm -rf $(ETCDIR)
rm -rf $(EJABBERDDIR)
rm -rf $(SPOOLDIR)
rm -rf $(CTLLOCKDIR)
rm -rf $(LOGDIR)
clean:
@@ -398,7 +394,7 @@ TAGS:
Makefile: Makefile.in
ifeq "$(IS_REBAR)" "3"
ifeq "$(REBAR_VER)" "3"
dialyzer:
$(REBAR) dialyzer
else
+2 -2
View File
@@ -4,8 +4,8 @@ After=network.target
[Service]
Type=notify
User=ejabberd
Group=ejabberd
User=@installuser@
Group=@installuser@
LimitNOFILE=65536
Restart=on-failure
RestartSec=5
+1
View File
@@ -59,6 +59,7 @@
policy = restricted :: open | restricted | admin | user,
%% access is: [accessRuleName] or [{Module, AccessOption, DefaultAccessRuleName}]
access = [] :: [{atom(),atom(),atom()}|atom()],
definer = unknown :: atom(),
result = {res, rescode} :: rterm() | '_' | '$2',
args_rename = [] :: [{atom(),atom()}],
args_desc = none :: none | [string()] | '_',
+13 -4
View File
@@ -65,6 +65,7 @@
captcha_whitelist = (?SETS):empty() :: gb_sets:set(),
mam = false :: boolean(),
pubsub = <<"">> :: binary(),
enable_hats = false :: boolean(),
lang = ejabberd_option:language() :: binary()
}).
@@ -87,6 +88,16 @@
nick = <<>> :: binary(),
nodes = [] :: [binary()]}).
-record(muc_subscribers,
{subscribers = #{} :: subscribers(),
subscriber_nicks = #{} :: subscriber_nicks(),
subscriber_nodes = #{} :: subscriber_nodes()
}).
-type subscribers() :: #{ljid() => #subscriber{}}.
-type subscriber_nicks() :: #{binary() => [ljid()]}.
-type subscriber_nodes() :: #{binary() => subscribers()}.
-record(activity,
{
message_time = 0 :: integer(),
@@ -106,8 +117,7 @@
jid = #jid{} :: jid(),
config = #config{} :: config(),
users = #{} :: users(),
subscribers = #{} :: subscribers(),
subscriber_nicks = #{} :: subscriber_nicks(),
muc_subscribers = #muc_subscribers{} :: #muc_subscribers{},
last_voice_request_time = treap:empty() :: treap:treap(),
robots = #{} :: robots(),
nicks = #{} :: nicks(),
@@ -115,6 +125,7 @@
history = #lqueue{} :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: ejabberd_shaper:shaper(),
@@ -126,5 +137,3 @@
-type robots() :: #{jid() => {binary(), stanza()}}.
-type nicks() :: #{binary() => [ljid()]}.
-type affiliations() :: #{ljid() => affiliation() | {affiliation(), binary()}}.
-type subscribers() :: #{ljid() => #subscriber{}}.
-type subscriber_nicks() :: #{binary() => [ljid()]}.
+1 -1
View File
@@ -23,7 +23,7 @@
-define(ERR_EXTENDED(E, C), mod_pubsub:extended_error(E, C)).
%% The actual limit can be configured with mod_pubsub's option max_items_node
-define(MAXITEMS, 10).
-define(MAXITEMS, 1000).
%% this is currently a hard limit.
%% Would be nice to have it configurable.
+266 -75
View File
@@ -2,12 +2,12 @@
.\" Title: ejabberd.yml
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 07/21/2021
.\" Date: 12/08/2021
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "07/21/2021" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "12/08/2021" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -82,7 +82,7 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
.sp
Some options can be specified for particular virtual host(s) only using \fIhost_config\fR or \fIappend_host_config\fR options\&. Such options are called \fIlocal\fR\&. Examples are \fImodules\fR, \fIauth_method\fR and \fIdefault_db\fR\&. The options that cannot be defined per virtual host are called \fIglobal\fR\&. Examples are \fIloglevel\fR, \fIcertfiles\fR and \fIlisten\fR\&. It is a configuration mistake to put \fIglobal\fR options under \fIhost_config\fR or \fIappend_host_config\fR section \- ejabberd will refuse to load such configuration\&.
.sp
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/21\&.07/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/21\&.12/ejabberd\&.yml\&.example\&. Once you get ejabberd running you can start changing configuration options to meet your requirements\&.
.sp
Note that this document is intended to provide comprehensive description of all configuration options that can be consulted to understand the meaning of a particular option, its format and possible values\&. It will be quite hard to understand how to configure ejabberd by reading this document only \- for this purpose the reader is recommended to read online Configuration Guide available at https://docs\&.ejabberd\&.im/admin/configuration\&.
.SH "TOP LEVEL OPTIONS"
@@ -316,14 +316,46 @@ means that the same username can be taken multiple times in anonymous login mode
.PP
\fBanonymous_protocol\fR: \fIlogin_anon | sasl_anon | both\fR
.RS 4
Define what anonymous protocol will be used:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIlogin_anon\fR
means that the anonymous login method will be used\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIsasl_anon\fR
means that the SASL Anonymous method will be used\&.
\fIboth\fR
means that SASL Anonymous and login anonymous are both enabled\&. The default value is
\fIsasl_anon\fR\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIboth\fR
means that SASL Anonymous and login anonymous are both enabled\&.
.RE
.RE
.sp
The default value is \fIsasl_anon\fR\&.
.PP
\fBapi_permissions\fR: \fI[Permission, \&.\&.\&.]\fR
.RS 4
@@ -375,7 +407,7 @@ A list of authentication methods to use\&. If several methods are defined, authe
This is used by the contributed module
\fIejabberd_auth_http\fR
that can be installed from the
\fIejabberd\-contrib\fR
ejabberd\-contrib
Git repository\&. Please refer to that module\(cqs README file for details\&.
.RE
.sp
@@ -383,14 +415,36 @@ Git repository\&. Please refer to that module\(cqs README file for details\&.
.PP
\fBauth_password_format\fR: \fIplain | scram\fR
.RS 4
The option defines in what format the users passwords are stored\&.
The option defines in what format the users passwords are stored:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIplain\fR: The password is stored as plain text in the database\&. This is risky because the passwords can be read if your database gets compromised\&. This is the default value\&. This format allows clients to authenticate using: the old Jabber Non\-SASL (XEP\-0078), SASL PLAIN, SASL DIGEST\-MD5, and SASL SCRAM\-SHA\-1\&.
\fIscram\fR: The password is not stored, only some information that allows to verify the hash provided by the client\&. It is impossible to obtain the original plain password from the stored information; for this reason, when this value is configured it cannot be changed to plain anymore\&. This format allows clients to authenticate using: SASL PLAIN and SASL SCRAM\-SHA\-1\&.
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
\fIscram\fR: The password is not stored, only some information that allows to verify the hash provided by the client\&. It is impossible to obtain the original plain password from the stored information; for this reason, when this value is configured it cannot be changed to plain anymore\&. This format allows clients to authenticate using: SASL PLAIN and SASL SCRAM\-SHA\-1\&. The default value is
\fIplain\fR\&.
.RE
.RE
.PP
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
.RS 4
Hash algorith that should be used to store password in SCRAM format\&. You shouldn\(cqt change this if you already have passwords generated with a different algorithm \- users that have such passwords will not be able to authenticate\&.
Hash algorith that should be used to store password in SCRAM format\&. You shouldn\(cqt change this if you already have passwords generated with a different algorithm \- users that have such passwords will not be able to authenticate\&. The default value is
\fIsha\fR\&.
.RE
.PP
\fBauth_use_cache\fR: \fItrue | false\fR
@@ -476,7 +530,7 @@ For server conections, this \fIca_file\fR option is overriden by the s2s_cafile
\fBcache_life_time\fR: \fItimeout()\fR
.RS 4
The time of a cached item to keep in cache\&. Once it\(cqs expired, the corresponding item is erased from cache\&. The default value is
\fIone hour\fR\&. Several modules have a similar option; and some core ejabberd parts support similar options too, see
\fI1 hour\fR\&. Several modules have a similar option; and some core ejabberd parts support similar options too, see
\fIauth_cache_life_time\fR,
\fIoauth_cache_life_time\fR,
\fIrouter_cache_life_time\fR, and
@@ -507,7 +561,9 @@ A maximum number of items (not memory!) in cache\&. The rule of thumb, for all t
.PP
\fBcaptcha_cmd\fR: \fIPath\fR
.RS 4
Full path to a script that generates CAPTCHA images\&. There is no default value: when this option is not set, CAPTCHA functionality is completely disabled\&.
Full path to a script that generates
CAPTCHA
images\&. There is no default value: when this option is not set, CAPTCHA functionality is completely disabled\&.
.RE
.PP
\fBcaptcha_host\fR: \fIString\fR
@@ -519,13 +575,17 @@ instead\&.
.PP
\fBcaptcha_limit\fR: \fIpos_integer() | infinity\fR
.RS 4
Maximum number of CAPTCHA generated images per minute for any given JID\&. The option is intended to protect the server from CAPTCHA DoS\&. The default value is
Maximum number of
CAPTCHA
generated images per minute for any given JID\&. The option is intended to protect the server from CAPTCHA DoS\&. The default value is
\fIinfinity\fR\&.
.RE
.PP
\fBcaptcha_url\fR: \fIURL\fR
.RS 4
An URL where CAPTCHA requests should be sent\&. NOTE: you need to configure
An URL where
CAPTCHA
requests should be sent\&. NOTE: you need to configure
\fIrequest_handlers\fR
for
\fIejabberd_http\fR
@@ -815,7 +875,8 @@ Path to the file that contains the JWK Key\&. The default value is
.RS 4
The option defines the default language of server strings that can be seen by XMPP clients\&. If an XMPP client does not possess
\fIxml:lang\fR
attribute, the specified language is used\&.
attribute, the specified language is used\&. The default value is
\fI"en"\fR\&.
.RE
.PP
\fBldap_backups\fR: \fI[Host, \&.\&.\&.]\fR
@@ -948,7 +1009,11 @@ section for details\&.
\fBlog_rotate_count\fR: \fINumber\fR
.RS 4
The number of rotated log files to keep\&. The default value is
\fI1\fR\&.
\fI1\fR, which means that only keeps
ejabberd\&.log\&.0,
error\&.log\&.0
and
crash\&.log\&.0\&.
.RE
.PP
\fBlog_rotate_size\fR: \fIpos_integer() | infinity\fR
@@ -989,8 +1054,7 @@ seconds\&.
.RS 4
This option can be used to tune tick time parameter of
\fInet_kernel\fR\&. It tells Erlang VM how often nodes should check if intra\-node communication was not interrupted\&. This option must have identical value on all nodes, or it will lead to subtle bugs\&. Usually leaving default value of this is option is best, tweak it only if you know what you are doing\&. The default value is
\fI1\fR
minute\&.
\fI1 minute\fR\&.
.RE
.PP
\fBnew_sql_schema\fR: \fItrue | false\fR
@@ -998,7 +1062,7 @@ minute\&.
Whether to use
\fInew\fR
SQL schema\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/21\&.07/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
https://github\&.com/processone/ejabberd/tree/21\&.12/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
\fInew\fR
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
\fInew\fR
@@ -1392,8 +1456,7 @@ if the latter is not set\&.
\fBs2s_timeout\fR: \fItimeout()\fR
.RS 4
A time to wait before closing an idle s2s connection\&. The default value is
\fI10\fR
minutes\&.
\fI10 minutes\fR\&.
.RE
.PP
\fBs2s_tls_compression\fR: \fItrue | false\fR
@@ -1795,24 +1858,7 @@ Details for some commands:
\fIsrg\-create\fR: If you want to put a group Name with blankspaces, use the characters "\*(Aq and \*(Aq" to define when the Name starts and ends\&. See an example below\&.
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBmodule_resource\fR: \fIResource\fR
.RS 4
Indicate the resource that the XMPP stanzas must use in the FROM or TO JIDs\&. This is only useful in the
\fIget_vcard*\fR
and
\fIset_vcard*\fR
commands\&. The default value is
\fImod_admin_extra\fR\&.
.RE
.RE
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@@ -1835,8 +1881,7 @@ access_rules:
vcard_set:
\- allow: adminextraresource
modules:
mod_admin_extra:
module_resource: "modadminextraf8x,31ad"
mod_admin_extra: {}
mod_vcard:
access_set: vcard_set
.fi
@@ -1884,7 +1929,7 @@ ejabberdctl srg\-create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g
.RE
.SS "mod_admin_update_sql"
.sp
This module can be used to update existing SQL database from the default to the new schema\&. Check the section Default and New Schemas for details\&. Please note that only PostgreSQL is supported\&. When the module is loaded use \fIupdate_sql\fR ejabberdctl command\&.
This module can be used to update existing SQL database from the default to the new schema\&. Check the section Default and New Schemas for details\&. Please note that only PostgreSQL is supported\&. When the module is loaded use \fIupdate_sql\fR API\&.
.sp
The module has no options\&.
.SS "mod_announce"
@@ -2321,6 +2366,78 @@ While a client is inactive, queue presence stanzas that indicate (un)availabilit
The module provides server configuration functionality via XEP\-0050: Ad\-Hoc Commands\&. This module requires \fImod_adhoc\fR to be loaded\&.
.sp
The module has no options\&.
.SS "mod_conversejs"
.sp
This module serves a simple page for the Converse XMPP web browser client\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR → \fIejabberd_http\fR → request_handlers\&.
.sp
You must also setup either the option \fIwebsocket_url\fR or \fIbosh_service_url\fR\&.
.sp
By default, the options \fIconversejs_css\fR and \fIconversejs_script\fR point to the public Converse\&.js client\&. Alternatively, you can host the client locally using \fImod_http_fileserver\fR\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBbosh_service_url\fR: \fIBoshURL\fR
.RS 4
BOSH service URL to which Converse\&.js can connect to\&.
.RE
.PP
\fBconversejs_css\fR: \fIURL\fR
.RS 4
Converse\&.js CSS URL\&.
.RE
.PP
\fBconversejs_script\fR: \fIURL\fR
.RS 4
Converse\&.js main script URL\&.
.RE
.PP
\fBdefault_domain\fR: \fIDomain\fR
.RS 4
Specify a domain to act as the default for user JIDs\&. The default value is the first domain defined in the ejabberd configuration file\&.
.RE
.PP
\fBwebsocket_url\fR: \fIWebsocketURL\fR
.RS 4
A websocket URL to which Converse\&.js can connect to\&.
.RE
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/websocket: ejabberd_http_ws
/conversejs: mod_conversejs
modules:
mod_conversejs:
websocket_url: "ws://example\&.org:5280/websocket"
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_delegation"
.sp
This module is an implementation of XEP\-0355: Namespace Delegation\&. Only admin mode has been implemented by now\&. Namespace delegation allows external services to handle IQ using specific namespace\&. This may be applied for external PEP service\&.
@@ -2479,11 +2596,11 @@ server_info:
\-
modules: all
name: abuse\-addresses
urls: [mailto:abuse@shakespeare\&.lit]
urls: ["mailto:abuse@shakespeare\&.lit"]
\-
modules: [mod_muc]
name: "Web chatroom logs"
urls: [http://www\&.example\&.org/muc\-logs]
urls: ["http://www\&.example\&.org/muc\-logs"]
\-
modules: [mod_disco]
name: feedback\-addresses
@@ -2563,13 +2680,40 @@ The number of C2S authentication failures to trigger the IP ban\&. The default v
.sp
This module provides a ReST API to call ejabberd commands using JSON data\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also add it to \fIrequest_handlers\fR of some listener\&.
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR → \fIejabberd_http\fR → request_handlers\&.
.sp
To use a specific API version N, when defining the URL path in the request_handlers, add a \fIvN\fR\&. For example: \fI/api/v2: mod_http_api\fR
.sp
To run a command, send a POST request to the corresponding URL: \fIhttp://localhost:5280/api/<command_name>\fR
.sp
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/api: mod_http_api
modules:
mod_http_api: {}
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_http_fileserver"
.sp
This simple module serves files from the local disk over HTTP\&.
@@ -2697,7 +2841,7 @@ modules:
.sp
This module allows for requesting permissions to upload a file via HTTP as described in XEP\-0363: HTTP File Upload\&. If the request is accepted, the client receives a URL for uploading the file and another URL from which that file can later be downloaded\&.
.sp
In order to use this module, it must be configured as a \fIrequest_handler\fR for \fIejabberd_http\fR listener\&.
In order to use this module, it must be enabled in \fIlisten\fR \fIejabberd_http\fR → request_handlers\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@@ -2746,7 +2890,9 @@ This option defines the permission bits of uploaded files\&. The bits are specif
.PP
\fBget_url\fR: \fIURL\fR
.RS 4
This option specifies the initial part of the GET URLs used for downloading the files\&. By default, it is set to the same value as
This option specifies the initial part of the GET URLs used for downloading the files\&. The default value is
\fIundefined\fR\&. When this option is
\fIundefined\fR, this option is set to the same value as
\fIput_url\fR\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: if GET requests are handled by
\fImod_http_upload\fR, the
\fIget_url\fR
@@ -2795,7 +2941,7 @@ A name of the service in the Service Discovery\&. This will only be displayed by
.PP
\fBput_url\fR: \fIURL\fR
.RS 4
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is "https://@HOST@:5443"\&.
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword @HOST@ is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is "https://@HOST@:5443/upload"\&.
.RE
.PP
\fBrm_on_unregister\fR: \fItrue | false\fR
@@ -3530,21 +3676,24 @@ This option specifies who is allowed to administrate the Multi\-User Chat servic
.PP
\fBaccess_create\fR: \fIAccessName\fR
.RS 4
To configure who is allowed to create new rooms at the Multi\-User Chat service, this option can be used\&. By default any account in the local ejabberd server is allowed to create rooms\&.
To configure who is allowed to create new rooms at the Multi\-User Chat service, this option can be used\&. The default value is
\fIall\fR, which means everyone is allowed to create rooms\&.
.RE
.PP
\fBaccess_mam\fR: \fIAccessName\fR
.RS 4
To configure who is allowed to modify the
\fImam\fR
room option\&. By default any account in the local ejabberd server is allowed to modify that option\&.
room option\&. The default value is
\fIall\fR, which means everyone is allowed to modify that option\&.
.RE
.PP
\fBaccess_persistent\fR: \fIAccessName\fR
.RS 4
To configure who is allowed to modify the
\fIpersistent\fR
room option\&. By default any account in the local ejabberd server is allowed to modify that option\&.
room option\&. The default value is
\fIall\fR, which means everyone is allowed to modify that option\&.
.RE
.PP
\fBaccess_register\fR: \fIAccessName\fR
@@ -3825,7 +3974,8 @@ This option defines the number of service admins or room owners allowed to enter
.PP
\fBmax_users_presence\fR: \fINumber\fR
.RS 4
This option defines after how many users in the room, it is considered overcrowded\&. When a MUC room is considered overcrowed, presence broadcasts are limited to reduce load, traffic and excessive presence "storm" received by participants\&.
This option defines after how many users in the room, it is considered overcrowded\&. When a MUC room is considered overcrowed, presence broadcasts are limited to reduce load, traffic and excessive presence "storm" received by participants\&. The default value is
\fI1000\fR\&.
.RE
.PP
\fBmin_message_interval\fR: \fINumber\fR
@@ -4498,8 +4648,7 @@ This module implements support for XEP\-0199: XMPP Ping and periodic keepalives\
\fBping_ack_timeout\fR: \fItimeout()\fR
.RS 4
How long to wait before deeming that a client has not answered a given server ping request\&. The default value is
\fI32\fR
seconds\&.
\fIundefined\fR\&.
.RE
.PP
\fBping_interval\fR: \fItimeout()\fR
@@ -4531,7 +4680,7 @@ means destroying the underlying connection,
\fInone\fR
means to do nothing\&. NOTE: when
\fImod_stream_mgmt\fR
module is loaded and stream management is enabled by a client, killing the client connection doesn\(cqt mean killing the client session \- the session will be kept alive in order to give the client a chance to resume it\&. The default value is
is loaded and stream management is enabled by a client, killing the client connection doesn\(cqt mean killing the client session \- the session will be kept alive in order to give the client a chance to resume it\&. The default value is
\fInone\fR\&.
.RE
.RE
@@ -5136,10 +5285,16 @@ or
\fIfalse\fR\&. If not defined, pubsub does not cache last items\&. On systems with not so many nodes, caching last items speeds up pubsub and allows to raise user connection rate\&. The cost is memory usage, as every item is stored in memory\&.
.RE
.PP
\fBmax_items_node\fR: \fIMaxItems\fR
\fBmax_item_expire_node\fR: \fItimeout() | infinity\fR
.RS 4
Specify the maximum item epiry time\&. Default value is:
\fIinfinity\fR\&.
.RE
.PP
\fBmax_items_node\fR: \fInon_neg_integer() | infinity\fR
.RS 4
Define the maximum number of items that can be stored in a node\&. Default value is:
\fI10\fR\&.
\fI1000\fR\&.
.RE
.PP
\fBmax_nodes_discoitems\fR: \fIpos_integer() | infinity\fR
@@ -5437,9 +5592,9 @@ The module depends on \fImod_push\fR\&.
.PP
\fBresume_timeout\fR: \fItimeout()\fR
.RS 4
This option specifies the period of time until the session of a disconnected push client times out\&. This timeout is only in effect as long as no push notification is issued\&. Once that happened, the resumption timeout configured for the
This option specifies the period of time until the session of a disconnected push client times out\&. This timeout is only in effect as long as no push notification is issued\&. Once that happened, the resumption timeout configured for
\fImod_stream_mgmt\fR
module is restored\&. The default value is
is restored\&. The default value is
\fI72\fR
hours\&.
.RE
@@ -5499,7 +5654,7 @@ Change the password from an existing account on the server\&.
Delete an existing account on the server\&.
.RE
.sp
This module reads also another option defined globally for the server: \fIregistration_timeout\fR\&. Please check that option documentation in the section with top\-level options\&.
This module reads also the top\-level \fIregistration_timeout\fR option defined globally for the server, so please check that option documentation too\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@@ -5528,11 +5683,18 @@ doesn\(cqt allow to register new accounts from s2s or existing c2s sessions\&. Y
Specify rules to restrict access for user unregistration\&. By default any user is able to unregister their account\&.
.RE
.PP
\fBallow_modules\fR: \fIall | [Module, \&.\&.\&.]\fR
.RS 4
List of modules that can register accounts, or
\fIall\fR\&. The default value is
\fIall\fR, which is equivalent to something like
\fI[mod_register, mod_register_web]\fR\&.
.RE
.PP
\fBcaptcha_protected\fR: \fItrue | false\fR
.RS 4
Protect registrations with CAPTCHA (see section
CAPTCHA
of the Configuration Guide)\&. The default is
Protect registrations with
CAPTCHA\&. The default is
\fIfalse\fR\&.
.RE
.PP
@@ -5551,7 +5713,8 @@ This option sets the minimum
Shannon entropy
for passwords\&. The value
\fIEntropy\fR
is a number of bits of entropy\&. The recommended minimum is 32 bits\&. The default is 0, i\&.e\&. no checks are performed\&.
is a number of bits of entropy\&. The recommended minimum is 32 bits\&. The default is
\fI0\fR, i\&.e\&. no checks are performed\&.
.RE
.PP
\fBredirect_url\fR: \fIURL\fR
@@ -5610,13 +5773,40 @@ Change the password from an existing account on the server\&.
Unregister an existing account on the server\&.
.RE
.sp
This module supports CAPTCHA image to register a new account\&. To enable this feature, configure the options \fIcaptcha_cmd\fR and \fIcaptcha_url\fR, which are documented in the section with top\-level options\&.
This module supports CAPTCHA to register a new account\&. To enable this feature, configure the top\-level \fIcaptcha_cmd\fR and top\-level \fIcaptcha_url\fR options\&.
.sp
As an example usage, the users of the host \fIexample\&.org\fR can visit the page: \fIhttps://example\&.org:5281/register/\fR It is important to include the last / character in the URL, otherwise the subpages URL will be incorrect\&.
As an example usage, the users of the host \fIlocalhost\fR can visit the page: \fIhttps://localhost:5280/register/\fR It is important to include the last / character in the URL, otherwise the subpages URL will be incorrect\&.
.sp
The module depends on \fImod_register\fR where all the configuration is performed\&.
This module is enabled in \fIlisten\fR → \fIejabberd_http\fR → request_handlers, no need to enable in \fImodules\fR\&. The module depends on \fImod_register\fR where all the configuration is performed\&.
.sp
The module has no options\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 5280
module: ejabberd_http
request_handlers:
/register: mod_register_web
modules:
mod_register: {}
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_roster"
.sp
This module implements roster management as defined in RFC6121 Section 2\&. The module also adds support for XEP\-0237: Roster Versioning\&.
@@ -5910,8 +6100,9 @@ option, but applied to this module only\&.
.PP
\fBdb_type\fR: \fImnesia | sql\fR
.RS 4
Define the type of storage where the module will create the tables and store user information\&. The default is the storage defined by the global option
\fIdefault_db\fR, or
Define the type of storage where the module will create the tables and store user information\&. The default is the storage defined by the top\-level
\fIdefault_db\fR
option, or
\fImnesia\fR
if omitted\&. If
\fIsql\fR
@@ -6521,7 +6712,8 @@ minute\&.
.RS 4
Same as top\-level
\fIcache_life_time\fR
option, but applied to this module only\&.
option, but applied to this module only\&. The default value is
\fI48 hours\fR\&.
.RE
.PP
\fBcache_size\fR: \fIpos_integer() | infinity\fR
@@ -6594,8 +6786,7 @@ This option defines which access rule will be used to control who is allowed to
\fBcredentials_lifetime\fR: \fItimeout()\fR
.RS 4
The lifetime of temporary credentials offered to clients\&. If ejabberd\(cqs built\-in TURN service is used, TURN relays allocated using temporary credentials will be terminated shortly after the credentials expired\&. The default value is
\fI12\fR
hours\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
\fI12 hours\fR\&. Note that restarting the ejabberd node invalidates any temporary credentials offered before the restart unless a
\fIsecret\fR
is specified (see below)\&.
.RE
@@ -7121,7 +7312,7 @@ The module depends on \fImod_vcard\fR\&.
.ps -1
.br
.sp
Nowadays XEP\-0153 is used mostly as "read\-only", i\&.e\&. modern clients don\(cqt publish their avatars inside vCards\&. Thus in the majority of cases the module is only used along with \fImod_avatar\fR module for providing backward compatibility\&.
Nowadays XEP\-0153 is used mostly as "read\-only", i\&.e\&. modern clients don\(cqt publish their avatars inside vCards\&. Thus in the majority of cases the module is only used along with \fImod_avatar\fR for providing backward compatibility\&.
.sp .5v
.RE
.sp
@@ -7189,13 +7380,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 21\&.04\&.131\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 21\&.12\&. Configuration options of other ejabberd versions may differ significantly\&.
.SH "REPORTING BUGS"
.sp
Report bugs to https://github\&.com/processone/ejabberd/issues
.SH "SEE ALSO"
.sp
Default configuration file: https://github\&.com/processone/ejabberd/blob/21\&.07/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/21\&.12/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp
+8 -3
View File
@@ -24,6 +24,10 @@ defmodule Ejabberd.MixProject do
case config(:vsn) do
:false -> "0.0.0" # ./configure wasn't run: vars.config not created
'0.0' -> "0.0.0" # the full git repository wasn't downloaded
'latest.0' -> "0.0.0" # running 'docker-ejabberd/ecs/build.sh latest'
[_, _, ?., _, _] = x ->
head = String.replace(:erlang.list_to_binary(x), ~r/0+([0-9])/, "\\1")
<<head::binary, ".0">>
vsn -> String.replace(:erlang.list_to_binary(vsn), ~r/0+([0-9])/, "\\1")
end
end
@@ -84,6 +88,7 @@ defmodule Ejabberd.MixProject do
if_version_below('23', [{:d, :USE_OLD_PG2}]) ++
if_version_below('24', [{:d, :COMPILER_REPORTS_ONLY_LINES}]) ++
if_version_below('24', [{:d, :SYSTOOLS_APP_DEF_WITHOUT_OPTIONAL}]) ++
if_function_exported(:uri_string, :normalize, 1, [{:d, :HAVE_URI_STRING}])
if_function_exported(:erl_error, :format_exception, 6, [{:d, :HAVE_ERL_ERROR}])
defines = for {:d, value} <- result, do: {:d, value}
result ++ [{:d, :ALL_DEFS, defines}]
@@ -99,7 +104,7 @@ defmodule Ejabberd.MixProject do
end
defp deps do
[{:base64url, "~> 0.0.1"},
[{:base64url, "~> 1.0"},
{:cache_tab, "~> 1.0"},
{:distillery, "~> 2.0"},
{:eimp, "~> 1.0"},
@@ -110,7 +115,7 @@ defmodule Ejabberd.MixProject do
{:fast_yaml, "~> 1.0"},
{:idna, "~> 6.0"},
{:jiffy, "~> 1.0.5"},
{:jose, "~> 1.8"},
{:jose, "~> 1.11.1"},
{:lager, "~> 3.9.1"},
{:mqtree, "~> 1.0"},
{:p1_acme, "~> 1.0"},
@@ -119,7 +124,7 @@ defmodule Ejabberd.MixProject do
{:p1_pgsql, "~> 1.1"},
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26", override: true},
{:stringprep, ">= 1.0.26"},
{:stun, "~> 1.0"},
{:xmpp, "~> 1.5"},
{:yconf, "~> 1.0"}]
+11 -11
View File
@@ -1,36 +1,36 @@
%{
"artificery": {:hex, :artificery, "0.4.3", "0bc4260f988dcb9dda4b23f9fc3c6c8b99a6220a331534fdf5bf2fd0d4333b02", [:mix], [], "hexpm", "12e95333a30e20884e937abdbefa3e7f5e05609c2ba8cf37b33f000b9ffc0504"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
"cache_tab": {:hex, :cache_tab, "1.0.29", "6c161988620b788d8df28c8f6af557571609c8e4b671dbadab295a4722cd501b", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "a02a638021cce91ed1a8628dcbb4795bf5c01c9d11db8c613065923142824ce9"},
"distillery": {:hex, :distillery, "2.1.1", "f9332afc2eec8a1a2b86f22429e068ef35f84a93ea1718265e740d90dd367814", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm", "bbc7008b0161a6f130d8d903b5b3232351fccc9c31a991f8fcbf2a12ace22995"},
"earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
"eimp": {:hex, :eimp, "1.0.21", "2e918a5dc9a1959ef8713a2360499e3baeee64cfd7881bd9d1f361ca9ddf07e8", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "998f58538f58aa0cff103414994d7ce56dc253e6576cd6fb40c1ead64aa73a28"},
"epam": {:hex, :epam, "1.0.12", "2a5625d4133bca4b3943791a3f723ba764455a461ae9b6ba5debb262efcf4b40", [:rebar3], [], "hexpm", "54c166c4459cef72f2990a3d89a8f0be27180fe0ab0f24b28ddcc3b815f49f7f"},
"esip": {:hex, :esip, "1.0.43", "1cbdc073073f80b9b50e2759f66ca13a353eb4f874bcf92501bd4cd767e34d46", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.44", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "b2c758ae52c4588e0399c0b4ce550bfa56551a5a2f828a28389f2614797e4f4b"},
"ex_doc": {:hex, :ex_doc, "0.25.0", "4070a254664ee5495c2f7cce87c2f43064a8752f7976f2de4937b65871b05223", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2d90883bd4f3d826af0bde7fea733a4c20adba1c79158e2330f7465821c8949b"},
"esip": {:hex, :esip, "1.0.45", "2f21fb9750f7a505e6bbd43f6d48b0e879b808aba6c2224686c83f2bcd7a34bf", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.0.47", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "1f1eae69f2bd8d75f42c048409eabb4e3dc71ab6412fc5d998edbdade6ad5f75"},
"ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"},
"ezlib": {:hex, :ezlib, "1.0.10", "c1c24eb18944cfde55f0574e9922d5b0392fa864282f769f82b2ea15e54f6003", [:rebar3], [], "hexpm", "1d317f1d85373686199eb3b4164d3477e95033ac68e45a95ba18e7b7a8c23241"},
"fast_tls": {:hex, :fast_tls, "1.1.13", "828cdc75e1e8fce8158846d2b971d8b4fe2b2ddcc75b759e88d751079bf78afd", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d1f422af40c7777fe534496f508ee86515cb929ad10f7d1d56aa94ce899b44a0"},
"fast_xml": {:hex, :fast_xml, "1.1.47", "bd1d6c081b69c7bce0d2f22b013c1b864ed2588d48f34e2156d9428f8f772c66", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dd014c45247498effb9a28cf98cb716db79be635ad1e98c951240763119f24c7"},
"fast_xml": {:hex, :fast_xml, "1.1.48", "d41d14015227999a2367264cc97ac1e6770285aab1dc69545ac4f822be01a2d2", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "afcf9b808c77599395d4bd22ed4560b3d82aa1a24ff5b65f3930fe72a423b3cf"},
"fast_yaml": {:hex, :fast_yaml, "1.0.32", "43f53a2c8572f2e4d66cd4e787fc6761b1c65b9132e42c511d8b9540b0989d65", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "7258e322739ff0824237ebe44cd158e0bf52cd27a15fe731cf92f4b4c70b913e"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jiffy": {:hex, :jiffy, "1.0.5", "a69b58faf7123534c20e1b0b7ae97ac52079ca02ed4b6989b4b380179cd63a54", [:rebar3], [], "hexpm", "b617a53f46ae84f20d0c38951367dc947a2cf8cff922aa5c6ac6b64b8b052289"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm", "6429c4fee52b2dda7861ee19a4f09c8c1ffa213bee3a1ec187828fde95d447ed"},
"jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
"lager": {:hex, :lager, "3.9.2", "4cab289120eb24964e3886bd22323cb5fefe4510c076992a23ad18cf85413d8c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "7f904d9e87a8cb7e66156ed31768d1c8e26eba1d54f4bc85b1aa4ac1f6340c28"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mqtree": {:hex, :mqtree, "1.0.14", "d201a79b51a9232b80e764b4b77a866f7c30a90c7ac6205d71f391eb3ea7eb31", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8626dac5e862b575eaf4836f0fc1be5a7c8435c378c5a309e34ee012d48b6f6e"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"p1_acme": {:hex, :p1_acme, "1.0.13", "fec71df416004ce49e295f4846fe5ba3478b41fbe4f73a06b4a8fbc967d6e659", [:rebar3], [{:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.9.0", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.12", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "a2ce9d4904304df020c8e92e8577e0fc88f32623540656317c7e25440b4ac8d2"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"p1_acme": {:hex, :p1_acme, "1.0.16", "88b84cc24c9b6eb87204ea53969ccd9b524dcd4142de632441fdd2859ccab778", [:rebar3], [{:base64url, "1.0.1", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.0.5", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.11.1", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.12", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "ec0ef380a7345c38b57899733f6fece97c337a3d44fd02cc8898f6a2491a38a8"},
"p1_mysql": {:hex, :p1_mysql, "1.0.19", "22f1be58397780a7d580a954e7af66cde32a29dee1a24ab2aa196272fc654a4a", [:rebar3], [], "hexpm", "88f6cdb510e8959c14b6ae84ccda04967e3de239228f859d8341da67949622b1"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.10", "09ba1fbd447b1f480b223903e36d0415f21be592a1b00db964eea01285749028", [:rebar3], [], "hexpm", "c79cb61ababee4a8c85409b7f4932035797c093aeef1f9f53985e512b26f2a64"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.12", "10ae79eeb35ea98c0424a8b6420542fef9e4469eb12ccf41475d10840c291e68", [:rebar3], [], "hexpm", "32203f779e01cf0353270df24833a1d831ad7cb3e3e8e35a7556dfa1f40948d5"},
"p1_utils": {:hex, :p1_utils, "1.0.23", "7f94466ada69bd982ea7bb80fbca18e7053e7d0b82c9d9e37621fa508587069b", [:rebar3], [], "hexpm", "47f21618694eeee5006af1c88731ad86b757161e7823c29b6f73921b571c8502"},
"pkix": {:hex, :pkix, "1.0.8", "98ea05243847fd4504f7c7a0cd82cecd1010ac327a082e1c674c5384006eae75", [:rebar3], [], "hexpm", "399508819501fab9d2e586dfa601b5ee3ef22b5612d3db58204dd2d089ef45d7"},
"stringprep": {:hex, :stringprep, "1.0.27", "02808c7024bc6285ca6a8a67e7addfc16f35dda55551a582c5181d8ea960e890", [:rebar3], [{:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "a5967b1144ca8002a58a03d16dd109fbd0bcdb82616cead2f983944314af6a00"},
"stun": {:hex, :stun, "1.0.44", "30b6b774864b24b05ba901291abe583bff19081e7c4efb3361df50b781ec9d3b", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "e45bba816cbefff01d820e49e66814f450df25a7a468a70d68d1e64218d46520"},
"stun": {:hex, :stun, "1.0.47", "fae94c0dc7415263297e8f07f286f3355d327d8bf78b1b0743c9a5a492381f71", [:rebar3], [{:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "377d8487f4add85f6bc6ecdebdb4dcbcbe890e9462f27d6d31f3db1cf9b2cc9b"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"xmpp": {:hex, :xmpp, "1.5.4", "6cd8144b3fe04745dc2cb3e746d6f2a963bb283db48a61f159b49cbe3fab8623", [:rebar3], [{:ezlib, "1.0.10", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.47", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.27", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "3bc2b5cb24e52964fb11641422ce2b7ba7c261dd50080689a1cbe3d952a9db35"},
"xmpp": {:hex, :xmpp, "1.5.6", "09259177a39c880d682817932f4da0537c471160fd43aa891ea9cb71cf827b52", [:rebar3], [{:ezlib, "1.0.10", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.13", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.48", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.23", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "1.0.27", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "59b7317c4077d3384f9a891e0517a591cdbd44a323260b835eafbede4f4eb12e"},
"yconf": {:hex, :yconf, "1.0.12", "78c119d39bb805207fcb7671cb884805d75ee89c9ec98632b678f90a597dee2c", [:rebar3], [{:fast_yaml, "1.0.32", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "12faa51c281e95bcb6abf185fd034a242209621a7bb04b6cc411c867b192e207"},
}
+49 -1
View File
@@ -5,11 +5,20 @@
{" (Add * to the end of field to match substring)"," (Ajouter * à la fin du champ pour correspondre à la sous-chaîne)"}.
{" has set the subject to: "," a défini le sujet sur : "}.
{"# participants","# participants"}.
{"A description of the node","Une description du nœud"}.
{"A friendly name for the node","Un nom convivial pour le nœud"}.
{"A password is required to enter this room","Un mot de passe est nécessaire pour accéder à ce salon"}.
{"A Web Page","Une page Web"}.
{"Accept","Accepter"}.
{"Access denied by service policy","L'accès au service est refusé"}.
{"Access model of authorize","Modèle daccès de « autoriser »"}.
{"Access model of open","Modèle daccès de « ouvrir »"}.
{"Access model of presence","Modèle daccès de « présence »"}.
{"Access model of roster","Modèle daccès de « liste »"}.
{"Access model of whitelist","Modèle daccès de « liste blanche »"}.
{"Access model","Modèle daccès"}.
{"Account doesn't exist","Le compte nexiste pas"}.
{"Action on user","Action sur l'utilisateur"}.
{"Add Jabber ID","Ajouter un Jabber ID"}.
{"Add New","Ajouter"}.
@@ -19,7 +28,9 @@
{"Administrator privileges required","Les droits d'administrateur sont nécessaires"}.
{"All activity","Toute activité"}.
{"All Users","Tous les utilisateurs"}.
{"Allow subscription","Autoriser labonnement"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Autoriser ce Jabber ID à s'abonner à ce nœud PubSub ?"}.
{"Allow this person to register with the room?","Autoriser cette personne à enregistrer ce salon?"}.
{"Allow users to change the subject","Autoriser les utilisateurs à changer le sujet"}.
{"Allow users to query other users","Autoriser les utilisateurs à envoyer des requêtes aux autres utilisateurs"}.
{"Allow users to send invites","Autoriser les utilisateurs à envoyer des invitations"}.
@@ -28,8 +39,25 @@
{"Allow visitors to send private messages to","Autoriser les visiteurs à envoyer des messages privés"}.
{"Allow visitors to send status text in presence updates","Autoriser les visiteurs à envoyer un message d'état avec leur présence"}.
{"Allow visitors to send voice requests","Permettre aux visiteurs d'envoyer des demandes de 'voice'"}.
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Un groupe LDAP associé qui définit ladhésion à un salon; cela devrait être un nom distingué LDAP selon la définition spécifique à limplémentation ou au déploiement dun groupe."}.
{"Announcements","Annonces"}.
{"Answer associated with a picture","Réponse associée à une image"}.
{"Answer associated with a video","Réponse associée à une vidéo"}.
{"Answer associated with speech","Réponse associée à un discours"}.
{"Answer to a question","Réponse à une question"}.
{"Anyone in the specified roster group(s) may subscribe and retrieve items","Nimporte qui dans le groupe de la liste spécifiée peut sabonner et récupérer les items"}.
{"Anyone may associate leaf nodes with the collection","Nimporte qui peut associer les feuilles avec la collection"}.
{"Anyone may publish","Nimporte qui peut publier"}.
{"Anyone may subscribe and retrieve items","Nimporte qui peut sabonner et récupérer les items"}.
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Nimporte qui avec un abonnement de présence peut sabonner et récupérer les items"}.
{"Anyone with Voice","Nimporte qui avec Voice"}.
{"Anyone","Nimporte qui"}.
{"April","Avril"}.
{"Attribute 'channel' is required for this request","Lattribut « channel » est requis pour la requête"}.
{"Attribute 'id' is mandatory for MIX messages","Lattribut « id » est obligatoire pour les messages MIX"}.
{"Attribute 'jid' is not allowed here","Lattribut « jid » nest pas autorisé ici"}.
{"Attribute 'node' is not allowed here","Lattribut « node » nest pas autorisé ici"}.
{"Attribute 'to' of stanza that triggered challenge","Lattribut « to » de la strophe qui a déclenché le défi"}.
{"August","Août"}.
{"Automatic node creation is not enabled","La creation implicite de nœud n'est pas disponible"}.
{"Backup Management","Gestion des sauvegardes"}.
@@ -43,10 +71,14 @@
{"Cannot remove active list","La liste active ne peut être supprimée"}.
{"Cannot remove default list","La liste par défaut ne peut être supprimée"}.
{"CAPTCHA web page","Page web de CAPTCHA"}.
{"Challenge ID","Identifiant du défi"}.
{"Change Password","Modifier le mot de passe"}.
{"Change User Password","Changer le mot de passe de l'utilisateur"}.
{"Changing password is not allowed","La modification du mot de passe n'est pas autorisée"}.
{"Changing role/affiliation is not allowed","La modification role/affiliation n'est pas autorisée"}.
{"Channel already exists","Ce canal existe déjà"}.
{"Channel does not exist","Le canal nexiste pas"}.
{"Channels","Canaux"}.
{"Characters not allowed:","Caractères non autorisés :"}.
{"Chatroom configuration modified","Configuration du salon modifiée"}.
{"Chatroom is created","Le salon de discussion est créé"}.
@@ -58,30 +90,39 @@
{"Choose storage type of tables","Choisissez un type de stockage pour les tables"}.
{"Choose whether to approve this entity's subscription.","Choisissez d'approuver ou non l'abonnement de cette entité."}.
{"City","Ville"}.
{"Client acknowledged more stanzas than sent by server","Le client accuse réception de plus de strophes que ce qui est envoyé par le serveur"}.
{"Commands","Commandes"}.
{"Conference room does not exist","Le salon de discussion n'existe pas"}.
{"Configuration of room ~s","Configuration pour le salon ~s"}.
{"Configuration","Configuration"}.
{"Connected Resources:","Ressources connectées :"}.
{"Contact Addresses (normally, room owner or owners)","Adresses de contact (normalement les administrateurs du salon)"}.
{"Country","Pays"}.
{"CPU Time:","Temps CPU :"}.
{"Current Discussion Topic","Sujet de discussion courant"}.
{"Database failure","Échec sur la base de données"}.
{"Database Tables at ~p","Tables de base de données sur ~p"}.
{"Database Tables Configuration at ","Configuration des tables de base de données sur "}.
{"Database","Base de données"}.
{"December","Décembre"}.
{"Default users as participants","Les utilisateurs sont participant par défaut"}.
{"Delete content","Supprimer le contenu"}.
{"Delete message of the day on all hosts","Supprimer le message du jour sur tous les domaines"}.
{"Delete message of the day","Supprimer le message du jour"}.
{"Delete Selected","Suppression des éléments sélectionnés"}.
{"Delete table","Supprimer la table"}.
{"Delete User","Supprimer l'utilisateur"}.
{"Deliver event notifications","Envoyer les notifications d'événement"}.
{"Deliver payloads with event notifications","Inclure le contenu du message avec la notification"}.
{"Description:","Description :"}.
{"Disc only copy","Copie sur disque uniquement"}.
{"'Displayed groups' not added (they do not exist!): ","« Groupes affichés » non ajoutés (ils nexistent pas!) : "}.
{"Displayed:","Affichés :"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Ne révélez votre mot de passe à personne, pas même aux administrateurs du serveur XMPP."}.
{"Dump Backup to Text File at ","Enregistrer la sauvegarde dans un fichier texte sur "}.
{"Dump to Text File","Sauvegarder dans un fichier texte"}.
{"Duplicated groups are not allowed by RFC6121","Les groupes dupliqués ne sont pas autorisés par la RFC6121"}.
{"Dynamically specify a replyto of the item publisher","Spécifie dynamiquement un « réponse à » de litem de l’éditeur"}.
{"Edit Properties","Modifier les propriétés"}.
{"Either approve or decline the voice request.","Accepter ou refuser la demande de voix."}.
{"ejabberd MUC module","Module MUC ejabberd"}.
@@ -116,6 +157,7 @@
{"Failed to parse HTTP response","Échec de lecture de la réponse HTTP"}.
{"Failed to process option '~s'","Échec de traitement de l'option '~s'"}.
{"Family Name","Nom de famille"}.
{"FAQ Entry","Entrée FAQ"}.
{"February","Février"}.
{"File larger than ~w bytes","Taille de fichier suppérieur à ~w octets"}.
{"Fill in the form to search for any matching XMPP User","Complétez le formulaire pour rechercher un utilisateur XMPP correspondant"}.
@@ -176,6 +218,7 @@
{"July","Juillet"}.
{"June","Juin"}.
{"Just created","Vient d'être créé"}.
{"Label:","Étiquette :"}.
{"Last Activity","Dernière activité"}.
{"Last login","Dernière connexion"}.
{"Last month","Dernier mois"}.
@@ -192,7 +235,6 @@
{"Make room public searchable","Rendre le salon public"}.
{"Malformed username","Nom d'utilisateur invalide"}.
{"March","Mars"}.
{"Max # of items to persist","Nombre maximum d'éléments à stocker"}.
{"Max payload size in bytes","Taille maximum pour le contenu du message en octet"}.
{"Maximum file size","Taille maximale du fichier"}.
{"Maximum Number of History Messages Returned by Room","Nombre maximal de messages d'historique renvoyés par salle"}.
@@ -274,6 +316,7 @@
{"Online Users:","Utilisateurs connectés :"}.
{"Online Users","Utilisateurs en ligne"}.
{"Online","En ligne"}.
{"Only admins can see this","Seuls les administrateurs peuvent voir cela"}.
{"Only deliver notifications to available users","Envoyer les notifications uniquement aux utilisateurs disponibles"}.
{"Only <enable/> or <disable/> tags are allowed","Seul le tag <enable/> ou <disable/> est autorisé"}.
{"Only <list/> element is allowed in this query","Seul l'élément <list/> est autorisé dans cette requête"}.
@@ -283,6 +326,7 @@
{"Only moderators can approve voice requests","Seuls les modérateurs peuvent accépter les requêtes voix"}.
{"Only occupants are allowed to send messages to the conference","Seuls les occupants peuvent envoyer des messages à la conférence"}.
{"Only occupants are allowed to send queries to the conference","Seuls les occupants sont autorisés à envoyer des requêtes à la conférence"}.
{"Only publishers may publish","Seuls les éditeurs peuvent publier"}.
{"Only service administrators are allowed to send service messages","Seuls les administrateurs du service sont autoriser à envoyer des messages de service"}.
{"Organization Name","Nom de l'organisation"}.
{"Organization Unit","Unité de l'organisation"}.
@@ -290,6 +334,7 @@
{"Outgoing s2s Connections:","Connexions s2s sortantes :"}.
{"Owner privileges required","Les droits de propriétaire sont nécessaires"}.
{"Packet","Paquet"}.
{"Participant","Participant"}.
{"Password Verification","Vérification du mot de passe"}.
{"Password Verification:","Vérification du mot de passe :"}.
{"Password","Mot de passe"}.
@@ -323,6 +368,9 @@
{"Remove User","Supprimer l'utilisateur"}.
{"Remove","Supprimer"}.
{"Replaced by new connection","Remplacé par une nouvelle connexion"}.
{"Request has timed out","La demande a expiré"}.
{"Request is ignored","La demande est ignorée"}.
{"Requested role","Rôle demandé"}.
{"Resources","Ressources"}.
{"Restart Service","Redémarrer le service"}.
{"Restart","Redémarrer"}.
+15 -18
View File
@@ -4,7 +4,7 @@
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (Add * to the end of field to match substring)"," (在字段末添加*来匹配子串)"}.
{" has set the subject to: ","已将标题设置为: "}.
{" has set the subject to: "," 已将标题设置为: "}.
{"# participants","# 个参与人"}.
{"A description of the node","该节点的描述"}.
{"A friendly name for the node","该节点的友好名称"}.
@@ -23,7 +23,7 @@
{"Add Jabber ID","添加Jabber ID"}.
{"Add New","添加新用户"}.
{"Add User","添加用户"}.
{"Administration of ","管理"}.
{"Administration of ","管理 "}.
{"Administration","管理"}.
{"Administrator privileges required","需要管理员权限"}.
{"All activity","所有活动"}.
@@ -62,7 +62,7 @@
{"Automatic node creation is not enabled","未启用自动节点创建"}.
{"Backup Management","备份管理"}.
{"Backup of ~p","~p的备份"}.
{"Backup to File at ","备份文件位于"}.
{"Backup to File at ","备份文件位于 "}.
{"Backup","备份"}.
{"Bad format","格式错误"}.
{"Birthday","出生日期"}.
@@ -102,7 +102,7 @@
{"Current Discussion Topic","当前讨论话题"}.
{"Database failure","数据库失败"}.
{"Database Tables at ~p","位于~p的数据库表"}.
{"Database Tables Configuration at ","数据库表格配置位于"}.
{"Database Tables Configuration at ","数据库表格配置位于 "}.
{"Database","数据库"}.
{"December","十二月"}.
{"Default users as participants","用户默认被视为参与人"}.
@@ -119,12 +119,12 @@
{"'Displayed groups' not added (they do not exist!): ","'显示的群组' 未被添加 (它们不存在!): "}.
{"Displayed:","已显示:"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","不要将密码告诉任何人, 就算是XMPP服务器的管理员也不可以."}.
{"Dump Backup to Text File at ","转储备份到文本文件"}.
{"Dump Backup to Text File at ","将备份转储到位于以下位置的文本文件 "}.
{"Dump to Text File","转储到文本文件"}.
{"Duplicated groups are not allowed by RFC6121","按照RFC6121的规则,不允许有重复的群组"}.
{"Dynamically specify a replyto of the item publisher","为项目发布者动态指定一个 replyto"}.
{"Edit Properties","编辑属性"}.
{"Either approve or decline the voice request.","接受或拒绝声音请求"}.
{"Either approve or decline the voice request.","接受或拒绝声音请求."}.
{"ejabberd HTTP Upload service","ejabberd HTTP 上传服务"}.
{"ejabberd MUC module","ejabberd MUC 模块"}.
{"ejabberd Multicast service","ejabberd多重映射服务"}.
@@ -194,10 +194,10 @@
{"Import Directory","导入目录"}.
{"Import File","导入文件"}.
{"Import user data from jabberd14 spool file:","从 jabberd14 Spool 文件导入用户数据:"}.
{"Import User from File at ","导入用户的文件位于"}.
{"Import User from File at ","从以下位置的文件导入用户 "}.
{"Import users data from a PIEFXIS file (XEP-0227):","从 PIEFXIS 文件 (XEP-0227) 导入用户数据:"}.
{"Import users data from jabberd14 spool directory:","从jabberd14 Spool目录导入用户数据:"}.
{"Import Users from Dir at ","导入用户的目录位于"}.
{"Import Users from Dir at ","从以下位置目录导入用户 "}.
{"Import Users From jabberd14 Spool Files","从 jabberd14 Spool 文件导入用户"}.
{"Improper domain part of 'from' attribute","不恰当的'from'属性域名部分"}.
{"Improper message type","不恰当的消息类型"}.
@@ -249,7 +249,6 @@
{"Malformed username","用户名无效"}.
{"MAM preference modification denied by service policy","MAM偏好被服务策略拒绝"}.
{"March","三月"}.
{"Max # of items to persist","允许持久化的最大内容条目数"}.
{"Max payload size in bytes","最大有效负载字节数"}.
{"Maximum file size","最大文件大小"}.
{"Maximum Number of History Messages Returned by Room","房间返回的历史消息最大值"}.
@@ -288,7 +287,7 @@
{"Never","从未"}.
{"New Password:","新密码:"}.
{"Nickname can't be empty","昵称不能为空"}.
{"Nickname Registration at ","昵称注册于"}.
{"Nickname Registration at ","昵称注册于 "}.
{"Nickname ~s does not exist in the room","昵称~s不在该房间"}.
{"Nickname","昵称"}.
{"No address elements found","没有找到地址的各元素"}.
@@ -326,6 +325,7 @@
{"Node ~p","节点~p"}.
{"Nodeprep has failed","Nodeprep 已失效"}.
{"Nodes","节点"}.
{"Node","节点"}.
{"None","无"}.
{"Not allowed","不允许"}.
{"Not Found","没有找到"}.
@@ -339,7 +339,6 @@
{"Number of Offline Messages","离线消息数量"}.
{"Number of online users","在线用户数"}.
{"Number of registered users","注册用户数"}.
{"Number of seconds after which to automatically purge items","自动清除项目要等待的秒数"}.
{"Occupants are allowed to invite others","允许成员邀请其他人"}.
{"Occupants May Change the Subject","成员可以修改主题"}.
{"October","十月"}.
@@ -428,7 +427,7 @@
{"Resources","资源"}.
{"Restart Service","重启服务"}.
{"Restart","重启"}.
{"Restore Backup from File at ","恢复备份文件位于"}.
{"Restore Backup from File at ","从以下位置的文件恢复备份 "}.
{"Restore binary backup after next ejabberd restart (requires less memory):","在下次 ejabberd 重启后恢复二进制备份(需要的内存更少):"}.
{"Restore binary backup immediately:","立即恢复二进制备份:"}.
{"Restore plain text backup immediately:","立即恢复普通文本备份:"}.
@@ -455,7 +454,7 @@
{"Search Results for ","搜索结果属于关键词 "}.
{"Search the text","搜索文本"}.
{"Search until the date","搜索截至日期"}.
{"Search users in ","搜索用户"}.
{"Search users in ","在以下位置搜索用户 "}.
{"Select All","全选"}.
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
{"Send announcement to all online users","发送通知给所有在线用户"}.
@@ -519,7 +518,6 @@
{"The JIDs of those with an affiliation of owner","隶属所有人的JID"}.
{"The JIDs of those with an affiliation of publisher","隶属发布人的JID"}.
{"The list of JIDs that may associate leaf nodes with a collection","可以将叶节点与集合关联的JID列表"}.
{"The maximum number of child nodes that can be associated with a collection","可以与集合关联的最大子节点数"}.
{"The minimum number of milliseconds between sending any two notification digests","发送任何两个通知摘要之间的最小毫秒数"}.
{"The name of the node","该节点的名称"}.
{"The node is a collection node","该节点是集合节点"}.
@@ -544,13 +542,12 @@
{"The type of node data, usually specified by the namespace of the payload (if any)","节点数据的类型, 如果有, 通常由有效负载的名称空间指定"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","XSL转换的URL,可以将其应用于有效负载以生成适当的消息正文元素。"}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","XSL转换的URL, 可以将其应用于有效负载格式, 以生成有效的数据表单结果, 客户端可以使用通用数据表单呈现引擎来显示该结果"}.
{"The username is not valid","用户名无效"}.
{"There was an error changing the password: ","修改密码出错: "}.
{"There was an error creating the account: ","帐户创建出错: "}.
{"There was an error deleting the account: ","帐户删除失败: "}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","此处不区分大小写: macbeth 与 MacBeth 和 Macbeth 是一样的."}.
{"This page allows to register an XMPP account in this XMPP server. Your JID (Jabber ID) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","本页面允许在此服务器上注册XMPP帐户. 你的JID (Jabber ID) 的形式如下: 用户名@服务器. 请仔细阅读说明并正确填写相应字段."}.
{"This page allows to unregister an XMPP account in this XMPP server.","此页面允许在此XMPP服务器上注销XMPP帐户"}.
{"This page allows to unregister an XMPP account in this XMPP server.","此页面允许在此 XMPP 服务器上注销 XMPP 帐户"}.
{"This room is not anonymous","此房间不是匿名房间"}.
{"This service can not process the address: ~s","此服务无法处理地址: ~s"}.
{"Thursday","星期四"}.
@@ -565,7 +562,7 @@
{"Too many child elements","太多子元素"}.
{"Too many <item/> elements","太多 <item/> 元素"}.
{"Too many <list/> elements","太多 <list/> 元素"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","来自IP地址(~p)的(~s)失败认证太多. 该地址将在UTC时间~s被禁用."}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","来自IP地址(~p)的(~s)失败认证太多将在UTC时间 ~s 解除对该地址的封锁"}.
{"Too many receiver fields were specified","指定的接收者字段太多"}.
{"Too many unacked stanzas","未被确认的节太多"}.
{"Too many users in this conference","该会议的用户太多"}.
@@ -656,7 +653,7 @@
{"You need a client that supports x:data to register the nickname","您需要一个支持 x:data 的客户端来注册昵称"}.
{"You need an x:data capable client to search","您需要一个兼容 x:data 的客户端来搜索"}.
{"Your active privacy list has denied the routing of this stanza.","你的活跃私聊列表拒绝了在此房间进行路由分发."}.
{"Your contact offline message queue is full. The message has been discarded.","您的联系人离线消息队列已满. 消息已被丢弃"}.
{"Your contact offline message queue is full. The message has been discarded.","您的联系人离线消息队列已满消息已被丢弃"}.
{"Your subscription request and/or messages to ~s have been blocked. To unblock your subscription request, visit ~s","您发送给~s的消息已被阻止. 要解除阻止, 请访问 ~s"}.
{"Your XMPP account was successfully registered.","你的XMPP帐户注册成功."}.
{"Your XMPP account was successfully unregistered.","你的XMPP帐户注销成功."}.
+8 -8
View File
@@ -30,25 +30,25 @@
{if_var_true, redis,
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
{if_var_true, sip,
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.43"}}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.45"}}}},
{if_var_true, zlib,
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.10"}}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.13"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.47"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.48"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.32"}}},
{idna, ".*", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "1.0.5"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.9.0"}}},
{jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}},
{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.9.1"}}},
{if_var_true, lua,
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.3"}}}},
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.14"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.13"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.16"}}},
{if_var_true, mysql,
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.19"}}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.10"}}},
{if_var_true, pgsql,
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.12"}}}},
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.16"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.23"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.8"}}},
{if_not_rebar3, %% Needed because modules are not fully migrated to new structure and mix
@@ -58,8 +58,8 @@
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.13"}}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.27"}}},
{if_var_true, stun,
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.44"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.5.4"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.47"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.5.6"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.12"}}}
]}.
@@ -110,6 +110,7 @@
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
{if_have_fun, {erl_error, format_exception, 6}, {d, 'HAVE_ERL_ERROR'}},
{if_have_fun, {uri_string, normalize, 1}, {d, 'HAVE_URI_STRING'}},
{src_dirs, [src,
{if_rebar3, sql},
{if_var_true, tools, tools},
@@ -179,7 +180,6 @@
{overlay_vars, "vars.config"},
{extended_start_script, true},
{overlay, [{mkdir, "var/log/ejabberd"},
{mkdir, "var/lock"},
{mkdir, "var/lib/ejabberd"},
{mkdir, "etc/ejabberd"},
{copy, "rel/files/erl", "\{\{erts_vsn\}\}/bin/erl"}, % in rebar2 this prepends erts-
+2 -2
View File
@@ -387,8 +387,8 @@ Rules = [
]}]), []},
{[plugins], IsRebar3 and (os:getenv("GITHUB_ACTIONS") == "true"),
AppendList([{coveralls, {git,
"https://github.com/RoadRunnr/coveralls-erl.git",
{branch, "feature/git-info"}}} ]), []},
"https://github.com/processone/coveralls-erl.git",
{branch, "addjsonfile"}}} ]), []},
{[overrides], [post_hook_configure], SystemDeps == false,
AppendList2(GenDepsConfigure), [], []},
{[ct_extra_params], [eunit_compile_opts], true,
-1
View File
@@ -89,7 +89,6 @@ Sys = [{lib_dirs, []},
Overlay = [
{mkdir, "var/log/ejabberd"},
{mkdir, "var/lock"},
{mkdir, "var/lib/ejabberd"},
{mkdir, "etc/ejabberd"},
{mkdir, "doc"},
+1 -1
View File
@@ -86,7 +86,7 @@ CREATE TABLE sr_user (
PRIMARY KEY (server_host(191), jid, grp)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE UNIQUE INDEX i_sr_user_sh_jid_group ON sr_group(server_host(191), jid, grp);
CREATE UNIQUE INDEX i_sr_user_sh_jid_group ON sr_user(server_host(191), jid, grp);
CREATE INDEX i_sr_user_sh_jid ON sr_user(server_host(191), jid);
CREATE INDEX i_sr_user_sh_grp ON sr_user(server_host(191), grp);
+2
View File
@@ -77,6 +77,7 @@ BEGIN
ALTER TABLE `last` ADD PRIMARY KEY (`server_host`, `username`);
ALTER TABLE `sr_group` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `name`;
ALTER TABLE `sr_group` ALTER COLUMN `server_host` DROP DEFAULT;
ALTER TABLE `sr_group` ADD UNIQUE INDEX `i_sr_group_sh_name` (`server_host`, `name`);
ALTER TABLE `sr_group` ADD PRIMARY KEY (`server_host`, `name`);
ALTER TABLE `muc_registered` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `host`;
ALTER TABLE `muc_registered` ALTER COLUMN `server_host` DROP DEFAULT;
@@ -99,6 +100,7 @@ BEGIN
ALTER TABLE `sr_user` DROP INDEX `i_sr_user_jid_group`;
ALTER TABLE `sr_user` ADD COLUMN `server_host` VARCHAR (191) COLLATE `utf8mb4_unicode_ci` NOT NULL DEFAULT @DEFAULT_HOST AFTER `jid`;
ALTER TABLE `sr_user` ALTER COLUMN `server_host` DROP DEFAULT;
ALTER TABLE `sr_user` ADD UNIQUE INDEX `i_sr_user_sh_jid_group` (`server_host`, `jid`, `grp`);
ALTER TABLE `sr_user` ADD INDEX `i_sr_user_sh_jid` (`server_host`, `jid`);
ALTER TABLE `sr_user` ADD INDEX `i_sr_user_sh_grp` (`server_host`, `grp`);
ALTER TABLE `sr_user` ADD PRIMARY KEY (`server_host`, `jid`, `grp`);
+7 -1
View File
@@ -156,6 +156,12 @@
-- CREATE INDEX i_sm_sh_username ON sm USING btree (server_host, username);
-- ALTER TABLE sm ALTER COLUMN server_host DROP DEFAULT;
-- ALTER TABLE push_session ADD COLUMN server_host text NOT NULL DEFAULT '<HOST>';
-- DROP INDEX i_push_usn;
-- DROP INDEX i_push_ut;
-- ALTER TABLE push_session ADD PRIMARY KEY (server_host, username, timestamp);
-- CREATE UNIQUE INDEX i_push_session_susn ON push_session USING btree (server_host, username, service, node);
CREATE TABLE users (
username text NOT NULL,
@@ -305,7 +311,7 @@ CREATE TABLE vcard_search (
lorgname text NOT NULL,
orgunit text NOT NULL,
lorgunit text NOT NULL,
PRIMARY KEY (server_host, username)
PRIMARY KEY (server_host, lusername)
);
CREATE INDEX i_vcard_search_sh_lfn ON vcard_search(server_host, lfn);
+2 -1
View File
@@ -27,7 +27,8 @@
%% Hooks
-export([ejabberd_started/0, register_certfiles/0, cert_expired/2]).
%% ejabberd commands
-export([request_certificate/1, revoke_certificate/1, list_certificates/0]).
-export([get_commands_spec/0, request_certificate/1,
revoke_certificate/1, list_certificates/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, format_status/2]).
+37 -17
View File
@@ -119,12 +119,15 @@ get_commands_spec() ->
desc = "Restart ejabberd gracefully",
module = init, function = restart,
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs, server],
desc = "Reopen the log files",
#ejabberd_commands{name = reopen_log, tags = [logs],
desc = "Reopen the log files after being renamed",
longdesc = "This can be useful when an external tool is "
"used for log rotation. See "
"https://docs.ejabberd.im/admin/guide/troubleshooting/#log-files",
policy = admin,
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
#ejabberd_commands{name = rotate_log, tags = [logs, server],
#ejabberd_commands{name = rotate_log, tags = [logs],
desc = "Rotate the log files",
module = ?MODULE, function = rotate_log,
args = [], result = {res, rescode}},
@@ -139,14 +142,14 @@ get_commands_spec() ->
args_example = [60, <<"Server will stop now.">>],
args = [{delay, integer}, {announcement, string}],
result = {res, rescode}},
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
#ejabberd_commands{name = get_loglevel, tags = [logs],
desc = "Get the current loglevel",
module = ejabberd_logger, function = get,
result_desc = "Tuple with the log level number, its keyword and description",
result_example = warning,
args = [],
result = {levelatom, atom}},
#ejabberd_commands{name = set_loglevel, tags = [logs, server],
#ejabberd_commands{name = set_loglevel, tags = [logs],
desc = "Set the loglevel",
module = ?MODULE, function = set_loglevel,
args_desc = ["Desired logging level: none | emergency | alert | critical "
@@ -200,7 +203,7 @@ get_commands_spec() ->
result_example = [<<"example.com">>, <<"anon.example.com">>],
args = [],
result = {vhosts, {list, {vhost, string}}}},
#ejabberd_commands{name = reload_config, tags = [server, config],
#ejabberd_commands{name = reload_config, tags = [config],
desc = "Reload config file in memory",
module = ?MODULE, function = reload_config,
args = [],
@@ -254,21 +257,21 @@ get_commands_spec() ->
module = ejabberd_piefxis, function = import_file,
args_desc = ["Full path to the PIEFXIS file"],
args_example = ["/var/lib/ejabberd/example.com.xml"],
args = [{file, string}], result = {res, rescode}},
args = [{file, binary}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_server,
args_desc = ["Full path to a directory"],
args_example = ["/var/lib/ejabberd/"],
args = [{dir, string}], result = {res, rescode}},
args = [{dir, binary}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_host,
args_desc = ["Full path to a directory", "Vhost to export"],
args_example = ["/var/lib/ejabberd/", "example.com"],
args = [{dir, string}, {host, string}], result = {res, rescode}},
args = [{dir, binary}, {host, binary}], result = {res, rescode}},
#ejabberd_commands{name = delete_mnesia, tags = [mnesia, sql],
#ejabberd_commands{name = delete_mnesia, tags = [mnesia],
desc = "Delete elements in Mnesia database for a given vhost",
module = ejd2sql, function = delete,
args_desc = ["Vhost which content will be deleted in Mnesia database"],
@@ -326,7 +329,7 @@ get_commands_spec() ->
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [mnesia],
#ejabberd_commands{name = set_master, tags = [cluster],
desc = "Set master node of the clustered Mnesia tables",
longdesc = "If you provide as nodename \"self\", this "
"node will be set as its own master.",
@@ -345,31 +348,41 @@ get_commands_spec() ->
{oldbackup, string}, {newbackup, string}],
result = {res, restuple}},
#ejabberd_commands{name = backup, tags = [mnesia],
desc = "Store the database to backup file",
desc = "Backup the Mnesia database to a binary file",
module = ?MODULE, function = backup_mnesia,
args_desc = ["Full path for the destination backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = restore, tags = [mnesia],
desc = "Restore the database from backup file",
desc = "Restore the Mnesia database from a binary backup file",
longdesc = "This restores immediately from a "
"binary backup file the internal Mnesia "
"database. This will consume a lot of memory if "
"you have a large database, you may prefer "
"'install_fallback'.",
module = ?MODULE, function = restore_mnesia,
args_desc = ["Full path to the backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump, tags = [mnesia],
desc = "Dump the database to a text file",
desc = "Dump the Mnesia database to a text file",
module = ?MODULE, function = dump_mnesia,
args_desc = ["Full path for the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump_table, tags = [mnesia],
desc = "Dump a table to a text file",
desc = "Dump a Mnesia table to a text file",
module = ?MODULE, function = dump_table,
args_desc = ["Full path for the text file", "Table name"],
args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
args = [{file, string}, {table, string}], result = {res, restuple}},
#ejabberd_commands{name = load, tags = [mnesia],
desc = "Restore the database from a text file",
desc = "Restore Mnesia database from a text dump file",
longdesc = "Restore immediately. This is not "
"recommended for big databases, as it will "
"consume much time, memory and processor. In "
"that case it's preferable to use 'backup' and "
"'install_fallback'.",
module = ?MODULE, function = load_mnesia,
args_desc = ["Full path to the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
@@ -385,7 +398,14 @@ get_commands_spec() ->
args_example = ["roster"],
args = [{table, string}], result = {res, string}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
desc = "Install Mnesia database from a binary backup file",
longdesc = "The binary backup file is "
"installed as fallback: it will be used to "
"restore the database at the next ejabberd "
"start. This means that, after running this "
"command, you have to restart ejabberd. This "
"command requires less memory than
'restore'.",
module = ?MODULE, function = install_fallback_mnesia,
args_desc = ["Full path to the fallback file"],
args_example = ["/var/lib/ejabberd/database.fallback"],
+14
View File
@@ -299,6 +299,20 @@ export(_Server) ->
["username=%(LUser)s",
"server_host=%(LServer)s",
"password=%(Password)s"])];
(Host, {passwd, {LUser, LServer},
{scram, StoredKey1, ServerKey, Salt, IterationCount}})
when LServer == Host ->
Hash = sha,
StoredKey = scram_hash_encode(Hash, StoredKey1),
[?SQL("delete from users where username=%(LUser)s and %(LServer)H;"),
?SQL_INSERT(
"users",
["username=%(LUser)s",
"server_host=%(LServer)s",
"password=%(StoredKey)s",
"serverkey=%(ServerKey)s",
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"])];
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
when LServer == Host ->
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),
+16 -1
View File
@@ -41,6 +41,7 @@
get_tags_commands/0,
get_tags_commands/1,
register_commands/1,
register_commands/2,
unregister_commands/1,
get_commands_spec/0,
get_commands_definition/0,
@@ -90,6 +91,17 @@ get_commands_spec() ->
"that will have example invocation include in markdown document"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
result_example = ok},
#ejabberd_commands{name = gen_markdown_doc_for_tags, tags = [documentation],
desc = "Generates markdown documentation for ejabberd_commands",
note = "added in 21.12",
module = ejabberd_commands_doc, function = generate_tags_md,
args = [{file, binary}],
result = {res, rescode},
args_desc = ["Path to file where generated "
"documentation should be stored"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/tags.md"],
result_example = ok}].
start_link() ->
@@ -129,10 +141,13 @@ code_change(_OldVsn, State, _Extra) ->
-spec register_commands([ejabberd_commands()]) -> ok.
register_commands(Commands) ->
register_commands(unknown, Commands).
register_commands(Definer, Commands) ->
lists:foreach(
fun(Command) ->
%% XXX check if command exists
mnesia:dirty_write(Command)
mnesia:dirty_write(Command#ejabberd_commands{definer = Definer})
%% ?DEBUG("This command is already defined:~n~p", [Command])
end,
Commands),
+39 -6
View File
@@ -28,6 +28,7 @@
-export([generate_html_output/3]).
-export([generate_md_output/3]).
-export([generate_tags_md/1]).
-include("ejabberd_commands.hrl").
@@ -360,8 +361,16 @@ gen_param(Name, Type, Desc, HTMLOutput) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))].
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc, note=Note,
make_tags(HTMLOutput) ->
TagsList = ejabberd_commands:get_tags_commands(1000000),
lists:map(fun(T) -> gen_tags(T, HTMLOutput) end, TagsList).
-dialyzer({no_match, gen_tags/2}).
gen_tags({TagName, Commands}, HTMLOutput) ->
[?TAG(h1, TagName) | [?TAG(p, ?RAW("* *`"++C++"`*")) || C <- Commands]].
gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc, note=Note, definer=Definer,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
try
ArgsText = case ArgsDesc of
@@ -389,6 +398,17 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
end,
TagsText = [?RAW("*`"++atom_to_list(Tag)++"`* ") || Tag <- Tags],
IsDefinerMod = case Definer of
unknown -> true;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
end,
ModuleText = case IsDefinerMod of
true ->
[?TAG(h2, <<"Module:">>), ?TAG(p, ?RAW("*`"++atom_to_list(Definer)++"`*"))];
false ->
[]
end,
NoteEl = case Note of
"" -> [];
_ -> ?TAG('div', "note-down", ?RAW(Note))
@@ -403,6 +423,8 @@ gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
end,
?TAG(h2, <<"Arguments:">>), ArgsText,
?TAG(h2, <<"Result:">>), ResultText,
?TAG(h2, <<"Tags:">>), ?TAG(p, TagsText)]
++ ModuleText ++ [
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
catch
_:Ex ->
@@ -421,12 +443,13 @@ find_commands_definitions() ->
lists:flatmap(fun(P) ->
Mod = list_to_atom(filename:rootname(P)),
code:ensure_loaded(Mod),
case erlang:function_exported(Mod, get_commands_spec, 0) of
Cs = case erlang:function_exported(Mod, get_commands_spec, 0) of
true ->
apply(Mod, get_commands_spec, []);
_ ->
[]
end
end,
[C#ejabberd_commands{definer = Mod} || C <- Cs]
end, filelib:wildcard("*.beam", Path))
end.
@@ -466,15 +489,25 @@ generate_md_output(File, RegExp, Languages) ->
end, Cmds2),
Cmds4 = [maybe_add_policy_arguments(Cmd) || Cmd <- Cmds3],
Langs = binary:split(Languages, <<",">>, [global]),
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: Administration API\norder: 40\n"
Header = <<"---\ntitle: Administration API reference\ntoc: true\nmenu: API Reference\norder: 1\n"
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_commands'\n---\n\n"
"This section describes API of ejabberd.">>,
"This section describes API of ejabberd.\n">>,
Out = lists:map(fun(C) -> gen_doc(C, false, Langs) end, Cmds4),
{ok, Fh} = file:open(File, [write]),
io:format(Fh, "~ts~ts", [Header, Out]),
file:close(Fh),
ok.
generate_tags_md(File) ->
Header = <<"---\ntitle: API Tags\ntoc: true\nmenu: API Tags\norder: 2\n"
"// Autogenerated with 'ejabberdctl gen_markdown_doc_for_tags'\n---\n\n"
"This section enumerates the tags and their associated API.\n">>,
Tags = make_tags(false),
{ok, Fh} = file:open(File, [write]),
io:format(Fh, "~ts~ts", [Header, Tags]),
file:close(Fh),
ok.
html_pre() ->
"<!DOCTYPE>
<html>
+12 -1
View File
@@ -525,6 +525,7 @@ print_usage(Version) ->
print_usage(HelpMode, MaxC, ShCode, Version) ->
AllCommands =
[
{"help", ["[arguments]"], "Get help"},
{"status", [], "Get ejabberd status"},
{"stop", [], "Stop ejabberd"},
{"restart", [], "Restart ejabberd"},
@@ -827,6 +828,7 @@ print_usage_command(Cmd, MaxC, ShCode, Version) ->
print_usage_command2(Cmd, C, MaxC, ShCode) ->
#ejabberd_commands{
tags = TagsAtoms,
definer = Definer,
desc = Desc,
args = ArgsDef,
longdesc = LongDesc,
@@ -851,6 +853,15 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
TagsFmt = [" ",?B("Tags"),":", prepare_long_line(8, MaxC, [?G(atom_to_list(TagA)) || TagA <- TagsAtoms])],
IsDefinerMod = case Definer of
unknown -> true;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
end,
ModuleFmt = case IsDefinerMod of
true -> [" ",?B("Module"),": ", atom_to_list(Definer), "\n\n"];
false -> []
end,
DescFmt = [" ",?B("Description"),":", prepare_description(15, MaxC, Desc)],
LongDescFmt = case LongDesc of
@@ -866,7 +877,7 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
case Cmd of
"help" -> ok;
_ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt,
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", DescFmt, "\n\n"], [])
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", ModuleFmt, DescFmt, "\n\n"], [])
end,
print([LongDescFmt, NoteEjabberdctl], []).
+10 -2
View File
@@ -288,10 +288,11 @@ start(Level) ->
ConsoleFmtConfig = FmtConfig#{template => console_template()},
try
ok = logger:set_primary_config(level, Level),
ok = logger:update_formatter_config(default, ConsoleFmtConfig),
DefaultHandlerId = get_default_handlerid(),
ok = logger:update_formatter_config(DefaultHandlerId, ConsoleFmtConfig),
case quiet_mode() of
true ->
ok = logger:set_handler_config(default, level, critical);
ok = logger:set_handler_config(DefaultHandlerId, level, critical);
_ ->
ok
end,
@@ -319,6 +320,13 @@ start(Level) ->
Err
end.
get_default_handlerid() ->
Ids = logger:get_handler_ids(),
case lists:member(default, Ids) of
true -> default;
false -> hd(Ids)
end.
-spec restart() -> ok.
restart() ->
ok.
+97 -90
View File
@@ -61,11 +61,11 @@ doc() ->
desc =>
?T("The time of a cached item to keep in cache. "
"Once it's expired, the corresponding item is "
"erased from cache. The default value is 'one hour'. "
"erased from cache. The default value is '1 hour'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_life_time', 'oauth_cache_life_time', "
"'router_cache_life_time', and 'sm_cache_life_time'.")}},
"_`auth_cache_life_time`_, _`oauth_cache_life_time`_, "
"_`router_cache_life_time`_, and _`sm_cache_life_time`_.")}},
{cache_missed,
#{value => "true | false",
desc =>
@@ -73,12 +73,12 @@ doc() ->
"an attempt to lookup for a value in a database and "
"this value is not found and the option is set to 'true', "
"this attempt will be cached and no attempts will be "
"performed until the cache expires (see 'cache_life_time'). "
"performed until the cache expires (see _`cache_life_time`_). "
"Usually you don't want to change it. Default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_missed', 'oauth_cache_missed', "
"'router_cache_missed', and 'sm_cache_missed'.")}},
"_`auth_cache_missed`_, _`oauth_cache_missed`_, "
"_`router_cache_missed`_, and _`sm_cache_missed`_.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
@@ -93,16 +93,16 @@ doc() ->
"performance. The default value is '1000'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_cache_size', 'oauth_cache_size', "
"'router_cache_size', and 'sm_cache_size'.")}},
"_`auth_cache_size`_, _`oauth_cache_size`_, "
"_`router_cache_size`_, and _`sm_cache_size`_.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Enable or disable cache. The default is 'true'. "
"Several modules have a similar option; and some core "
"ejabberd parts support similar options too, see "
"'auth_use_cache', 'oauth_use_cache', 'router_use_cache', "
"and 'sm_use_cache'.")}},
"_`auth_use_cache`_, _`oauth_use_cache`_, _`router_use_cache`_, "
"and _`sm_use_cache`_.")}},
{default_db,
#{value => "mnesia | sql",
desc =>
@@ -122,14 +122,14 @@ doc() ->
"Modules may have its own value of the option. "
"The value of 'ram' means that queues will be kept in memory. "
"If value 'file' is set, you may also specify directory "
"in 'queue_dir' option where file queues will be placed. "
"in _`queue_dir`_ option where file queues will be placed. "
"The default value is 'ram'.")}},
{version,
#{value => "string()",
desc =>
?T("The option can be used to set custom ejabberd version, "
"that will be used by different parts of ejabberd, for "
"example by 'mod_version' module. The default value is "
"example by _`mod_version`_ module. The default value is "
"obtained at compile time from the underlying version "
"control system.")}},
{acl,
@@ -141,7 +141,7 @@ doc() ->
"has name 'ACLName': it can be any string except 'all' or 'none' "
"(those are predefined names for the rules that match all or nothing "
"respectively). The name 'ACLName' can be referenced from other "
"parts of the configuration file, for example in 'access_rules' "
"parts of the configuration file, for example in _`access_rules`_ "
"option. The rules of 'ACLName' are represented by mapping "
"'pass:[{ACLType: ACLValue}]'. These can be one of the following:")},
[{user,
@@ -225,7 +225,7 @@ doc() ->
"of the configuration file (mostly from 'access' options of "
"ejabberd modules). Each rule definition may contain "
"arbitrary number of 'allow' or 'deny' sections, and each "
"section may contain any number of ACL rules (see 'acl' option). "
"section may contain any number of ACL rules (see _`acl`_ option). "
"There are no access rules defined by default."),
example =>
["access_rules:",
@@ -313,10 +313,12 @@ doc() ->
{anonymous_protocol,
#{value => "login_anon | sasl_anon | both",
desc =>
?T("'login_anon' means that the anonymous login method will be used. "
"'sasl_anon' means that the SASL Anonymous method will be used. "
"'both' means that SASL Anonymous and login anonymous are both "
"enabled. The default value is 'sasl_anon'.")}},
[?T("Define what anonymous protocol will be used: "), "",
?T("* 'login_anon' means that the anonymous login method will be used. "), "",
?T("* 'sasl_anon' means that the SASL Anonymous method will be used. "), "",
?T("* 'both' means that SASL Anonymous and login anonymous are both "
"enabled."), "",
?T("The default value is 'sasl_anon'."), ""]}},
{api_permissions,
#{value => "[Permission, ...]",
desc =>
@@ -334,18 +336,18 @@ doc() ->
{auth_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to authentication cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to authentication cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{auth_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to authentication cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to authentication cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{auth_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to authentication cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to authentication cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{auth_method,
#{value => "[mnesia | sql | anonymous | external | jwt | ldap | pam, ...]",
desc =>
@@ -359,38 +361,41 @@ doc() ->
desc =>
?T("This is used by the contributed module "
"'ejabberd_auth_http' that can be installed from the "
"'ejabberd-contrib' Git repository. Please refer to that "
"https://github.com/processone/ejabberd-contrib[ejabberd-contrib] "
"Git repository. Please refer to that "
"module's README file for details.")}},
{auth_password_format,
#{value => "plain | scram",
note => "improved in 20.01",
desc =>
?T("The option defines in what format the users passwords "
"are stored. 'plain': The password is stored as plain text "
[?T("The option defines in what format the users passwords "
"are stored:"), "",
?T("* 'plain': The password is stored as plain text "
"in the database. This is risky because the passwords "
"can be read if your database gets compromised. "
"This is the default value. This format allows clients to "
"authenticate using: the old Jabber Non-SASL (XEP-0078), "
"SASL PLAIN, SASL DIGEST-MD5, and SASL SCRAM-SHA-1. "
"'scram': The password is not stored, only some information "
"SASL PLAIN, SASL DIGEST-MD5, and SASL SCRAM-SHA-1. "), "",
?T("* 'scram': The password is not stored, only some information "
"that allows to verify the hash provided by the client. "
"It is impossible to obtain the original plain password "
"from the stored information; for this reason, when this "
"value is configured it cannot be changed to plain anymore. "
"This format allows clients to authenticate using: "
"SASL PLAIN and SASL SCRAM-SHA-1.")}},
"SASL PLAIN and SASL SCRAM-SHA-1."),
?T("The default value is 'plain'.")]}},
{auth_scram_hash,
#{value => "sha | sha256 | sha512",
desc =>
?T("Hash algorith that should be used to store password in SCRAM format. "
"You shouldn't change this if you already have passwords generated with "
"a different algorithm - users that have such passwords will not be able "
"to authenticate.")}},
"to authenticate. The default value is 'sha'.")}},
{auth_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to authentication cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to authentication cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{c2s_cafile,
#{value => ?T("Path"),
desc =>
@@ -449,22 +454,22 @@ doc() ->
{captcha_cmd,
#{value => ?T("Path"),
desc =>
?T("Full path to a script that generates CAPTCHA images. "
?T("Full path to a script that generates http://../basic/#captcha[CAPTCHA] images. "
"There is no default value: when this option is not "
"set, CAPTCHA functionality is completely disabled.")}},
{captcha_limit,
#{value => "pos_integer() | infinity",
desc =>
?T("Maximum number of CAPTCHA generated images per minute for "
?T("Maximum number of http://../basic/#captcha[CAPTCHA] generated images per minute for "
"any given JID. The option is intended to protect the server "
"from CAPTCHA DoS. The default value is 'infinity'.")}},
{captcha_host,
#{value => "String",
desc => ?T("Deprecated. Use 'captcha_url' instead.")}},
desc => ?T("Deprecated. Use _`captcha_url`_ instead.")}},
{captcha_url,
#{value => ?T("URL"),
desc =>
?T("An URL where CAPTCHA requests should be sent. NOTE: you need "
?T("An URL where http://../basic/#captcha[CAPTCHA] requests should be sent. NOTE: you need "
"to configure 'request_handlers' for 'ejabberd_http' listener "
"as well. There is no default value.")}},
{certfiles,
@@ -674,7 +679,8 @@ doc() ->
desc =>
?T("The option defines the default language of server strings "
"that can be seen by XMPP clients. If an XMPP client does not "
"possess 'xml:lang' attribute, the specified language is used.")}},
"possess 'xml:lang' attribute, the specified language is used. "
"The default value is '\"en\"'.")}},
{ldap_servers,
#{value => "[Host, ...]",
desc =>
@@ -684,7 +690,7 @@ doc() ->
#{value => "[Host, ...]",
desc =>
?T("A list of IP addresses or DNS names of LDAP backup servers. "
"When no servers listed in 'ldap_servers' option are reachable, "
"When no servers listed in _`ldap_servers`_ option are reachable, "
"ejabberd will try to connect to these backup servers. "
"The default is an empty list, i.e. no backup servers specified. "
"WARNING: ejabberd doesn't try to reconnect back to the main "
@@ -791,7 +797,7 @@ doc() ->
"the result set. There is no default value, which means the "
"result is not filtered. WARNING: Since this filter makes "
"additional LDAP lookups, use it only as the last resort: "
"try to define all filter rules in 'ldap_filter' option if possible."),
"try to define all filter rules in _`ldap_filter`_ option if possible."),
example =>
["ldap_dn_filter:",
" \"(&(name=%s)(owner=%D)(user=%u@%d))\": [sn]"]}},
@@ -799,7 +805,8 @@ doc() ->
#{value => ?T("Number"),
desc =>
?T("The number of rotated log files to keep. "
"The default value is '1'.")}},
"The default value is '1', which means that only keeps "
"`ejabberd.log.0`, `error.log.0` and `crash.log.0`.")}},
{log_rotate_size,
#{value => "pos_integer() | infinity",
desc =>
@@ -835,7 +842,7 @@ doc() ->
"must have identical value on all nodes, or it will lead to subtle "
"bugs. Usually leaving default value of this is option is best, "
"tweak it only if you know what you are doing. "
"The default value is '1' minute.")}},
"The default value is '1 minute'.")}},
{new_sql_schema,
#{value => "true | false",
desc =>
@@ -861,13 +868,13 @@ doc() ->
{oauth_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to OAuth cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{oauth_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to OAuth cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{oauth_cache_rest_failure_life_time,
#{value => "timeout()",
note => "added in 21.01",
@@ -877,8 +884,8 @@ doc() ->
{oauth_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to OAuth cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to OAuth cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{oauth_client_id_check,
#{value => "allow | db | deny",
desc =>
@@ -888,13 +895,13 @@ doc() ->
{oauth_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to OAuth cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to OAuth cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{oauth_db_type,
#{value => "mnesia | sql",
desc =>
?T("Database backend to use for OAuth authentication. "
"The default value is picked from 'default_db' option, or "
"The default value is picked from _`default_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{oauth_expire,
#{value => "timeout()",
@@ -908,7 +915,7 @@ doc() ->
desc =>
?T("Enable or disable OOM (out-of-memory) killer. "
"When system memory raises above the limit defined in "
"'oom_watermark' option, ejabberd triggers OOM killer "
"_`oom_watermark`_ option, ejabberd triggers OOM killer "
"to terminate most memory consuming Erlang processes. "
"Note that in order to maintain functionality, ejabberd only "
"attempts to kill transient processes, such as those managing "
@@ -919,14 +926,14 @@ doc() ->
desc =>
?T("Trigger OOM killer when some of the running Erlang processes "
"have messages queue above this 'Size'. Note that "
"such processes won't be killed if 'oom_killer' option is set "
"such processes won't be killed if _`oom_killer`_ option is set "
"to 'false' or if 'oom_watermark' is not reached yet.")}},
{oom_watermark,
#{value => ?T("Percent"),
desc =>
?T("A percent of total system memory consumed at which "
"OOM killer should be activated with some of the processes "
"possibly be killed (see 'oom_killer' option). Later, when "
"possibly be killed (see _`oom_killer`_ option). Later, when "
"memory drops below this 'Percent', OOM killer is deactivated. "
"The default value is '80' percents.")}},
{outgoing_s2s_families,
@@ -979,7 +986,7 @@ doc() ->
{queue_dir,
#{value => ?T("Directory"),
desc =>
?T("If 'queue_type' option is set to 'file', use this 'Directory' "
?T("If _`queue_type`_ option is set to 'file', use this 'Directory' "
"to store file queues. The default is to keep queues inside "
"Mnesia directory.")}},
{redis_connect_timeout,
@@ -1009,8 +1016,8 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of request queue for the Redis server. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{redis_server,
#{value => ?T("Hostname"),
@@ -1020,7 +1027,7 @@ doc() ->
{registration_timeout,
#{value => "timeout()",
desc =>
?T("This is a global option for module 'mod_register'. "
?T("This is a global option for module _`mod_register`_. "
"It limits the frequency of registrations from a given "
"IP or username. So, a user that tries to register a "
"new account from the same IP address or JID during "
@@ -1043,29 +1050,29 @@ doc() ->
{router_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to routing table cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to routing table cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{router_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to routing table cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to routing table cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{router_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to routing table cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to routing table cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{router_db_type,
#{value => "mnesia | redis | sql",
desc =>
?T("Database backend to use for routing information. "
"The default value is picked from 'default_ram_db' option, or "
"The default value is picked from _`default_ram_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{router_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to routing table cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to routing table cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{rpc_timeout,
#{value => "timeout()",
desc =>
@@ -1142,14 +1149,14 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of a queue for s2s packets. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{s2s_timeout,
#{value => "timeout()",
desc =>
?T("A time to wait before closing an idle s2s connection. "
"The default value is '10' minutes.")}},
"The default value is '10 minutes'.")}},
{s2s_use_starttls,
#{value => "true | false | optional | required",
desc =>
@@ -1171,7 +1178,7 @@ doc() ->
desc =>
?T("The option defines a set of shapers. Every shaper is assigned "
"a name 'ShaperName' that can be used in other parts of the "
"configuration file, such as 'shaper_rules' option. The shaper "
"configuration file, such as _`shaper_rules`_ option. The shaper "
"itself is defined by its 'Rate', where 'Rate' stands for the "
"maximum allowed incoming rate in **bytes** per second. "
"When a connection exceeds this limit, ejabberd stops reading "
@@ -1187,9 +1194,9 @@ doc() ->
#{value => "{ShaperRuleName: {Number|ShaperName: ACLRule|ACLName}}",
desc =>
?T("An entry allowing to declaring shaper to use for matching user/hosts. "
"Semantics is similar to 'access_rules' option, the only difference is "
"Semantics is similar to _`access_rules`_ option, the only difference is "
"that instead using 'allow' or 'deny', a name of a shaper (defined in "
"'shaper' option) or a positive number should be used."),
"_`shaper`_ option) or a positive number should be used."),
example =>
["shaper_rules:",
" connections_limit:",
@@ -1205,29 +1212,29 @@ doc() ->
{sm_cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as 'cache_life_time', but applied to client sessions table cache "
"only. If not set, the value from 'cache_life_time' will be used.")}},
?T("Same as _`cache_life_time`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_life_time`_ will be used.")}},
{sm_cache_missed,
#{value => "true | false",
desc =>
?T("Same as 'cache_missed', but applied to client sessions table cache "
"only. If not set, the value from 'cache_missed' will be used.")}},
?T("Same as _`cache_missed`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_missed`_ will be used.")}},
{sm_cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as 'cache_size', but applied to client sessions table cache "
"only. If not set, the value from 'cache_size' will be used.")}},
?T("Same as _`cache_size`_, but applied to client sessions table cache "
"only. If not set, the value from _`cache_size`_ will be used.")}},
{sm_db_type,
#{value => "mnesia | redis | sql",
desc =>
?T("Database backend to use for client sessions information. "
"The default value is picked from 'default_ram_db' option, or "
"The default value is picked from _`default_ram_db`_ option, or "
"if it's not set, 'mnesia' will be used.")}},
{sm_use_cache,
#{value => "true | false",
desc =>
?T("Same as 'use_cache', but applied to client sessions table cache "
"only. If not set, the value from 'use_cache' will be used.")}},
?T("Same as _`use_cache`_, but applied to client sessions table cache "
"only. If not set, the value from _`use_cache`_ will be used.")}},
{sql_type,
#{value => "mssql | mysql | odbc | pgsql | sqlite",
desc =>
@@ -1253,7 +1260,7 @@ doc() ->
note => "added in 20.12",
desc =>
?T("Path to the ODBC driver to use to connect to a Microsoft SQL "
"Server database. This option is only valid if the 'sql_type' "
"Server database. This option is only valid if the _`sql_type`_ "
"option is set to 'mssql'. "
"The default value is: 'libtdsodbc.so'")}},
{sql_password,
@@ -1288,8 +1295,8 @@ doc() ->
#{value => "ram | file",
desc =>
?T("The type of a request queue for the SQL server. "
"See description of 'queue_type' option for the explanation. "
"The default value is the value defined in 'queue_type' "
"See description of _`queue_type`_ option for the explanation. "
"The default value is the value defined in _`queue_type`_ "
"or 'ram' if the latter is not set.")}},
{sql_server,
#{value => ?T("Host"),
@@ -1307,15 +1314,15 @@ doc() ->
#{value => ?T("Path"),
desc =>
?T("A path to a file with CA root certificates that will "
"be used to verify SQL connections. Implies 'sql_ssl' "
"and 'sql_ssl_verify' options are set to 'true'. "
"be used to verify SQL connections. Implies _`sql_ssl`_ "
"and _`sql_ssl_verify`_ options are set to 'true'. "
"There is no default which means "
"certificate verification is disabled.")}},
{sql_ssl_certfile,
#{value => ?T("Path"),
desc =>
?T("A path to a certificate file that will be used "
"for SSL connections to the SQL server. Implies 'sql_ssl' "
"for SSL connections to the SQL server. Implies _`sql_ssl`_ "
"option is set to 'true'. There is no default which means "
"ejabberd won't provide a client certificate to the SQL "
"server.")}},
@@ -1323,8 +1330,8 @@ doc() ->
#{value => "true | false",
desc =>
?T("Whether to verify SSL connection to the SQL server against "
"CA root certificates defined in 'sql_ssl_cafile' option. "
"Implies 'sql_ssl' option is set to 'true'. "
"CA root certificates defined in _`sql_ssl_cafile`_ option. "
"Implies _`sql_ssl`_ option is set to 'true'. "
"The default value is 'false'.")}},
{sql_start_interval,
#{value => "timeout()",
+86 -40
View File
@@ -24,17 +24,15 @@
%%%----------------------------------------------------------------------
%%% Not implemented:
%%% - PEP nodes export/import
%%% - message archives export/import
%%% - write mod_piefxis with ejabberdctl commands
%%% - Export from mod_offline_sql.erl
%%% - Export from mod_private_sql.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
%%%% Headers
-module(ejabberd_piefxis).
-protocol({xep, 227, '1.0'}).
-protocol({xep, 227, '1.1'}).
-export([import_file/1, export_server/1, export_host/2]).
@@ -166,33 +164,66 @@ export_users([], _Server, _Fd) ->
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
Pass = case ejabberd_auth:password_format(LServer) of
scram -> format_scram_password(Password);
_ -> Password
{PassPlain, PassScram} = case ejabberd_auth:password_format(LServer) of
scram -> {[], [format_scram_password(Password)]};
_ -> {[{<<"password">>, Password}], []}
end,
Els = get_offline(User, Server) ++
Els =
PassScram ++
get_offline(User, Server) ++
get_vcard(User, Server) ++
get_privacy(User, Server) ++
get_roster(User, Server) ++
get_private(User, Server),
print(Fd, fxml:element_to_binary(
#xmlel{name = <<"user">>,
attrs = [{<<"name">>, User},
{<<"password">>, Pass}],
attrs = [{<<"name">>, User} | PassPlain],
children = Els})).
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
salt = Salt, iterationcount = IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = (integer_to_binary(IterationCount)),
Hash2 = case Hash of
sha -> <<>>;
sha256 -> <<"sha256,">>;
sha512 -> <<"sha512,">>
end,
<<"scram:", Hash2/binary, StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = (integer_to_binary(IterationCount)),
MechanismB = case Hash of
sha -> <<"SCRAM-SHA-1">>;
sha256 -> <<"SCRAM-SHA-256">>;
sha512 -> <<"SCRAM-SHA-512">>
end,
Children =
[
#xmlel{name = <<"iter-count">>,
children = [{xmlcdata, IterationCountBin}]},
#xmlel{name = <<"salt">>,
children = [{xmlcdata, SaltB64}]},
#xmlel{name = <<"server-key">>,
children = [{xmlcdata, ServerKeyB64}]},
#xmlel{name = <<"stored-key">>,
children = [{xmlcdata, StoredKeyB64}]}
],
#xmlel{name = <<"scram-credentials">>,
attrs = [{<<"xmlns">>, <<?NS_PIE/binary, "#scram">>},
{<<"mechanism">>, MechanismB}],
children = Children}.
parse_scram_password(#xmlel{attrs = Attrs} = El) ->
Hash = case fxml:get_attr_s(<<"mechanism">>, Attrs) of
<<"SCRAM-SHA-1">> -> sha;
<<"SCRAM-SHA-256">> -> sha256;
<<"SCRAM-SHA-512">> -> sha512
end,
StoredKeyB64 = fxml:get_path_s(El, [{elem, <<"stored-key">>}, cdata]),
ServerKeyB64 = fxml:get_path_s(El, [{elem, <<"server-key">>}, cdata]),
IterationCountBin = fxml:get_path_s(El, [{elem, <<"iter-count">>}, cdata]),
SaltB64 = fxml:get_path_s(El, [{elem, <<"salt">>}, cdata]),
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
};
parse_scram_password(PassData) ->
Split = binary:split(PassData, <<",">>, [global]),
@@ -214,26 +245,30 @@ parse_scram_password(PassData) ->
get_vcard(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_vcard:get_vcard(LUser, LServer) of
try mod_vcard:get_vcard(LUser, LServer) of
error -> [];
Els -> Els
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_offline(binary(), binary()) -> [xmlel()].
get_offline(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case mod_offline:get_offline_els(LUser, LServer) of
try mod_offline:get_offline_els(LUser, LServer) of
[] ->
[];
Els ->
NewEls = lists:map(fun xmpp:encode/1, Els),
[#xmlel{name = <<"offline-messages">>, children = NewEls}]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_privacy(binary(), binary()) -> [xmlel()].
get_privacy(User, Server) ->
case mod_privacy:get_user_lists(User, Server) of
try mod_privacy:get_user_lists(User, Server) of
{ok, #privacy{default = Default,
lists = [_|_] = Lists}} ->
XLists = lists:map(
@@ -246,12 +281,14 @@ get_privacy(User, Server) ->
[xmpp:encode(#privacy_query{default = Default, lists = XLists})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_roster(binary(), binary()) -> [xmlel()].
get_roster(User, Server) ->
JID = jid:make(User, Server),
case mod_roster:get_roster(User, Server) of
try mod_roster:get_roster(User, Server) of
[_|_] = Items ->
Subs =
lists:flatmap(
@@ -278,15 +315,19 @@ get_roster(User, Server) ->
[xmpp:encode(#roster_query{items = Rs}) | Subs];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
-spec get_private(binary(), binary()) -> [xmlel()].
get_private(User, Server) ->
case mod_private:get_data(User, Server) of
try mod_private:get_data(User, Server) of
[_|_] = Els ->
[xmpp:encode(#private{sub_els = Els})];
_ ->
[]
catch
error:{module_not_loaded, _, _} -> []
end.
process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) ->
@@ -398,21 +439,10 @@ process_users([_|Els], State) ->
process_users([], State) ->
{ok, State}.
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els} = El,
#state{server = LServer} = State) ->
Name = fxml:get_attr_s(<<"name">>, Attrs),
Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_auth:password_format(LServer),
Pass = case PasswordFormat of
scram ->
case Password of
<<"scram:", PassData/binary>> ->
parse_scram_password(PassData);
P -> P
end;
_ -> Password
end,
Pass = process_password(El, LServer),
case jid:nodeprep(Name) of
error ->
stop("Invalid 'user': ~ts", [Name]);
@@ -420,13 +450,29 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
case ejabberd_auth:try_register(LUser, LServer, Pass) of
ok ->
process_user_els(Els, State#state{user = LUser});
{error, invalid_password} when (Password == <<>>) ->
{error, invalid_password} when (Pass == <<>>) ->
process_user_els(Els, State#state{user = LUser});
{error, Err} ->
stop("Failed to create user '~ts': ~p", [Name, Err])
end
end.
process_password(#xmlel{name = <<"user">>, attrs = Attrs} = El, LServer) ->
{PassPlain, PassOldScram} = case fxml:get_attr_s(<<"password">>, Attrs) of
<<"scram:", PassData/binary>> -> {<<"">>, PassData};
P -> {P, false}
end,
ScramCred = fxml:get_subtag(El, <<"scram-credentials">>),
PasswordFormat = ejabberd_auth:password_format(LServer),
case {PassPlain, PassOldScram, ScramCred, PasswordFormat} of
{PassPlain, false, false, plain} -> PassPlain;
{<<"">>, false, ScramCred, plain} -> parse_scram_password(ScramCred);
{<<"">>, PassOldScram, false, plain} -> parse_scram_password(PassOldScram);
{PassPlain, false, false, scram} -> PassPlain;
{<<"">>, false, ScramCred, scram} -> parse_scram_password(ScramCred);
{<<"">>, PassOldScram, false, scram} -> parse_scram_password(PassOldScram)
end.
process_user_els([#xmlel{} = El|Els], State) ->
case process_user_el(El, State) of
{ok, NewState} ->
+33 -46
View File
@@ -33,8 +33,8 @@
%% API
-export([start_link/0, stop/0, route/1, have_connection/1,
get_connections_pids/1, try_register/1,
remove_connection/2, start_connection/2, start_connection/3,
get_connections_pids/1,
start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
stop_s2s_connections/0,
@@ -64,7 +64,7 @@
%% once a server is temporary blocked, it stay blocked for 60 seconds
-record(s2s, {fromto :: {binary(), binary()},
-record(s2s, {fromto :: {binary(), binary()} | '_',
pid :: pid()}).
-record(state, {}).
@@ -112,24 +112,6 @@ is_temporarly_blocked(Host) ->
end
end.
-spec remove_connection({binary(), binary()}, pid()) -> ok.
remove_connection({From, To} = FromTo, Pid) ->
case mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, pid = Pid}) of
[#s2s{pid = Pid}] ->
F = fun() ->
mnesia:delete_object(#s2s{fromto = FromTo, pid = Pid})
end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed to unregister s2s connection ~ts -> ~ts: "
"Mnesia failure: ~p",
[From, To, Reason])
end;
_ ->
ok
end.
-spec have_connection({binary(), binary()}) -> boolean().
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
@@ -148,31 +130,6 @@ get_connections_pids(FromTo) ->
[]
end.
-spec try_register({binary(), binary()}) -> boolean().
try_register({From, To} = FromTo) ->
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
max_s2s_connections_number_per_node(FromTo),
F = fun () ->
L = mnesia:read({s2s, FromTo}),
NeededConnections = needed_connections_number(L,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode),
if NeededConnections > 0 ->
mnesia:write(#s2s{fromto = FromTo, pid = self()}),
true;
true -> false
end
end,
case mnesia:transaction(F) of
{atomic, Res} -> Res;
{aborted, Reason} ->
?ERROR_MSG("Failed to register s2s connection ~ts -> ~ts: "
"Mnesia failure: ~p",
[From, To, Reason]),
false
end.
-spec dirty_get_connections() -> [{binary(), binary()}].
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
@@ -269,6 +226,8 @@ init([]) ->
{stop, Reason}
end.
handle_call({new_connection, Args}, _From, State) ->
{reply, erlang:apply(fun new_connection_int/7, Args), State};
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
@@ -289,6 +248,21 @@ handle_info({route, Packet}, State) ->
misc:format_exception(2, Class, Reason, StackTrace)])
end,
{noreply, State};
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
case mnesia:dirty_match_object(s2s, #s2s{fromto = '_', pid = Pid}) of
[#s2s{pid = Pid, fromto = {From, To}} = Obj] ->
F = fun() -> mnesia:delete_object(Obj) end,
case mnesia:transaction(F) of
{atomic, _} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed to unregister s2s connection for pid ~p (~ts -> ~ts):"
"Mnesia failure: ~p",
[Pid, From, To, Reason])
end,
{noreply, State};
_ ->
{noreply, State}
end;
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
@@ -458,6 +432,18 @@ open_several_connections(N, MyServer, Server, From,
integer(), integer(), [proplists:property()]) -> [pid()].
new_connection(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
case whereis(ejabberd_s2s) == self() of
true ->
new_connection_int(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts);
false ->
gen_server:call(ejabberd_s2s, {new_connection, [MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber,
MaxS2SConnectionsNumberPerNode, Opts]})
end.
new_connection_int(MyServer, Server, From, FromTo,
MaxS2SConnectionsNumber, MaxS2SConnectionsNumberPerNode, Opts) ->
{ok, Pid} = ejabberd_s2s_out:start(MyServer, Server, Opts),
F = fun() ->
L = mnesia:read({s2s, FromTo}),
@@ -474,6 +460,7 @@ new_connection(MyServer, Server, From, FromTo,
case TRes of
{atomic, Pid1} ->
if Pid1 == Pid ->
erlang:monitor(process, Pid),
ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop_async(Pid)
+6 -16
View File
@@ -318,7 +318,6 @@ handle_info(Info, #{server_host := ServerHost} = State) ->
terminate(Reason, #{server := LServer,
remote_server := RServer} = State) ->
ejabberd_s2s:remove_connection({LServer, RServer}, self()),
State1 = case Reason of
normal -> State;
_ -> State#{stop_reason => internal_failure}
@@ -351,21 +350,12 @@ bounce_queue(State) ->
end, State).
-spec bounce_message_queue({binary(), binary()}, state()) -> state().
bounce_message_queue({LServer, RServer} = FromTo, State) ->
Pids = ejabberd_s2s:get_connections_pids(FromTo),
case lists:member(self(), Pids) of
true ->
?WARNING_MSG("Outgoing s2s connection ~ts -> ~ts is supposed "
"to be unregistered, but pid ~p still presents "
"in 's2s' table", [LServer, RServer, self()]),
State;
false ->
receive {route, Pkt} ->
State1 = bounce_packet(Pkt, State),
bounce_message_queue(FromTo, State1)
after 0 ->
State
end
bounce_message_queue(FromTo, State) ->
receive {route, Pkt} ->
State1 = bounce_packet(Pkt, State),
bounce_message_queue(FromTo, State1)
after 0 ->
State
end.
-spec bounce_packet(xmpp_element(), state()) -> state().
+68 -9
View File
@@ -564,15 +564,23 @@ make_sql_upsert(Table, ParseRes, Pos) ->
[]
end,
erl_syntax:fun_expr(
[erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90100))],
[make_sql_upsert_pgsql901(Table, ParseRes),
erl_syntax:atom(ok)])] ++
MySqlReplace ++
[erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90500))],
[make_sql_upsert_pgsql905(Table, ParseRes),
erl_syntax:atom(ok)]),
erl_syntax:clause(
[erl_syntax:atom(pgsql), erl_syntax:variable("__Version")],
[erl_syntax:infix_expr(
erl_syntax:variable("__Version"),
erl_syntax:operator('>='),
erl_syntax:integer(90100))],
[make_sql_upsert_pgsql901(Table, ParseRes),
erl_syntax:atom(ok)])] ++
MySqlReplace ++
[erl_syntax:clause(
[erl_syntax:underscore(), erl_syntax:underscore()],
none,
@@ -713,6 +721,57 @@ make_sql_upsert_pgsql901(Table, ParseRes0) ->
erl_syntax:atom(sql_query_t),
[Upsert]).
make_sql_upsert_pgsql905(Table, ParseRes0) ->
ParseRes = lists:map(
fun({"family", A2, A3}) -> {"\"family\"", A2, A3};
(Other) -> Other
end, ParseRes0),
Vals =
lists:map(
fun({_Field, _, ST}) ->
ST
end, ParseRes),
Fields =
lists:map(
fun({Field, _, _ST}) ->
#state{'query' = [{str, Field}]}
end, ParseRes),
SPairs =
lists:flatmap(
fun({_Field, key, _ST}) ->
[];
({_Field, {false}, _ST}) ->
[];
({Field, {true}, ST}) ->
[ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'
}]
end, ParseRes),
Set = join_states(SPairs, ", "),
KeyFields =
lists:flatmap(
fun({Field, key, _ST}) ->
[#state{'query' = [{str, Field}]}];
({_Field, _, _ST}) ->
[]
end, ParseRes),
State =
concat_states(
[#state{'query' = [{str, "INSERT INTO "}, {str, Table}, {str, "("}]},
join_states(Fields, ", "),
#state{'query' = [{str, ") VALUES ("}]},
join_states(Vals, ", "),
#state{'query' = [{str, ") ON CONFLICT ("}]},
join_states(KeyFields, ", "),
#state{'query' = [{str, ") DO UPDATE SET "}]},
Set
]),
Upsert = make_sql_query(State),
erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Upsert]).
check_upsert(ParseRes, Pos) ->
Set =
+6 -4
View File
@@ -602,12 +602,14 @@ list_vhosts2(Lang, Hosts) ->
[?AC(<<"../server/", Host/binary,
"/">>,
Host)]),
?XAC(<<"td">>,
?XAE(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(RegisteredUsers))),
?XAC(<<"td">>,
[?AC(<<"../server/", Host/binary, "/users/">>,
pretty_string_int(RegisteredUsers))]),
?XAE(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(OnlineUsers)))])
[?AC(<<"../server/", Host/binary, "/online-users/">>,
pretty_string_int(OnlineUsers))])])
end,
SHosts)))])].
+10 -5
View File
@@ -73,11 +73,16 @@ export(Server, Output) ->
end, Modules),
close_output(Output, IO).
export(Server, Output, Module1) ->
Module = case Module1 of
mod_pubsub -> pubsub_db;
_ -> Module1
end,
export(Server, Output, mod_mam = M1) ->
MucServices = gen_mod:get_module_opt_hosts(Server, mod_muc),
[export2(MucService, Output, M1, M1) || MucService <- MucServices],
export2(Server, Output, M1, M1);
export(Server, Output, mod_pubsub = M1) ->
export2(Server, Output, M1, pubsub_db);
export(Server, Output, M1) ->
export2(Server, Output, M1, M1).
export2(Server, Output, Module1, Module) ->
SQLMod = gen_mod:db_mod(sql, Module),
LServer = jid:nameprep(iolist_to_binary(Server)),
IO = prepare_output(Output),
+7 -7
View File
@@ -84,14 +84,14 @@ code_change(_OldVsn, State, _Extra) ->
%% -- ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{name = modules_update_specs,
tags = [admin,modules],
tags = [modules],
desc = "Update the module source code from Git",
longdesc = "A connection to Internet is required",
module = ?MODULE, function = update,
args = [],
result = {res, rescode}},
#ejabberd_commands{name = modules_available,
tags = [admin,modules],
tags = [modules],
desc = "List the contributed modules available to install",
module = ?MODULE, function = available_command,
result_desc = "List of tuples with module name and description",
@@ -103,7 +103,7 @@ get_commands_spec() ->
[{name, atom},
{summary, string}]}}}}},
#ejabberd_commands{name = modules_installed,
tags = [admin,modules],
tags = [modules],
desc = "List the contributed modules already installed",
module = ?MODULE, function = installed_command,
result_desc = "List of tuples with module name and description",
@@ -115,7 +115,7 @@ get_commands_spec() ->
[{name, atom},
{summary, string}]}}}}},
#ejabberd_commands{name = module_install,
tags = [admin,modules],
tags = [modules],
desc = "Compile, install and start an available contributed module",
module = ?MODULE, function = install,
args_desc = ["Module name"],
@@ -123,7 +123,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_uninstall,
tags = [admin,modules],
tags = [modules],
desc = "Uninstall a contributed module",
module = ?MODULE, function = uninstall,
args_desc = ["Module name"],
@@ -131,7 +131,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_upgrade,
tags = [admin,modules],
tags = [modules],
desc = "Upgrade the running code of an installed module",
longdesc = "In practice, this uninstalls and installs the module",
module = ?MODULE, function = upgrade,
@@ -140,7 +140,7 @@ get_commands_spec() ->
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_check,
tags = [admin,modules],
tags = [modules],
desc = "Check the contributed module repository compliance",
module = ?MODULE, function = check,
args_desc = ["Module name"],
+9
View File
@@ -122,12 +122,21 @@
{result, {default, broadcast}} |
{error, stanza_error()}.
-callback remove_extra_items(NodeIdx :: nodeIdx(),
Max_Items :: unlimited | non_neg_integer()) ->
{result, {[itemId()], [itemId()]}
}.
-callback remove_extra_items(NodeIdx :: nodeIdx(),
Max_Items :: unlimited | non_neg_integer(),
ItemIds :: [itemId()]) ->
{result, {[itemId()], [itemId()]}
}.
-callback remove_expired_items(NodeIdx :: nodeIdx(),
Seconds :: infinity | non_neg_integer()) ->
{result, [itemId()]}.
-callback get_node_affiliations(NodeIdx :: nodeIdx()) ->
{result, [{ljid(), affiliation()}]}.
+3
View File
@@ -67,6 +67,9 @@
-callback get_nodes(Host :: host())->
[pubsubNode()].
-callback get_all_nodes(Host :: host()) ->
[pubsubNode()].
-callback get_parentnodes(Host :: host(),
NodeId :: nodeId(),
From :: jid:jid()) ->
+2 -11
View File
@@ -92,7 +92,7 @@
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
@@ -1652,14 +1652,6 @@ mod_doc() ->
?T("If you want to put a group Name with blankspaces, use the "
"characters \"\' and \'\" to define when the Name starts and "
"ends. See an example below.")],
opts =>
[{module_resource,
#{value => ?T("Resource"),
desc =>
?T("Indicate the resource that the XMPP stanzas must use "
"in the FROM or TO JIDs. This is only useful in the "
"'get_vcard*' and 'set_vcard*' commands. The default "
"value is 'mod_admin_extra'.")}}],
example =>
[{?T("With this configuration, vCards can only be modified with "
"mod_admin_extra commands:"),
@@ -1670,8 +1662,7 @@ mod_doc() ->
" vcard_set:",
" - allow: adminextraresource",
"modules:",
" mod_admin_extra:",
" module_resource: \"modadminextraf8x,31ad\"",
" mod_admin_extra: {}",
" mod_vcard:",
" access_set: vcard_set"]},
{?T("Content of roster file for 'pushroster' command:"),
+5 -3
View File
@@ -46,7 +46,7 @@
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(_Host) ->
ejabberd_commands:unregister_commands(get_commands_spec()).
@@ -140,6 +140,7 @@ update_tables(State) ->
add_sh_column(State, "sr_group"),
add_pkey(State, "sr_group", ["server_host", "name"]),
create_unique_index(State, "sr_group", "i_sr_group_sh_name", ["server_host", "name"]),
drop_sh_default(State, "sr_group"),
add_sh_column(State, "sr_user"),
@@ -147,6 +148,7 @@ update_tables(State) ->
drop_index(State, "i_sr_user_jid"),
drop_index(State, "i_sr_user_grp"),
add_pkey(State, "sr_user", ["server_host", "jid", "grp"]),
create_unique_index(State, "sr_user", "i_sr_user_sh_jid_grp", ["server_host", "jid", "grp"]),
create_index(State, "sr_user", "i_sr_user_sh_jid", ["server_host", "jid"]),
create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
drop_sh_default(State, "sr_user"),
@@ -364,6 +366,6 @@ mod_doc() ->
#{desc =>
?T("This module can be used to update existing SQL database "
"from the default to the new schema. Check the section "
"http://../database-ldap/#default-and-new-schemas[Default and New Schemas] for details. "
"http://../database/#default-and-new-schemas[Default and New Schemas] for details. "
"Please note that only PostgreSQL is supported. "
"When the module is loaded use 'update_sql' ejabberdctl command.")}.
"When the module is loaded use _`update_sql`_ API.")}.
+7 -7
View File
@@ -930,7 +930,7 @@ mod_doc() ->
"should be disabled for instances of ejabberd with hundreds of "
"thousands users."), "",
?T("The Ad-hoc Commands are listed in the Server Discovery. "
"For this feature to work, 'mod_adhoc' must be enabled."), "",
"For this feature to work, _`mod_adhoc`_ must be enabled."), "",
?T("The specific JIDs where messages can be sent are listed below. "
"The first JID in each entry will apply only to the specified "
"virtual host example.org, while the JID between brackets "
@@ -940,7 +940,7 @@ mod_doc() ->
"online and connected to several resources, only the resource "
"with the highest priority will receive the message. "
"If the registered user is not connected, the message will be "
"stored offline in assumption that offline storage (see 'mod_offline') "
"stored offline in assumption that offline storage (see _`mod_offline`_) "
"is enabled."),
"- example.org/announce/online (example.org/announce/all-hosts/online)::",
?T("The message is sent to all connected users. If the user is "
@@ -965,20 +965,20 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
+2 -2
View File
@@ -469,8 +469,8 @@ mod_doc() ->
"[XEP-0398: User Avatar to vCard-Based Avatars Conversion]."), "",
?T("Also, the module supports conversion between avatar "
"image formats on the fly."), "",
?T("The module depends on 'mod_vcard', 'mod_vcard_xupdate' and "
"'mod_pubsub'.")],
?T("The module depends on _`mod_vcard`_, _`mod_vcard_xupdate`_ and "
"_`mod_pubsub`_.")],
opts =>
[{convert,
#{value => "{From: To}",
+6 -6
View File
@@ -240,27 +240,27 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{ram_db_type,
#{value => "mnesia | sql | redis",
desc =>
?T("Same as 'default_ram_db' but applied to this module only.")}},
?T("Same as _`default_ram_db`_ but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
["listen:",
" -",
+19 -7
View File
@@ -49,7 +49,8 @@
handle_cast/2, terminate/2, code_change/3]).
-export([user_send_packet/1, user_receive_packet/1,
c2s_presence_in/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
c2s_presence_in/2, c2s_copy_session/2,
mod_opt_type/1, mod_options/1, mod_doc/0]).
-include("logger.hrl").
@@ -274,6 +275,13 @@ c2s_presence_in(C2SState,
C2SState#{caps_resources => NewRs}
end.
-spec c2s_copy_session(ejabberd_c2s:state(), ejabberd_c2s:state())
-> ejabberd_c2s:state().
c2s_copy_session(C2SState, #{caps_resources := Rs}) ->
C2SState#{caps_resources => Rs};
c2s_copy_session(C2SState, _) ->
C2SState.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
@@ -304,6 +312,8 @@ init([Host|_]) ->
caps_stream_features, 75),
ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE,
caps_stream_features, 75),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 75),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_features, 75),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
@@ -341,6 +351,8 @@ terminate(_Reason, State) ->
?MODULE, caps_stream_features, 75),
ejabberd_hooks:delete(s2s_in_post_auth_features, Host,
?MODULE, caps_stream_features, 75),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 75),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, disco_features, 75),
ejabberd_hooks:delete(disco_local_identity, Host,
@@ -595,25 +607,25 @@ mod_doc() ->
"https://xmpp.org/extensions/xep-0115.html"
"[XEP-0115: Entity Capabilities]."),
?T("The main purpose of the module is to provide "
"PEP functionality (see 'mod_pubsub').")],
"PEP functionality (see _`mod_pubsub`_).")],
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
+1 -1
View File
@@ -1564,4 +1564,4 @@ mod_doc() ->
?T("The module provides server configuration functionality via "
"https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. This module requires "
"'mod_adhoc' to be loaded.")}.
"_`mod_adhoc`_ to be loaded.")}.
+157
View File
@@ -0,0 +1,157 @@
%%%----------------------------------------------------------------------
%%% File : mod_conversejs.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Serve simple page for Converse.js XMPP web browser client
%%% Created : 8 Nov 2021 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2021 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_conversejs).
-author('alexey@process-one.net').
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process/2, depends/2,
mod_opt_type/1, mod_options/1, mod_doc/0]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
-include("translate.hrl").
-include("ejabberd_web_admin.hrl").
start(_Host, _Opts) ->
ok.
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
process([], #request{method = 'GET'}) ->
Host = ejabberd_config:get_myname(),
Domain = gen_mod:get_module_opt(Host, ?MODULE, default_domain),
Script = gen_mod:get_module_opt(Host, ?MODULE, conversejs_script),
CSS = gen_mod:get_module_opt(Host, ?MODULE, conversejs_css),
Init = [{<<"discover_connection_methods">>, false},
{<<"jid">>, Domain},
{<<"default_domain">>, Domain},
{<<"domain_placeholder">>, Domain},
{<<"view_mode">>, <<"fullscreen">>}],
Init2 =
case gen_mod:get_module_opt(Host, ?MODULE, websocket_url) of
undefined -> Init;
WSURL -> [{<<"websocket_url">>, WSURL} | Init]
end,
Init3 =
case gen_mod:get_module_opt(Host, ?MODULE, bosh_service_url) of
undefined -> Init2;
BoshURL -> [{<<"bosh_service_url">>, BoshURL} | Init2]
end,
{200, [html],
[<<"<!DOCTYPE html>">>,
<<"<html>">>,
<<"<head>">>,
<<"<meta charset='utf-8'>">>,
<<"<link rel='stylesheet' type='text/css' media='screen' href='">>,
fxml:crypt(CSS), <<"'>">>,
<<"<script src='">>, fxml:crypt(Script), <<"' charset='utf-8'></script>">>,
<<"</head>">>,
<<"<body>">>,
<<"<script>">>,
<<"converse.initialize(">>, jiffy:encode({Init3}), <<");">>,
<<"</script>">>,
<<"</body>">>,
<<"</html>">>]};
process(_, _) ->
ejabberd_web:error(not_found).
mod_opt_type(bosh_service_url) ->
econf:either(undefined, econf:binary());
mod_opt_type(websocket_url) ->
econf:either(undefined, econf:binary());
mod_opt_type(conversejs_script) ->
econf:binary();
mod_opt_type(conversejs_css) ->
econf:binary();
mod_opt_type(default_domain) ->
econf:binary().
mod_options(_) ->
[{bosh_service_url, undefined},
{websocket_url, undefined},
{default_domain, ejabberd_config:get_myname()},
{conversejs_script, <<"https://cdn.conversejs.org/dist/converse.min.js">>},
{conversejs_css, <<"https://cdn.conversejs.org/dist/converse.min.css">>}].
mod_doc() ->
#{desc =>
[?T("This module serves a simple page for the "
"https://conversejs.org/[Converse] XMPP web browser client."), "",
?T("This module is available since ejabberd 21.12."), "",
?T("To use this module, in addition to adding it to the 'modules' "
"section, you must also enable it in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers]."), "",
?T("You must also setup either the option 'websocket_url' or 'bosh_service_url'."), "",
?T("By default, the options 'conversejs_css' and 'conversejs_script'"
" point to the public Converse.js client. Alternatively, you can"
" host the client locally using _`mod_http_fileserver`_.")
],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /websocket: ejabberd_http_ws",
" /conversejs: mod_conversejs",
"",
"modules:",
" mod_conversejs:",
" websocket_url: \"ws://example.org:5280/websocket\""],
opts =>
[{websocket_url,
#{value => ?T("WebsocketURL"),
desc =>
?T("A websocket URL to which Converse.js can connect to.")}},
{bosh_service_url,
#{value => ?T("BoshURL"),
desc =>
?T("BOSH service URL to which Converse.js can connect to.")}},
{default_domain,
#{value => ?T("Domain"),
desc =>
?T("Specify a domain to act as the default for user JIDs. "
"The default value is the first domain defined in the "
"ejabberd configuration file.")}},
{conversejs_script,
#{value => ?T("URL"),
desc =>
?T("Converse.js main script URL.")}},
{conversejs_css,
#{value => ?T("URL"),
desc =>
?T("Converse.js CSS URL.")}}]
}.
+41
View File
@@ -0,0 +1,41 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_conversejs_opt).
-export([bosh_service_url/1]).
-export([conversejs_css/1]).
-export([conversejs_script/1]).
-export([default_domain/1]).
-export([websocket_url/1]).
-spec bosh_service_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
bosh_service_url(Opts) when is_map(Opts) ->
gen_mod:get_opt(bosh_service_url, Opts);
bosh_service_url(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, bosh_service_url).
-spec conversejs_css(gen_mod:opts() | global | binary()) -> binary().
conversejs_css(Opts) when is_map(Opts) ->
gen_mod:get_opt(conversejs_css, Opts);
conversejs_css(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_css).
-spec conversejs_script(gen_mod:opts() | global | binary()) -> binary().
conversejs_script(Opts) when is_map(Opts) ->
gen_mod:get_opt(conversejs_script, Opts);
conversejs_script(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, conversejs_script).
-spec default_domain(gen_mod:opts() | global | binary()) -> binary().
default_domain(Opts) when is_map(Opts) ->
gen_mod:get_opt(default_domain, Opts);
default_domain(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, default_domain).
-spec websocket_url(gen_mod:opts() | global | binary()) -> 'undefined' | binary().
websocket_url(Opts) when is_map(Opts) ->
gen_mod:get_opt(websocket_url, Opts);
websocket_url(Host) ->
gen_mod:get_module_opt(Host, mod_conversejs, websocket_url).
+1 -1
View File
@@ -95,7 +95,7 @@ mod_doc() ->
?T("WARNING: Security issue: Namespace delegation gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust the component."), "",
?T("NOTE: This module is complementary to 'mod_privilege' but can "
?T("NOTE: This module is complementary to _`mod_privilege`_ but can "
"also be used separately.")],
opts =>
[{namespaces,
+2 -2
View File
@@ -464,11 +464,11 @@ mod_doc() ->
" -",
" modules: all",
" name: abuse-addresses",
" urls: [mailto:abuse@shakespeare.lit]",
" urls: [\"mailto:abuse@shakespeare.lit\"]",
" -",
" modules: [mod_muc]",
" name: \"Web chatroom logs\"",
" urls: [http://www.example.org/muc-logs]",
" urls: [\"http://www.example.org/muc-logs\"]",
" -",
" modules: [mod_disco]",
" name: feedback-addresses",
+1 -1
View File
@@ -107,7 +107,7 @@ c2s_stream_started(#{ip := {Addr, _}} = State, _) ->
start(Host, Opts) ->
catch ets:new(failed_auth, [named_table, public,
{heir, erlang:group_leader(), none}]),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
+13 -3
View File
@@ -527,10 +527,20 @@ mod_doc() ->
[?T("This module provides a ReST API to call ejabberd commands "
"using JSON data."), "",
?T("To use this module, in addition to adding it to the 'modules' "
"section, you must also add it to 'request_handlers' of some "
"listener."), "",
"section, you must also enable it in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers]."), "",
?T("To use a specific API version N, when defining the URL path "
"in the request_handlers, add a 'vN'. "
"For example: '/api/v2: mod_http_api'"), "",
?T("To run a command, send a POST request to the corresponding "
"URL: 'http://localhost:5280/api/<command_name>'")]}.
"URL: 'http://localhost:5280/api/<command_name>'")],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /api: mod_http_api",
"",
"modules:",
" mod_http_api: {}"]}.
+7 -5
View File
@@ -232,8 +232,9 @@ mod_doc() ->
"[XEP-0363: HTTP File Upload]. If the request is accepted, "
"the client receives a URL for uploading the file and "
"another URL from which that file can later be downloaded."), "",
?T("In order to use this module, it must be configured as "
"a 'request_handler' for 'ejabberd_http' listener.")],
?T("In order to use this module, it must be enabled "
"in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers].")],
opts =>
[{host,
#{desc => ?T("Deprecated. Use 'hosts' instead.")}},
@@ -320,17 +321,18 @@ mod_doc() ->
"used for file uploads. The keyword @HOST@ is replaced "
"with the virtual host name. NOTE: different virtual "
"hosts cannot use the same PUT URL. "
"The default value is \"https://@HOST@:5443\".")}},
"The default value is \"https://@HOST@:5443/upload\".")}},
{get_url,
#{value => ?T("URL"),
desc =>
?T("This option specifies the initial part of the GET URLs "
"used for downloading the files. By default, it is set "
"used for downloading the files. The default value is 'undefined'. "
"When this option is 'undefined', this option is set "
"to the same value as 'put_url'. The keyword @HOST@ is "
"replaced with the virtual host name. NOTE: if GET requests "
"are handled by 'mod_http_upload', the 'get_url' must match the "
"'put_url'. Setting it to a different value only makes "
"sense if an external web server or 'mod_http_fileserver' "
"sense if an external web server or _`mod_http_fileserver`_ "
"is used to serve the uploaded files.")}},
{service_url,
#{desc => ?T("Deprecated.")}},
+11 -12
View File
@@ -27,7 +27,6 @@
-author('holger@zedat.fu-berlin.de').
-define(TIMEOUT, timer:hours(24)).
-define(INITIAL_TIMEOUT, timer:minutes(10)).
-define(FORMAT(Error), file:format_error(Error)).
-behaviour(gen_server).
@@ -64,7 +63,7 @@
max_days :: pos_integer() | infinity,
docroot :: binary(),
disk_usage = #{} :: disk_usage(),
timers :: [timer:tref()]}).
timer :: reference() | undefined}).
-type disk_usage() :: #{{binary(), binary()} => non_neg_integer()}.
-type state() :: #state{}.
@@ -166,12 +165,11 @@ init([ServerHost|_]) ->
DocRoot1 = mod_http_upload_opt:docroot(ServerHost),
DocRoot2 = mod_http_upload:expand_home(str:strip(DocRoot1, right, $/)),
DocRoot3 = mod_http_upload:expand_host(DocRoot2, ServerHost),
Timers = if MaxDays == infinity -> [];
true ->
{ok, T1} = timer:send_after(?INITIAL_TIMEOUT, sweep),
{ok, T2} = timer:send_interval(?TIMEOUT, sweep),
[T1, T2]
end,
Timer = if MaxDays == infinity -> undefined;
true ->
Timeout = p1_rand:uniform(?TIMEOUT div 2),
erlang:send_after(Timeout, self(), sweep)
end,
ejabberd_hooks:add(http_upload_slot_request, ServerHost, ?MODULE,
handle_slot_request, 50),
{ok, #state{server_host = ServerHost,
@@ -179,7 +177,7 @@ init([ServerHost|_]) ->
access_hard_quota = AccessHardQuota,
max_days = MaxDays,
docroot = DocRoot3,
timers = Timers}}.
timer = Timer}}.
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
handle_call(Request, From, State) ->
@@ -249,6 +247,7 @@ handle_info(sweep, #state{server_host = ServerHost,
max_days = MaxDays} = State)
when is_integer(MaxDays), MaxDays > 0 ->
?DEBUG("Got 'sweep' message for ~ts", [ServerHost]),
Timer = erlang:send_after(?TIMEOUT, self(), sweep),
case file:list_dir(DocRoot) of
{ok, Entries} ->
BackThen = secs_since_epoch() - (MaxDays * 86400),
@@ -264,17 +263,17 @@ handle_info(sweep, #state{server_host = ServerHost,
?ERROR_MSG("Cannot open document root ~ts: ~ts",
[DocRoot, ?FORMAT(Error)])
end,
{noreply, State};
{noreply, State#state{timer = Timer}};
handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, timers = Timers}) ->
terminate(Reason, #state{server_host = ServerHost, timer = Timer}) ->
?DEBUG("Stopping upload quota process for ~ts: ~p", [ServerHost, Reason]),
ejabberd_hooks:delete(http_upload_slot_request, ServerHost, ?MODULE,
handle_slot_request, 50),
lists:foreach(fun timer:cancel/1, Timers).
misc:cancel_timer(Timer).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) ->
+5 -5
View File
@@ -344,20 +344,20 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
+9 -8
View File
@@ -28,6 +28,7 @@
-protocol({xep, 313, '0.6.1'}).
-protocol({xep, 334, '0.2'}).
-protocol({xep, 359, '0.5.0'}).
-protocol({xep, 441, '0.2.0'}).
-behaviour(gen_mod).
@@ -148,7 +149,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(check_create_room, Host, ?MODULE,
check_create_room, 50)
end,
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
ok;
Err ->
Err
@@ -1456,7 +1457,7 @@ mod_doc() ->
#{value => "true | false",
desc =>
?T("This option determines how ejabberd's "
"stream management code (see 'mod_stream_mgmt') "
"stream management code (see _`mod_stream_mgmt`_) "
"handles unacknowledged messages when the "
"connection is lost. Usually, such messages are "
"either bounced or resent. However, neither is "
@@ -1495,28 +1496,28 @@ mod_doc() ->
#{value => "true | false",
desc =>
?T("Whether to destroy message archive of a room "
"(see 'mod_muc') when it gets destroyed. "
"(see _`mod_muc`_) when it gets destroyed. "
"The default value is 'true'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}},
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}},
{user_mucsub_from_muc_archive,
#{value => "true | false",
desc =>
+5 -15
View File
@@ -72,7 +72,7 @@ remove_from_archive(LUser, LServer, WithJid) ->
end.
delete_old_messages(ServerHost, TimeStamp, Type) ->
TS = now_to_usec(TimeStamp),
TS = misc:now_to_usec(TimeStamp),
case Type of
all ->
ejabberd_sql:sql_query(
@@ -315,7 +315,7 @@ export(_Server) ->
id = _ID, timestamp = TS, peer = Peer,
type = Type, nick = Nick, packet = Pkt})
when LServer == Host ->
TStmp = now_to_usec(TS),
TStmp = misc:now_to_usec(TS),
SUser = case Type of
chat -> LUser;
groupchat -> jid:encode({LUser, LServer, <<>>})
@@ -372,16 +372,6 @@ is_empty_for_room(LServer, LName, LHost) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
usec_to_now(Int) ->
Secs = Int div 1000000,
USec = Int rem 1000000,
MSec = Secs div 1000000,
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
Start = proplists:get_value(start, MAMQuery),
End = proplists:get_value('end', MAMQuery),
@@ -432,14 +422,14 @@ make_sql_query(User, LServer, MAMQuery, RSM, ExtraUsernames) ->
StartClause = case Start of
{_, _, _} ->
[<<" and timestamp >= ">>,
integer_to_binary(now_to_usec(Start))];
integer_to_binary(misc:now_to_usec(Start))];
_ ->
[]
end,
EndClause = case End of
{_, _, _} ->
[<<" and timestamp <= ">>,
integer_to_binary(now_to_usec(End))];
integer_to_binary(misc:now_to_usec(End))];
_ ->
[]
end,
@@ -526,7 +516,7 @@ make_archive_el(User, TS, XML, Peer, Kind, Nick, MsgType, JidRequestor, JidArchi
TSInt ->
try jid:decode(Peer) of
PeerJID ->
Now = usec_to_now(TSInt),
Now = misc:usec_to_now(TSInt),
PeerLJID = jid:tolower(PeerJID),
T = case Kind of
<<"">> -> chat;
+3 -3
View File
@@ -106,12 +106,12 @@ mod_doc() ->
"experimental feature, updated in 19.02, and is not "
"yet ready to use in production. It's asserted that "
"the MIX protocol is going to replace the MUC protocol "
"in the future (see 'mod_muc')."), "",
"in the future (see _`mod_muc`_)."), "",
?T("To learn more about how to use that feature, you can refer to "
"our tutorial: https://docs.ejabberd.im/tutorials/mix-010/"
"[Getting started with XEP-0369: Mediated Information "
"eXchange (MIX) v0.1]."), "",
?T("The module depends on 'mod_mam'.")],
?T("The module depends on _`mod_mam`_.")],
opts =>
[{access_create,
#{value => ?T("AccessName"),
@@ -136,7 +136,7 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}}]}.
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}}]}.
-spec route(stanza()) -> ok.
route(#iq{} = IQ) ->
+5 -5
View File
@@ -120,23 +120,23 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
-spec bounce_sm_packet({term(), stanza()}) -> {term(), stanza()}.
bounce_sm_packet({_, #message{to = #jid{lresource = <<>>} = To,
+10 -9
View File
@@ -278,8 +278,9 @@ listen_options() ->
%%%===================================================================
mod_doc() ->
#{desc =>
?T("This module adds support for the MQTT protocol "
"version '3.1.1' and '5.0'. Remember to configure "
?T("This module adds "
"https://docs.ejabberd.im/admin/guide/mqtt/[support for the MQTT] "
"protocol version '3.1.1' and '5.0'. Remember to configure "
"'mod_mqtt' in 'modules' and 'listen' sections."),
opts =>
[{access_subscribe,
@@ -326,37 +327,37 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, "
?T("Same as top-level _`queue_type`_ option, "
"but applied to this module only.")}},
{ram_db_type,
#{value => "mnesia",
desc =>
?T("Same as top-level 'default_ram_db' option, "
?T("Same as top-level _`default_ram_db`_ option, "
"but applied to this module only.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, "
?T("Same as top-level _`default_db`_ option, "
"but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, "
?T("Same as top-level _`use_cache`_ option, "
"but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, "
?T("Same as top-level _`cache_size`_ option, "
"but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, "
?T("Same as top-level _`cache_missed`_ option, "
"but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, "
?T("Same as top-level _`cache_life_time`_ option, "
"but applied to this module only.")}}]}.
%%%===================================================================
+17 -8
View File
@@ -40,6 +40,7 @@
room_destroyed/4,
store_room/4,
store_room/5,
store_changes/4,
restore_room/3,
forget_room/3,
create_room/3,
@@ -91,6 +92,7 @@
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok.
-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
-callback store_changes(binary(), binary(), binary(), list()) -> {atomic, any()}.
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
@@ -111,7 +113,8 @@
-callback get_subscribed_rooms(binary(), binary(), jid()) ->
{ok, [{jid(), binary(), [binary()]}]} | {error, db_failure}.
-optional_callbacks([get_subscribed_rooms/3]).
-optional_callbacks([get_subscribed_rooms/3,
store_changes/4]).
%%====================================================================
%% API
@@ -313,6 +316,11 @@ store_room(ServerHost, Host, Name, Opts, ChangesHints) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_room(LServer, Host, Name, Opts, ChangesHints).
store_changes(ServerHost, Host, Name, ChangesHints) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_changes(LServer, Host, Name, ChangesHints).
restore_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
@@ -570,7 +578,7 @@ unhibernate_room(ServerHost, Host, Room) ->
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
Proc = procname(ServerHost, {Room, Host}),
case ?GEN_SERVER:call(Proc, {unhibernate, Room, Host}) of
case ?GEN_SERVER:call(Proc, {unhibernate, Room, Host}, 20000) of
{ok, _} = R -> R;
_ -> error
end;
@@ -1358,19 +1366,19 @@ mod_doc() ->
desc =>
?T("To configure who is allowed to create new rooms at the "
"Multi-User Chat service, this option can be used. "
"By default any account in the local ejabberd server is "
"The default value is 'all', which means everyone is "
"allowed to create rooms.")}},
{access_persistent,
#{value => ?T("AccessName"),
desc =>
?T("To configure who is allowed to modify the 'persistent' room option. "
"By default any account in the local ejabberd server is allowed to "
"The default value is 'all', which means everyone is allowed to "
"modify that option.")}},
{access_mam,
#{value => ?T("AccessName"),
desc =>
?T("To configure who is allowed to modify the 'mam' room option. "
"By default any account in the local ejabberd server is allowed to "
"The default value is 'all', which means everyone is allowed to "
"modify that option.")}},
{access_register,
#{value => ?T("AccessName"),
@@ -1486,7 +1494,8 @@ mod_doc() ->
?T("This option defines after how many users in the room, "
"it is considered overcrowded. When a MUC room is considered "
"overcrowed, presence broadcasts are limited to reduce load, "
"traffic and excessive presence \"storm\" received by participants.")}},
"traffic and excessive presence \"storm\" received by participants. "
"The default value is '1000'.")}},
{min_message_interval,
#{value => ?T("Number"),
desc =>
@@ -1517,7 +1526,7 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{regexp_room_id,
#{value => "string()",
desc =>
@@ -1634,7 +1643,7 @@ mod_doc() ->
{logging,
#{value => "true | false",
desc =>
?T("The public messages are logged using 'mod_muc_log'. "
?T("The public messages are logged using _`mod_muc_log`_. "
"The default value is 'false'.")}},
{members_by_default,
#{value => "true | false",
+6 -3
View File
@@ -57,7 +57,7 @@
%%----------------------------
start(Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
@@ -710,7 +710,7 @@ create_room_with_opts(Name1, Host1, ServerHost1, CustomRoomOpts) ->
maybe_store_room(ServerHost, Host, Name, RoomOpts) ->
case proplists:get_bool(persistent, RoomOpts) of
true ->
{atomic, ok} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
{atomic, _} = mod_muc:store_room(ServerHost, Host, Name, RoomOpts),
ok;
false ->
ok
@@ -1166,6 +1166,7 @@ change_option(Option, Value, Config) ->
anonymous -> Config#config{anonymous = Value};
captcha_protected -> Config#config{captcha_protected = Value};
description -> Config#config{description = Value};
lang -> Config#config{lang = Value};
logging -> Config#config{logging = Value};
mam -> Config#config{mam = Value};
max_users -> Config#config{max_users = Value};
@@ -1178,8 +1179,10 @@ change_option(Option, Value, Config) ->
presence_broadcast -> Config#config{presence_broadcast = Value};
public -> Config#config{public = Value};
public_list -> Config#config{public_list = Value};
pubsub -> Config#config{pubsub = Value};
title -> Config#config{title = Value};
vcard -> Config#config{vcard = Value};
vcard_xupdate -> Config#config{vcard_xupdate = Value};
voice_request_min_interval -> Config#config{voice_request_min_interval = Value}
end.
@@ -1410,4 +1413,4 @@ mod_doc() ->
[?T("This module provides commands to administer local MUC "
"services and their MUC rooms. It also provides simple "
"WebAdmin pages to view the existing rooms."), "",
?T("This module depends on 'mod_muc'.")]}.
?T("This module depends on _`mod_muc`_.")]}.
+1 -1
View File
@@ -1021,7 +1021,7 @@ mod_doc() ->
?T("- URLs on messages and subjects are converted to hyperlinks."), "",
?T("- Timezone used on timestamps is shown on the log files."), "",
?T("- A custom link can be added on top of each page."), "",
?T("The module depends on 'mod_muc'.")],
?T("The module depends on _`mod_muc`_.")],
opts =>
[{access_log,
#{value => ?T("AccessName"),
+645 -117
View File
File diff suppressed because it is too large Load Diff
+19 -5
View File
@@ -29,7 +29,8 @@
-behaviour(mod_muc_room).
%% API
-export([init/2, store_room/5, restore_room/3, forget_room/3,
-export([init/2, store_room/5, store_changes/4,
restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4,
import/3, export/1]).
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
@@ -83,6 +84,12 @@ store_room(LServer, Host, Name, Opts, ChangesHints) ->
end,
ejabberd_sql:sql_transaction(LServer, F).
store_changes(LServer, Host, Name, Changes) ->
F = fun () ->
[change_room(Host, Name, Change) || Change <- Changes]
end,
ejabberd_sql:sql_transaction(LServer, F).
change_room(Host, Room, {add_subscription, JID, Nick, Nodes}) ->
SJID = jid:encode(JID),
SNodes = misc:term_to_expr(Nodes),
@@ -185,13 +192,20 @@ get_rooms(LServer, Host) ->
{selected, Subs} ->
SubsD = lists:foldl(
fun({Room, Jid, Nick, Nodes}, Dict) ->
dict:append(Room, {jid:decode(Jid),
Nick, ejabberd_sql:decode_term(Nodes)}, Dict)
end, dict:new(), Subs),
Sub = {jid:decode(Jid),
Nick, ejabberd_sql:decode_term(Nodes)},
maps:update_with(
Room,
fun(SubAcc) ->
[Sub | SubAcc]
end,
[Sub],
Dict)
end, maps:new(), Subs),
lists:map(
fun({Room, Opts}) ->
OptsD = ejabberd_sql:decode_term(Opts),
OptsD2 = case {dict:find(Room, SubsD), lists:keymember(subscribers, 1, OptsD)} of
OptsD2 = case {maps:find(Room, SubsD), lists:keymember(subscribers, 1, OptsD)} of
{_, true} ->
store_room(LServer, Host, Room, mod_muc:opts_to_binary(OptsD), undefined),
OptsD;
+134 -288
View File
@@ -35,7 +35,7 @@
%% API
-export([start/2, stop/1, reload/3,
user_send_packet/1]).
user_send_packet/1]).
%% gen_server callbacks
-export([init/1, handle_info/2, handle_call/3,
@@ -51,11 +51,6 @@
response,
ts :: integer()}).
-record(dest, {jid_string :: binary() | none,
jid_jid :: jid() | undefined,
type :: bcc | cc | noreply | ofrom | replyroom | replyto | to,
address :: address()}).
-type limit_value() :: {default | custom, integer()}.
-record(limits, {message :: limit_value(),
presence :: limit_value()}).
@@ -63,14 +58,6 @@
-record(service_limits, {local :: #limits{},
remote :: #limits{}}).
-type routing() :: route_single | {route_multicast, binary(), #service_limits{}}.
-record(group, {server :: binary(),
dests :: [#dest{}],
multicast :: routing() | undefined,
others :: [address()],
addresses :: [address()]}).
-record(state, {lserver :: binary(),
lservice :: binary(),
access :: atom(),
@@ -117,7 +104,7 @@ reload(LServerS, NewOpts, OldOpts) ->
user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
case xmpp:get_subtag(Packet, #addresses{}) of
#addresses{list = Addresses} ->
{ToDeliver, _Delivereds} = split_addresses_todeliver(Addresses),
{CC, BCC, _Invalid, _Delivered} = partition_addresses(Addresses),
NewState =
lists:foldl(
fun(Address, St) ->
@@ -138,7 +125,7 @@ user_send_packet({#presence{} = Packet, C2SState} = Acc) ->
undefined ->
St
end
end, C2SState, ToDeliver),
end, C2SState, CC ++ BCC),
{Packet, NewState};
false ->
Acc
@@ -308,19 +295,10 @@ iq_vcard(Lang, State) ->
%%%-------------------------
-spec route_trusted(binary(), binary(), jid(), [jid()], stanza()) -> 'ok'.
route_trusted(LServiceS, LServerS, FromJID,
Destinations, Packet) ->
Packet_stripped = Packet,
Delivereds = [],
Dests2 = lists:map(
fun(D) ->
#dest{jid_string = jid:encode(D),
jid_jid = D, type = bcc,
address = #address{type = bcc, jid = D}}
end, Destinations),
Groups = group_dests(Dests2),
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped).
route_trusted(LServiceS, LServerS, FromJID, Destinations, Packet) ->
Addresses = [#address{type = bcc, jid = D} || D <- Destinations],
Groups = group_by_destinations(Addresses, #{}),
route_grouped(LServerS, LServiceS, FromJID, Groups, [], Packet).
-spec route_untrusted(binary(), binary(), atom(), #service_limits{}, stanza()) -> 'ok'.
route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
@@ -356,50 +334,88 @@ route_untrusted(LServiceS, LServerS, Access, SLimits, Packet) ->
route_untrusted2(LServiceS, LServerS, Access, SLimits, Packet) ->
FromJID = xmpp:get_from(Packet),
ok = check_access(LServerS, Access, FromJID),
{ok, Packet_stripped, Addresses} = strip_addresses_element(Packet),
{To_deliver, Delivereds} = split_addresses_todeliver(Addresses),
Dests = convert_dest_record(To_deliver),
{Dests2, Not_jids} = split_dests_jid(Dests),
report_not_jid(FromJID, Packet, Not_jids),
ok = check_limit_dests(SLimits, FromJID, Packet, Dests2),
Groups = group_dests(Dests2),
{ok, PacketStripped, Addresses} = strip_addresses_element(Packet),
{CC, BCC, NotJids, Rest} = partition_addresses(Addresses),
report_not_jid(FromJID, Packet, NotJids),
ok = check_limit_dests(SLimits, FromJID, Packet, length(CC) + length(BCC)),
Groups0 = group_by_destinations(CC, #{}),
Groups = group_by_destinations(BCC, Groups0),
ok = check_relay(FromJID#jid.server, LServerS, Groups),
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped).
route_grouped(LServerS, LServiceS, FromJID, Groups, Rest, PacketStripped).
-spec route_common(binary(), binary(), jid(), [#group{}],
[address()], stanza()) -> 'ok'.
route_common(LServerS, LServiceS, FromJID, Groups,
Delivereds, Packet_stripped) ->
Groups2 = look_cached_servers(LServerS, LServiceS, Groups),
Groups3 = build_others_xml(Groups2),
Groups4 = add_addresses(Delivereds, Groups3),
AGroups = decide_action_groups(Groups4),
act_groups(FromJID, Packet_stripped, LServiceS,
AGroups).
-spec mark_as_delivered([address()]) -> [address()].
mark_as_delivered(Addresses) ->
[A#address{delivered = true} || A <- Addresses].
-spec act_groups(jid(), stanza(), binary(), [{routing(), #group{}}]) -> 'ok'.
act_groups(FromJID, Packet_stripped, LServiceS, AGroups) ->
-spec route_individual(jid(), [address()], [address()], [address()], stanza()) -> ok.
route_individual(From, CC, BCC, Other, Packet) ->
CCDelivered = mark_as_delivered(CC),
Addresses = CCDelivered ++ Other,
PacketWithAddresses = xmpp:append_subtags(Packet, [#addresses{list = Addresses}]),
lists:foreach(
fun(AGroup) ->
perform(FromJID, Packet_stripped, LServiceS,
AGroup)
end, AGroups).
-spec perform(jid(), stanza(), binary(),
{routing(), #group{}}) -> 'ok'.
perform(From, Packet, _,
{route_single, Group}) ->
fun(#address{jid = To}) ->
ejabberd_router:route(xmpp:set_from_to(PacketWithAddresses, From, To))
end, CC),
lists:foreach(
fun(ToUser) ->
Group_others = strip_other_bcc(ToUser, Group#group.others),
route_packet(From, ToUser, Packet,
Group_others, Group#group.addresses)
end, Group#group.dests);
perform(From, Packet, _,
{{route_multicast, JID, RLimits}, Group}) ->
route_packet_multicast(From, JID, Packet,
Group#group.dests, Group#group.addresses, RLimits).
fun(#address{jid = To} = Address) ->
Packet2 = case Addresses of
[] ->
Packet;
_ ->
xmpp:append_subtags(Packet, [#addresses{list = [Address | Addresses]}])
end,
ejabberd_router:route(xmpp:set_from_to(Packet2, From, To))
end, BCC).
-spec route_chunk(jid(), jid(), stanza(), [address()]) -> ok.
route_chunk(From, To, Packet, Addresses) ->
PacketWithAddresses = xmpp:append_subtags(Packet, [#addresses{list = Addresses}]),
ejabberd_router:route(xmpp:set_from_to(PacketWithAddresses, From, To)).
-spec route_in_chunks(jid(), jid(), stanza(), integer(), [address()], [address()], [address()]) -> ok.
route_in_chunks(_From, _To, _Packet, _Limit, [], [], _) ->
ok;
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses) when length(CC) > Limit ->
{Chunk, Rest} = lists:split(Limit, CC),
route_chunk(From, To, Packet, Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, Rest, BCC, RestOfAddresses);
route_in_chunks(From, To, Packet, Limit, [], BCC, RestOfAddresses) when length(BCC) > Limit ->
{Chunk, Rest} = lists:split(Limit, BCC),
route_chunk(From, To, Packet, Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, [], Rest, RestOfAddresses);
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses) when length(BCC) + length(CC) > Limit ->
{Chunk, Rest} = lists:split(Limit - length(CC), BCC),
route_chunk(From, To, Packet, CC ++ Chunk ++ RestOfAddresses),
route_in_chunks(From, To, Packet, Limit, [], Rest, RestOfAddresses);
route_in_chunks(From, To, Packet, _Limit, CC, BCC, RestOfAddresses) ->
route_chunk(From, To, Packet, CC ++ BCC ++ RestOfAddresses).
-spec route_multicast(jid(), jid(), [address()], [address()], [address()], stanza(), #limits{}) -> ok.
route_multicast(From, To, CC, BCC, RestOfAddresses, Packet, Limits) ->
{_Type, Limit} = get_limit_number(element(1, Packet),
Limits),
route_in_chunks(From, To, Packet, Limit, CC, BCC, RestOfAddresses).
-spec route_grouped(binary(), binary(), jid(), #{}, [address()], stanza()) -> ok.
route_grouped(LServer, LService, From, Groups, RestOfAddresses, Packet) ->
maps:fold(
fun(Server, {CC, BCC}, _) ->
OtherCC = maps:fold(
fun(Server2, _, Res) when Server2 == Server ->
Res;
(_, {CC2, _}, Res) ->
mark_as_delivered(CC2) ++ Res
end, [], Groups),
case search_server_on_cache(Server,
LServer, LService,
{?MAXTIME_CACHE_POSITIVE,
?MAXTIME_CACHE_NEGATIVE}) of
route_single ->
route_individual(From, CC, BCC, OtherCC ++ RestOfAddresses, Packet);
{route_multicast, Service, Limits} ->
route_multicast(From, Service, CC, BCC, OtherCC ++ RestOfAddresses, Packet, Limits)
end
end, ok, Groups).
%%%-------------------------
%%% Check access permission
@@ -425,245 +441,89 @@ strip_addresses_element(Packet) ->
throw(eadsele)
end.
%%%-------------------------
%%% Strip third-party bcc 'addresses'
%%%-------------------------
strip_other_bcc(#dest{jid_jid = ToUserJid}, Group_others) ->
lists:filter(
fun(#address{jid = JID, type = Type}) ->
case {JID, Type} of
{ToUserJid, bcc} -> true;
{_, bcc} -> false;
_ -> true
end
end,
Group_others).
%%%-------------------------
%%% Split Addresses
%%%-------------------------
-spec split_addresses_todeliver([address()]) -> {[address()], [address()]}.
split_addresses_todeliver(Addresses) ->
lists:partition(
fun(#address{delivered = true}) ->
false;
(#address{type = Type}) ->
case Type of
to -> true;
cc -> true;
bcc -> true;
_ -> false
end
end, Addresses).
partition_addresses(Addresses) ->
lists:foldl(
fun(#address{delivered = true} = A, {C, B, I, D}) ->
{C, B, I, [A | D]};
(#address{type = T, jid = undefined} = A, {C, B, I, D})
when T == to; T == cc; T == bcc ->
{C, B, [A | I], D};
(#address{type = T} = A, {C, B, I, D})
when T == to; T == cc ->
{[A | C], B, I, D};
(#address{type = bcc} = A, {C, B, I, D}) ->
{C, [A | B], I, D};
(A, {C, B, I, D}) ->
{C, B, I, [A | D]}
end, {[], [], [], []}, Addresses).
%%%-------------------------
%%% Check does not exceed limit of destinations
%%%-------------------------
-spec check_limit_dests(#service_limits{}, jid(), stanza(), [address()]) -> ok.
check_limit_dests(SLimits, FromJID, Packet,
Addresses) ->
-spec check_limit_dests(#service_limits{}, jid(), stanza(), integer()) -> ok.
check_limit_dests(SLimits, FromJID, Packet, NumOfAddresses) ->
SenderT = sender_type(FromJID),
Limits = get_slimit_group(SenderT, SLimits),
Type_of_stanza = type_of_stanza(Packet),
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
Limits),
case length(Addresses) > Limit_number of
StanzaType = type_of_stanza(Packet),
{_Type, Limit} = get_limit_number(StanzaType,
Limits),
case NumOfAddresses > Limit of
false -> ok;
true -> throw(etoorec)
end.
%%%-------------------------
%%% Convert Destination XML to record
%%%-------------------------
-spec convert_dest_record([address()]) -> [#dest{}].
convert_dest_record(Addrs) ->
lists:map(
fun(#address{jid = undefined, type = Type} = Addr) ->
#dest{jid_string = none,
type = Type, address = Addr};
(#address{jid = JID, type = Type} = Addr) ->
#dest{jid_string = jid:encode(JID), jid_jid = JID,
type = Type, address = Addr}
end, Addrs).
%%%-------------------------
%%% Split destinations by existence of JID
%%% and send error messages for other dests
%%%-------------------------
-spec split_dests_jid([#dest{}]) -> {[#dest{}], [#dest{}]}.
split_dests_jid(Dests) ->
lists:partition(fun (Dest) ->
case Dest#dest.jid_string of
none -> false;
_ -> true
end
end,
Dests).
-spec report_not_jid(jid(), stanza(), [#dest{}]) -> any().
report_not_jid(From, Packet, Dests) ->
Dests2 = [fxml:element_to_binary(xmpp:encode(Dest#dest.address))
|| Dest <- Dests],
[route_error(
xmpp:set_from_to(Packet, From, From), jid_malformed,
str:format(?T("This service can not process the address: ~s"), [D]))
|| D <- Dests2].
-spec report_not_jid(jid(), stanza(), [address()]) -> any().
report_not_jid(From, Packet, Addresses) ->
lists:foreach(
fun(Address) ->
route_error(
xmpp:set_from_to(Packet, From, From), jid_malformed,
str:format(?T("This service can not process the address: ~s"),
[fxml:element_to_binary(xmpp:encode(Address))]))
end, Addresses).
%%%-------------------------
%%% Group destinations by their servers
%%%-------------------------
-spec group_dests([#dest{}]) -> [#group{}].
group_dests(Dests) ->
D = lists:foldl(fun (Dest, Dict) ->
ServerS = (Dest#dest.jid_jid)#jid.server,
dict:append(ServerS, Dest, Dict)
end,
dict:new(), Dests),
Keys = dict:fetch_keys(D),
[#group{server = Key, dests = dict:fetch(Key, D),
addresses = [], others = []}
|| Key <- Keys].
%%%-------------------------
%%% Look for cached responses
%%%-------------------------
look_cached_servers(LServerS, LServiceS, Groups) ->
[look_cached(LServerS, LServiceS, Group) || Group <- Groups].
look_cached(LServerS, LServiceS, G) ->
Maxtime_positive = (?MAXTIME_CACHE_POSITIVE),
Maxtime_negative = (?MAXTIME_CACHE_NEGATIVE),
Cached_response = search_server_on_cache(G#group.server,
LServerS, LServiceS,
{Maxtime_positive,
Maxtime_negative}),
G#group{multicast = Cached_response}.
%%%-------------------------
%%% Build delivered XML element
%%%-------------------------
build_others_xml(Groups) ->
[Group#group{others =
build_other_xml(Group#group.dests)}
|| Group <- Groups].
build_other_xml(Dests) ->
lists:foldl(fun (Dest, R) ->
XML = Dest#dest.address,
case Dest#dest.type of
to -> [add_delivered(XML) | R];
cc -> [add_delivered(XML) | R];
_ -> [XML | R]
end
end,
[], Dests).
-spec add_delivered(address()) -> address().
add_delivered(Addr) ->
Addr#address{delivered = true}.
%%%-------------------------
%%% Add preliminary packets
%%%-------------------------
add_addresses(Delivereds, Groups) ->
Ps = [Group#group.others || Group <- Groups],
add_addresses2(Delivereds, Groups, [], [], Ps).
add_addresses2(_, [], Res, _, []) -> Res;
add_addresses2(Delivereds, [Group | Groups], Res, Pa,
[Pi | Pz]) ->
Addresses = lists:append([Delivereds] ++ Pa ++ Pz),
Group2 = Group#group{addresses = Addresses},
add_addresses2(Delivereds, Groups, [Group2 | Res],
[Pi | Pa], Pz).
%%%-------------------------
%%% Decide action groups
%%%-------------------------
-spec decide_action_groups([#group{}]) -> [{routing(), #group{}}].
decide_action_groups(Groups) ->
[{Group#group.multicast, Group}
|| Group <- Groups].
group_by_destinations(Addrs, Map) ->
lists:foldl(
fun
(#address{type = Type, jid = #jid{lserver = Server}} = Addr, Map2) when Type == to; Type == cc ->
maps:update_with(Server,
fun({CC, BCC}) ->
{[Addr | CC], BCC}
end, {[Addr], []}, Map2);
(#address{type = bcc, jid = #jid{lserver = Server}} = Addr, Map2) ->
maps:update_with(Server,
fun({CC, BCC}) ->
{CC, [Addr | BCC]}
end, {[], [Addr]}, Map2)
end, Map, Addrs).
%%%-------------------------
%%% Route packet
%%%-------------------------
-spec route_packet(jid(), #dest{}, stanza(), [addresses()], [addresses()]) -> 'ok'.
route_packet(From, ToDest, Packet, Others, Addresses) ->
Dests = case ToDest#dest.type of
bcc -> [];
_ -> [ToDest]
end,
route_packet2(From, ToDest#dest.jid_string, Dests,
Packet, {Others, Addresses}).
-spec route_packet_multicast(jid(), binary(), stanza(), [#dest{}], [address()], #limits{}) -> 'ok'.
route_packet_multicast(From, ToS, Packet, Dests,
Addresses, Limits) ->
Type_of_stanza = type_of_stanza(Packet),
{_Type, Limit_number} = get_limit_number(Type_of_stanza,
Limits),
Fragmented_dests = fragment_dests(Dests, Limit_number),
lists:foreach(fun(DFragment) ->
route_packet2(From, ToS, DFragment, Packet,
Addresses)
end, Fragmented_dests).
-spec route_packet2(jid(), binary(), [#dest{}], stanza(), {[address()], [address()]} | [address()]) -> 'ok'.
route_packet2(From, ToS, Dests, Packet, Addresses) ->
Els = case append_dests(Dests, Addresses) of
[] ->
xmpp:get_els(Packet);
ACs ->
[#addresses{list = ACs}|xmpp:get_els(Packet)]
end,
Packet2 = xmpp:set_els(Packet, Els),
ToJID = stj(ToS),
ejabberd_router:route(xmpp:set_from_to(Packet2, From, ToJID)).
-spec append_dests([#dest{}], {[address()], [address()]} | [address()]) -> [address()].
append_dests(_Dests, {Others, Addresses}) ->
Addresses ++ Others;
append_dests([], Addresses) -> Addresses;
append_dests([Dest | Dests], Addresses) ->
append_dests(Dests, [Dest#dest.address | Addresses]).
%%%-------------------------
%%% Check relay
%%%-------------------------
-spec check_relay(binary(), binary(), [#group{}]) -> ok.
-spec check_relay(binary(), binary(), #{}) -> ok.
check_relay(RS, LS, Gs) ->
case check_relay_required(RS, LS, Gs) of
false -> ok;
true -> throw(edrelay)
case lists:suffix(str:tokens(LS, <<".">>),
str:tokens(RS, <<".">>)) orelse
(maps:is_key(LS, Gs) andalso maps:size(Gs) == 1) of
true -> ok;
_ -> throw(edrelay)
end.
-spec check_relay_required(binary(), binary(), [#group{}]) -> boolean().
check_relay_required(RServer, LServerS, Groups) ->
case lists:suffix(str:tokens(LServerS, <<".">>),
str:tokens(RServer, <<".">>)) of
true -> false;
false -> check_relay_required(LServerS, Groups)
end.
-spec check_relay_required(binary(), [#group{}]) -> boolean().
check_relay_required(LServerS, Groups) ->
lists:any(fun (Group) -> Group#group.server /= LServerS
end,
Groups).
%%%-------------------------
%%% Check protocol support: Send request
%%%-------------------------
@@ -1060,20 +920,6 @@ get_slimit_group(local, SLimits) ->
get_slimit_group(remote, SLimits) ->
SLimits#service_limits.remote.
fragment_dests(Dests, Limit_number) ->
{R, _} = lists:foldl(fun (Dest, {Res, Count}) ->
case Count of
Limit_number ->
Head2 = [Dest], {[Head2 | Res], 0};
_ ->
[Head | Tail] = Res,
Head2 = [Dest | Head],
{[Head2 | Tail], Count + 1}
end
end,
{[[]], 0}, Dests),
R.
%%%-------------------------
%%% Limits: XEP-0128 Service Discovery Extensions
%%%-------------------------
+14 -4
View File
@@ -572,6 +572,16 @@ check_event(#message{from = From, to = To, id = ID, type = Type} = Msg) ->
sub_els = [#xevent{id = ID, offline = true}]},
ejabberd_router:route(NewMsg),
true;
% Don't store composing events
#xevent{id = V, composing = true} when V /= undefined ->
false;
% Nor composing stopped events
#xevent{id = V, composing = false, delivered = false,
displayed = false, offline = false} when V /= undefined ->
false;
% But store other received notifications
#xevent{id = V} when V /= undefined ->
true;
_ ->
false
end.
@@ -1315,19 +1325,19 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
[{?T("This example allows power users to have as much as 5000 "
"offline messages, administrators up to 2000, and all the "
+4 -4
View File
@@ -154,7 +154,7 @@ handle_info({iq_reply, timeout, JID}, State) ->
{noreply, State#state{timers = Timers}};
handle_info({timeout, _TRef, {ping, JID}}, State) ->
Host = State#state.host,
From = jid:remove_resource(JID),
From = jid:make(Host),
IQ = #iq{from = From, to = JID, type = get, sub_els = [#ping{}]},
ejabberd_router:route_iq(IQ, JID,
gen_mod:get_module_proc(Host, ?MODULE),
@@ -300,7 +300,7 @@ mod_doc() ->
desc =>
?T("How long to wait before deeming that a client "
"has not answered a given server ping request. "
"The default value is '32' seconds.")}},
"The default value is 'undefined'.")}},
{send_pings,
#{value => "true | false",
desc =>
@@ -317,8 +317,8 @@ mod_doc() ->
"server ping request in less than period defined "
"in 'ping_ack_timeout' option: "
"'kill' means destroying the underlying connection, "
"'none' means to do nothing. NOTE: when 'mod_stream_mgmt' "
"module is loaded and stream management is enabled by "
"'none' means to do nothing. NOTE: when _`mod_stream_mgmt`_ "
"is loaded and stream management is enabled by "
"a client, killing the client connection doesn't mean "
"killing the client session - the session will be kept "
"alive in order to give the client a chance to resume it. "
+6 -6
View File
@@ -887,25 +887,25 @@ mod_doc() ->
"https://xmpp.org/extensions/xep-0191.html"
"[XEP-0191: Blocking Command] which is implemented by "
"'mod_blocking' module. However, you still need "
"'mod_privacy' loaded in order for 'mod_blocking' to work.")],
"'mod_privacy' loaded in order for _`mod_blocking`_ to work.")],
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
+6 -6
View File
@@ -66,7 +66,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, ?MODULE, process_sm_iq),
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
@@ -128,23 +128,23 @@ mod_doc() ->
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
-spec get_sm_features({error, stanza_error()} | empty | {result, [binary()]},
jid(), jid(), binary(), binary()) ->
+1 -1
View File
@@ -106,7 +106,7 @@ mod_doc() ->
?T("WARNING: Security issue: Privileged access gives components "
"access to sensitive data, so permission should be granted "
"carefully, only if you trust a component."), "",
?T("NOTE: This module is complementary to 'mod_delegation', "
?T("NOTE: This module is complementary to _`mod_delegation`_, "
"but can also be used separately.")],
opts =>
[{roster,
+138 -15
View File
@@ -45,6 +45,7 @@
-include("mod_roster.hrl").
-include("translate.hrl").
-include("ejabberd_stacktrace.hrl").
-include("ejabberd_commands.hrl").
-define(STDTREE, <<"tree">>).
-define(STDNODE, <<"flat">>).
@@ -93,6 +94,9 @@
handle_call/3, handle_cast/2, handle_info/2, mod_doc/0,
terminate/2, code_change/3, depends/2, mod_opt_type/1, mod_options/1]).
%% ejabberd commands
-export([get_commands_spec/0, delete_old_items/1, delete_expired_items/0]).
-export([route/1]).
%%====================================================================
@@ -210,7 +214,7 @@
pep_mapping :: [{binary(), binary()}],
ignore_pep_from_offline :: boolean(),
last_item_cache :: boolean(),
max_items_node :: non_neg_integer(),
max_items_node :: non_neg_integer()|unlimited,
max_subscriptions_node :: non_neg_integer()|undefined,
default_node_config :: [{atom(), binary()|boolean()|integer()|atom()}],
nodetree :: binary(),
@@ -337,6 +341,7 @@ init([ServerHost|_]) ->
false ->
ok
end,
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins),
PepMapping = config(ServerHost, pep_mapping),
@@ -806,7 +811,13 @@ terminate(_Reason,
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
ejabberd_router:unregister_route(Host)
end, Hosts).
end, Hosts),
case gen_mod:is_loaded_elsewhere(ServerHost, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -3399,14 +3410,14 @@ node_config(_, _, []) ->
%% @doc <p>Return the maximum number of items for a given node.</p>
%% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p>
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer() | unlimited.
max_items(Host, Options) ->
case get_option(Options, persist_items) of
true ->
case get_option(Options, max_items) of
I when is_integer(I), I < 0 -> 0;
I when is_integer(I) -> I;
_ -> ?MAXITEMS
_ -> get_max_items_node(Host)
end;
false ->
case get_option(Options, send_last_published_item) of
@@ -3420,6 +3431,14 @@ max_items(Host, Options) ->
end
end.
-spec item_expire(host(), [{atom(), any()}]) -> non_neg_integer() | infinity.
item_expire(Host, Options) ->
case get_option(Options, item_expire) of
I when is_integer(I), I < 0 -> 0;
I when is_integer(I) -> I;
_ -> get_max_item_expire_node(Host)
end.
-spec get_configure_xfields(_, pubsub_node_config:result(),
binary(), [binary()]) -> [xdata_field()].
get_configure_xfields(_Type, Options, Lang, Groups) ->
@@ -3548,16 +3567,23 @@ decode_get_pending(#xdata{fields = Fs}, Lang) ->
{error, xmpp:err_resource_constraint(Txt, Lang)}
end.
-spec check_opt_range(atom(), [proplists:property()], non_neg_integer()) -> boolean().
check_opt_range(_Opt, _Opts, undefined) ->
-spec check_opt_range(atom(), [proplists:property()],
non_neg_integer() | unlimited) -> boolean().
check_opt_range(_Opt, _Opts, unlimited) ->
true;
check_opt_range(Opt, Opts, Max) ->
Val = proplists:get_value(Opt, Opts, Max),
Val =< Max.
case proplists:get_value(Opt, Opts, Max) of
max -> true;
Val -> Val =< Max
end.
-spec get_max_items_node(host()) -> undefined | non_neg_integer().
-spec get_max_items_node(host()) -> unlimited | non_neg_integer().
get_max_items_node(Host) ->
config(Host, max_items_node, undefined).
config(Host, max_items_node, ?MAXITEMS).
-spec get_max_item_expire_node(host()) -> infinity | non_neg_integer().
get_max_item_expire_node(Host) ->
config(Host, max_item_expire_node, infinity).
-spec get_max_subscriptions_node(host()) -> undefined | non_neg_integer().
get_max_subscriptions_node(Host) ->
@@ -3707,6 +3733,7 @@ features() ->
<<"access-whitelist">>, % OPTIONAL
<<"collections">>, % RECOMMENDED
<<"config-node">>, % RECOMMENDED
<<"config-node-max">>,
<<"create-and-configure">>, % RECOMMENDED
<<"item-ids">>, % RECOMMENDED
<<"last-published">>, % RECOMMENDED
@@ -4136,6 +4163,93 @@ purge_offline(Host, LJID, Node) ->
{error, xmpp:err_internal_server_error(Txt, Lang)}
end.
-spec delete_old_items(non_neg_integer()) -> ok | error.
delete_old_items(N) ->
Results = lists:flatmap(
fun(Host) ->
case tree_action(Host, get_all_nodes, [Host]) of
Nodes when is_list(Nodes) ->
lists:map(
fun(#pubsub_node{id = Nidx, type = Type}) ->
case node_action(Host, Type,
remove_extra_items,
[Nidx , N]) of
{result, _} ->
ok;
{error, _} ->
error
end
end, Nodes);
_ ->
error
end
end, ejabberd_option:hosts()),
case lists:member(error, Results) of
true ->
error;
false ->
ok
end.
-spec delete_expired_items() -> ok | error.
delete_expired_items() ->
Results = lists:flatmap(
fun(Host) ->
case tree_action(Host, get_all_nodes, [Host]) of
Nodes when is_list(Nodes) ->
lists:map(
fun(#pubsub_node{id = Nidx, type = Type,
options = Options}) ->
case item_expire(Host, Options) of
infinity ->
ok;
Seconds ->
case node_action(
Host, Type,
remove_expired_items,
[Nidx, Seconds]) of
{result, []} ->
ok;
{result, [_|_]} ->
unset_cached_item(
Host, Nidx);
{error, _} ->
error
end
end
end, Nodes);
_ ->
error
end
end, ejabberd_option:hosts()),
case lists:member(error, Results) of
true ->
error;
false ->
ok
end.
-spec get_commands_spec() -> [ejabberd_commands()].
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_pubsub_items, tags = [purge],
desc = "Keep only NUMBER of PubSub items per node",
note = "added in 21.12",
module = ?MODULE, function = delete_old_items,
args_desc = ["Number of items to keep per node"],
args = [{number, integer}],
result = {res, rescode},
result_desc = "0 if command failed, 1 when succeeded",
args_example = [1000],
result_example = ok},
#ejabberd_commands{name = delete_expired_pubsub_items, tags = [purge],
desc = "Delete expired PubSub items",
note = "added in 21.12",
module = ?MODULE, function = delete_expired_items,
args = [],
result = {res, rescode},
result_desc = "0 if command failed, 1 when succeeded",
result_example = ok}].
-spec mod_opt_type(atom()) -> econf:validator().
mod_opt_type(access_createnode) ->
econf:acl();
@@ -4146,7 +4260,9 @@ mod_opt_type(ignore_pep_from_offline) ->
mod_opt_type(last_item_cache) ->
econf:bool();
mod_opt_type(max_items_node) ->
econf:non_neg_int();
econf:non_neg_int(unlimited);
mod_opt_type(max_item_expire_node) ->
econf:timeout(second, infinity);
mod_opt_type(max_nodes_discoitems) ->
econf:non_neg_int(infinity);
mod_opt_type(max_subscriptions_node) ->
@@ -4194,6 +4310,7 @@ mod_options(Host) ->
{ignore_pep_from_offline, true},
{last_item_cache, false},
{max_items_node, ?MAXITEMS},
{max_item_expire_node, infinity},
{max_nodes_discoitems, 100},
{nodetree, ?STDTREE},
{pep_mapping, []},
@@ -4212,7 +4329,7 @@ mod_doc() ->
"(https://xmpp.org/extensions/xep-0163.html"
"[XEP-0163: Personal Eventing via Pubsub]) "
"is enabled in the default ejabberd configuration file, "
"and it requires 'mod_caps'.")],
"and it requires _`mod_caps`_.")],
opts =>
[{access_createnode,
#{value => "AccessName",
@@ -4225,7 +4342,7 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to "
?T("Same as top-level _`default_db`_ option, but applied to "
"this module only.")}},
{default_node_config,
#{value => "List of Key:Value",
@@ -4272,11 +4389,17 @@ mod_doc() ->
" so many nodes, caching last items speeds up pubsub "
"and allows to raise user connection rate. The cost "
"is memory usage, as every item is stored in memory.")}},
{max_item_expire_node,
#{value => "timeout() | infinity",
note => "added in 21.12",
desc =>
?T("Specify the maximum item epiry time. Default value "
"is: 'infinity'.")}},
{max_items_node,
#{value => "MaxItems",
#{value => "non_neg_integer() | infinity",
desc =>
?T("Define the maximum number of items that can be "
"stored in a node. Default value is: '10'.")}},
"stored in a node. Default value is: '1000'.")}},
{max_nodes_discoitems,
#{value => "pos_integer() | infinity",
desc =>
+8 -1
View File
@@ -11,6 +11,7 @@
-export([hosts/1]).
-export([ignore_pep_from_offline/1]).
-export([last_item_cache/1]).
-export([max_item_expire_node/1]).
-export([max_items_node/1]).
-export([max_nodes_discoitems/1]).
-export([max_subscriptions_node/1]).
@@ -68,7 +69,13 @@ last_item_cache(Opts) when is_map(Opts) ->
last_item_cache(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, last_item_cache).
-spec max_items_node(gen_mod:opts() | global | binary()) -> non_neg_integer().
-spec max_item_expire_node(gen_mod:opts() | global | binary()) -> 'infinity' | pos_integer().
max_item_expire_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_item_expire_node, Opts);
max_item_expire_node(Host) ->
gen_mod:get_module_opt(Host, mod_pubsub, max_item_expire_node).
-spec max_items_node(gen_mod:opts() | global | binary()) -> 'unlimited' | non_neg_integer().
max_items_node(Opts) when is_map(Opts) ->
gen_mod:get_opt(max_items_node, Opts);
max_items_node(Host) ->
+11 -10
View File
@@ -98,7 +98,7 @@ start(Host, Opts) ->
init_cache(Mod, Host, Opts),
register_iq_handlers(Host),
register_hooks(Host),
ejabberd_commands:register_commands(get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
-spec stop(binary()) -> ok.
stop(Host) ->
@@ -191,23 +191,23 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
%%--------------------------------------------------------------------
%% ejabberd command callback.
@@ -405,7 +405,7 @@ c2s_stanza(State, #stream_error{}, _SendResult) ->
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
Pkt, _SendResult) ->
?DEBUG("Notifying client of stanza", []),
notify(State, unwrap_message(Pkt), get_direction(Pkt)),
notify(State, Pkt, get_direction(Pkt)),
State;
c2s_stanza(State, _Pkt, _SendResult) ->
State.
@@ -454,7 +454,7 @@ c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
{Pkt, Dir} = case mod_stream_mgmt:queue_find(
fun is_incoming_chat_msg/1, Queue) of
none -> {none, undefined};
Pkt0 -> {unwrap_message(Pkt0), get_direction(Pkt0)}
Pkt0 -> {Pkt0, get_direction(Pkt0)}
end,
notify(State, Pkt, Dir),
State;
@@ -536,7 +536,8 @@ notify(LUser, LServer, Clients, Pkt, Dir) ->
-spec notify(binary(), ljid(), binary(), xdata(),
xmpp_element() | xmlel() | none, direction(),
fun((iq() | timeout) -> any())) -> ok.
notify(LServer, PushLJID, Node, XData, Pkt, Dir, HandleResponse) ->
notify(LServer, PushLJID, Node, XData, Pkt0, Dir, HandleResponse) ->
Pkt = unwrap_message(Pkt0),
From = jid:make(LServer),
Summary = make_summary(LServer, Pkt, Dir),
Item = #ps_item{sub_els = [#push_notification{xdata = Summary}]},
@@ -714,7 +715,7 @@ make_summary(Host, #message{from = From} = Pkt, recv) ->
make_summary(_Host, _Pkt, _Dir) ->
undefined.
-spec unwrap_message(stanza()) -> stanza().
-spec unwrap_message(Stanza) -> Stanza when Stanza :: stanza() | none.
unwrap_message(#message{meta = #{carbon_copy := true}} = Msg) ->
misc:unwrap_carbon(Msg);
unwrap_message(#message{type = normal} = Msg) ->
+4 -4
View File
@@ -94,13 +94,13 @@ mod_options(_Host) ->
mod_doc() ->
#{desc =>
[?T("This module tries to keep the stream management "
"session (see 'mod_stream_mgmt') of a disconnected "
"session (see _`mod_stream_mgmt`_) of a disconnected "
"mobile client alive if the client enabled push "
"notifications for that session. However, the normal "
"session resumption timeout is restored once a push "
"notification is issued, so the session will be closed "
"if the client doesn't respond to push notifications."), "",
?T("The module depends on 'mod_push'.")],
?T("The module depends on _`mod_push`_.")],
opts =>
[{resume_timeout,
#{value => "timeout()",
@@ -109,8 +109,8 @@ mod_doc() ->
"the session of a disconnected push client times out. "
"This timeout is only in effect as long as no push "
"notification is issued. Once that happened, the "
"resumption timeout configured for the 'mod_stream_mgmt' "
"module is restored. "
"resumption timeout configured for _`mod_stream_mgmt`_ "
"is restored. "
"The default value is '72' hours.")}},
{wake_on_start,
#{value => "true | false",
+1 -1
View File
@@ -52,7 +52,7 @@ store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
case ?SQL_UPSERT(LServer, "push_session",
["!username=%(LUser)s",
"!server_host=%(LServer)s",
"!timestamp=%(TS)d",
"timestamp=%(TS)d",
"!service=%(Service)s",
"!node=%(Node)s",
"xml=%(XML)s"]) of
+29 -11
View File
@@ -32,11 +32,13 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, stream_feature_register/2,
c2s_unauthenticated_packet/2, try_register/4,
c2s_unauthenticated_packet/2, try_register/4, try_register/5,
process_iq/1, send_registration_notifications/3,
mod_opt_type/1, mod_options/1, depends/2,
format_error/1, mod_doc/0]).
-deprecated({try_register, 4}).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("translate.hrl").
@@ -283,7 +285,7 @@ try_register_or_set_password(User, Server, Password,
_ when CaptchaSucceed ->
case check_from(From, Server) of
allow ->
case try_register(User, Server, Password, Source, Lang) of
case try_register(User, Server, Password, Source, ?MODULE, Lang) of
ok ->
xmpp:make_iq_result(IQ);
{error, Error} ->
@@ -328,6 +330,13 @@ try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) ->
xmpp:make_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang))
end.
try_register(User, Server, Password, SourceRaw, Module) ->
Modules = mod_register_opt:allow_modules(Server),
case (Modules == all) orelse lists:member(Module, Modules) of
true -> try_register(User, Server, Password, SourceRaw);
false -> {error, eaccess}
end.
try_register(User, Server, Password, SourceRaw) ->
case jid:is_nodename(User) of
false ->
@@ -363,8 +372,8 @@ try_register(User, Server, Password, SourceRaw) ->
end
end.
try_register(User, Server, Password, SourceRaw, Lang) ->
case try_register(User, Server, Password, SourceRaw) of
try_register(User, Server, Password, SourceRaw, Module, Lang) ->
case try_register(User, Server, Password, SourceRaw, Module) of
ok ->
JID = jid:make(User, Server),
Source = may_remove_resource(SourceRaw),
@@ -597,6 +606,8 @@ mod_opt_type(access_from) ->
econf:acl();
mod_opt_type(access_remove) ->
econf:acl();
mod_opt_type(allow_modules) ->
econf:either(all, econf:list(econf:atom()));
mod_opt_type(captcha_protected) ->
econf:bool();
mod_opt_type(ip_access) ->
@@ -623,6 +634,7 @@ mod_options(_Host) ->
[{access, all},
{access_from, none},
{access_remove, all},
{allow_modules, all},
{captcha_protected, false},
{ip_access, all},
{password_strength, 0},
@@ -638,9 +650,9 @@ mod_doc() ->
?T("* Register a new account on the server."), "",
?T("* Change the password from an existing account on the server."), "",
?T("* Delete an existing account on the server."), "",
?T("This module reads also another option defined globally for the "
"server: 'registration_timeout'. Please check that option "
"documentation in the section with top-level options.")],
?T("This module reads also the top-level _`registration_timeout`_ "
"option defined globally for the server, "
"so please check that option documentation too.")],
opts =>
[{access,
#{value => ?T("AccessName"),
@@ -661,12 +673,18 @@ mod_doc() ->
desc =>
?T("Specify rules to restrict access for user unregistration. "
"By default any user is able to unregister their account.")}},
{allow_modules,
#{value => "all | [Module, ...]",
note => "added in 21.12",
desc =>
?T("List of modules that can register accounts, or 'all'. "
"The default value is 'all', which is equivalent to "
"something like '[mod_register, mod_register_web]'.")}},
{captcha_protected,
#{value => "true | false",
desc =>
?T("Protect registrations with CAPTCHA (see section "
"https://docs.ejabberd.im/admin/configuration/basic/#captcha[CAPTCHA] "
"of the Configuration Guide). The default is 'false'.")}},
?T("Protect registrations with http://../basic/#captcha[CAPTCHA]. "
"The default is 'false'.")}},
{ip_access,
#{value => ?T("AccessName"),
desc =>
@@ -680,7 +698,7 @@ mod_doc() ->
"https://en.wikipedia.org/wiki/Entropy_(information_theory)"
"[Shannon entropy] for passwords. The value 'Entropy' is a "
"number of bits of entropy. The recommended minimum is 32 bits. "
"The default is 0, i.e. no checks are performed.")}},
"The default is '0', i.e. no checks are performed.")}},
{registration_watchers,
#{value => "[JID, ...]",
desc =>
+7
View File
@@ -6,6 +6,7 @@
-export([access/1]).
-export([access_from/1]).
-export([access_remove/1]).
-export([allow_modules/1]).
-export([captcha_protected/1]).
-export([ip_access/1]).
-export([password_strength/1]).
@@ -31,6 +32,12 @@ access_remove(Opts) when is_map(Opts) ->
access_remove(Host) ->
gen_mod:get_module_opt(Host, mod_register, access_remove).
-spec allow_modules(gen_mod:opts() | global | binary()) -> 'all' | [atom()].
allow_modules(Opts) when is_map(Opts) ->
gen_mod:get_opt(allow_modules, Opts);
allow_modules(Host) ->
gen_mod:get_module_opt(Host, mod_register, allow_modules).
-spec captcha_protected(gen_mod:opts() | global | binary()) -> boolean().
captcha_protected(Opts) when is_map(Opts) ->
gen_mod:get_opt(captcha_protected, Opts);
+50 -60
View File
@@ -23,32 +23,6 @@
%%%
%%%----------------------------------------------------------------------
%%% IDEAS:
%%%
%%% * Implement those options, already present in mod_register:
%%% + access
%%% + captcha_protected
%%% + password_strength
%%% + welcome_message
%%% + registration_timeout
%%%
%%% * Improve this module to allow each virtual host to have different
%%% options. See http://support.process-one.net/browse/EJAB-561
%%%
%%% * Check that all the text is translatable.
%%%
%%% * Add option to use a custom CSS file, or custom CSS lines.
%%%
%%% * Don't hardcode the "register" path in URL.
%%%
%%% * Allow private email during register, and store in custom table.
%%% * Optionally require private email to register.
%%% * Optionally require email confirmation to register.
%%% * Allow to set a private email address anytime.
%%% * Allow to recover password using private email to confirm (mod_passrecover)
%%% * Optionally require invitation
%%% * Optionally register request is forwarded to admin, no account created.
-module(mod_register_web).
-author('badlop@process-one.net').
@@ -111,7 +85,7 @@ process([Section],
process([<<"new">>],
#request{method = 'POST', q = Q, ip = {Ip, _Port},
lang = Lang, host = _HTTPHost}) ->
case form_new_post(Q) of
case form_new_post(Q, Ip) of
{success, ok, {Username, Host, _Password}} ->
Jid = jid:make(Username, Host),
mod_register:send_registration_notifications(?MODULE, Jid, Ip),
@@ -316,10 +290,10 @@ form_new_get2(Host, Lang, CaptchaEls) ->
%%% Formulary new POST
%%%----------------------------------------------------------------------
form_new_post(Q) ->
form_new_post(Q, Ip) ->
case catch get_register_parameters(Q) of
[Username, Host, Password, Password, Id, Key] ->
form_new_post(Username, Host, Password, {Id, Key});
form_new_post(Username, Host, Password, {Id, Key}, Ip);
[_Username, _Host, _Password, _Password2, false, false] ->
{error, passwords_not_identical};
[_Username, _Host, _Password, _Password2, Id, Key] ->
@@ -338,13 +312,12 @@ get_register_parameters(Q) ->
[<<"username">>, <<"host">>, <<"password">>, <<"password2">>,
<<"id">>, <<"key">>]).
form_new_post(Username, Host, Password,
{false, false}) ->
register_account(Username, Host, Password);
form_new_post(Username, Host, Password, {Id, Key}) ->
form_new_post(Username, Host, Password, {false, false}, Ip) ->
register_account(Username, Host, Password, Ip);
form_new_post(Username, Host, Password, {Id, Key}, Ip) ->
case ejabberd_captcha:check_captcha(Id, Key) of
captcha_valid ->
register_account(Username, Host, Password);
register_account(Username, Host, Password, Ip);
captcha_non_valid -> {error, captcha_non_valid};
captcha_not_found -> {error, captcha_non_valid}
end.
@@ -528,24 +501,27 @@ form_del_get(Host, Lang) ->
{<<"Content-Type">>, <<"text/html">>}],
ejabberd_web:make_xhtml(HeadEls, Els)}.
%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} |
%% @spec(Username, Host, Password, Ip) -> {success, ok, {Username, Host, Password} |
%% {success, exists, {Username, Host, Password}} |
%% {error, not_allowed} |
%% {error, invalid_jid}
register_account(Username, Host, Password) ->
Access = mod_register_opt:access(Host),
case jid:make(Username, Host) of
error -> {error, invalid_jid};
JID ->
case acl:match_rule(Host, Access, JID) of
deny -> {error, not_allowed};
allow -> register_account2(Username, Host, Password)
end
register_account(Username, Host, Password, Ip) ->
try mod_register_opt:access(Host) of
Access ->
case jid:make(Username, Host) of
error -> {error, invalid_jid};
JID ->
case acl:match_rule(Host, Access, JID) of
deny -> {error, not_allowed};
allow -> register_account2(Username, Host, Password, Ip)
end
end
catch _:{module_not_loaded, mod_register, _Host} ->
{error, host_unknown}
end.
register_account2(Username, Host, Password) ->
case ejabberd_auth:try_register(Username, Host,
Password)
register_account2(Username, Host, Password, Ip) ->
case mod_register:try_register(Username, Host, Password, Ip, ?MODULE)
of
ok ->
{success, ok, {Username, Host, Password}};
@@ -601,10 +577,8 @@ get_error_text({error, exists}) ->
?T("The account already exists");
get_error_text({error, password_incorrect}) ->
?T("Incorrect password");
get_error_text({error, invalid_jid}) ->
?T("The username is not valid");
get_error_text({error, not_allowed}) ->
?T("Not allowed");
get_error_text({error, host_unknown}) ->
?T("Host unknown");
get_error_text({error, account_doesnt_exist}) ->
?T("Account doesn't exist");
get_error_text({error, account_exists}) ->
@@ -614,7 +588,9 @@ get_error_text({error, password_not_changed}) ->
get_error_text({error, passwords_not_identical}) ->
?T("The passwords are different");
get_error_text({error, wrong_parameters}) ->
?T("Wrong parameters in the web formulary").
?T("Wrong parameters in the web formulary");
get_error_text({error, Why}) ->
mod_register:format_error(Why).
mod_options(_) ->
[].
@@ -625,13 +601,27 @@ mod_doc() ->
?T("- Register a new account on the server."), "",
?T("- Change the password from an existing account on the server."), "",
?T("- Unregister an existing account on the server."), "",
?T("This module supports CAPTCHA image to register a new account. "
"To enable this feature, configure the options 'captcha\_cmd' "
"and 'captcha\_url', which are documented in the section with "
"top-level options."), "",
?T("As an example usage, the users of the host 'example.org' can "
"visit the page: 'https://example.org:5281/register/' It is "
?T("This module supports http://../basic/#captcha[CAPTCHA] "
"to register a new account. "
"To enable this feature, configure the "
"top-level _`captcha_cmd`_ and "
"top-level _`captcha_url`_ options."), "",
?T("As an example usage, the users of the host 'localhost' can "
"visit the page: 'https://localhost:5280/register/' It is "
"important to include the last / character in the URL, "
"otherwise the subpages URL will be incorrect."), "",
?T("The module depends on 'mod_register' where all the configuration "
"is performed.")]}.
?T("This module is enabled in 'listen' -> 'ejabberd_http' -> "
"http://../listen-options/#request-handlers[request_handlers], "
"no need to enable in 'modules'."),
?T("The module depends on _`mod_register`_ where all the "
"configuration is performed.")],
example =>
["listen:",
" -",
" port: 5280",
" module: ejabberd_http",
" request_handlers:",
" /register: mod_register_web",
"",
"modules:",
" mod_register: {}"]}.
+7 -7
View File
@@ -1345,29 +1345,29 @@ mod_doc() ->
"This option does not affect the client in any way. "
"This option is only useful if option 'versioning' is "
"set to 'true'. The default value is 'false'. "
"IMPORTANT: if you use 'mod_shared_roster' or "
"'mod_shared_roster_ldap', you must set the value "
"IMPORTANT: if you use _`mod_shared_roster`_ or "
" _`mod_shared_roster_ldap`_, you must set the value "
"of the option to 'false'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
["modules:",
" ...",
+4 -6
View File
@@ -80,9 +80,10 @@ get_roster(LUser, LServer) ->
[]
end,
GroupsDict = lists:foldl(fun({J, G}, Acc) ->
dict:append(J, G, Acc)
Gs = maps:get(J, Acc, []),
maps:put(J, [G | Gs], Acc)
end,
dict:new(), JIDGroups),
maps:new(), JIDGroups),
{ok, lists:flatmap(
fun(I) ->
case raw_to_record(LServer, I) of
@@ -90,10 +91,7 @@ get_roster(LUser, LServer) ->
error -> [];
R ->
SJID = jid:encode(R#roster.jid),
Groups = case dict:find(SJID, GroupsDict) of
{ok, Gs} -> Gs;
error -> []
end,
Groups = maps:get(SJID, GroupsDict, []),
[R#roster{groups = Groups}]
end
end, Items)};
+7 -7
View File
@@ -1266,7 +1266,7 @@ mod_doc() ->
?T("- Displayed: A list of groups that will be in the "
"rosters of this group's members. A group of other vhost can "
"be identified with 'groupid@vhost'."), "",
?T("This module depends on 'mod_roster'. "
?T("This module depends on _`mod_roster`_. "
"If not enabled, roster queries will return 503 errors.")],
opts =>
[{db_type,
@@ -1274,25 +1274,25 @@ mod_doc() ->
desc =>
?T("Define the type of storage where the module will create "
"the tables and store user information. The default is "
"the storage defined by the global option 'default_db', "
"the storage defined by the top-level _`default_db`_ option, "
"or 'mnesia' if omitted. If 'sql' value is defined, "
"make sure you have defined the database.")}},
{use_cache,
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}],
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}],
example =>
[{?T("Take the case of a computer club that wants all its members "
"seeing each other in their rosters. To achieve this, they "
+1 -1
View File
@@ -796,7 +796,7 @@ mod_doc() ->
"disable the check. Default value is 'true'.")}}] ++
[{Opt,
#{desc =>
{?T("Same as top-level '~s' option, but "
{?T("Same as top-level _`~s`_ option, but "
"applied to this module only."), [Opt]}}}
|| Opt <- [ldap_backups, ldap_base, ldap_uids, ldap_deref_aliases,
ldap_encrypt, ldap_password, ldap_port, ldap_rootdn,
+5 -3
View File
@@ -962,12 +962,14 @@ mod_doc() ->
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
?T("Same as top-level _`queue_type`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, "
"but applied to this module only. "
"The default value is '48 hours'.")}}]}.
+1 -1
View File
@@ -176,7 +176,7 @@ mod_doc() ->
"clients. If ejabberd's built-in TURN service is used, "
"TURN relays allocated using temporary credentials will "
"be terminated shortly after the credentials expired. The "
"default value is '12' hours. Note that restarting the "
"default value is '12 hours'. Note that restarting the "
"ejabberd node invalidates any temporary credentials "
"offered before the restart unless a 'secret' is "
"specified (see below).")}},
+5 -5
View File
@@ -640,23 +640,23 @@ mod_doc() ->
{db_type,
#{value => "mnesia | sql | ldap",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}},
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}},
{vcard,
#{value => ?T("vCard"),
desc =>
+1 -1
View File
@@ -571,7 +571,7 @@ mod_doc() ->
}]}}] ++
[{Opt,
#{desc =>
{?T("Same as top-level '~s' option, but "
{?T("Same as top-level _`~s`_ option, but "
"applied to this module only."), [Opt]}}}
|| Opt <- [ldap_base, ldap_servers, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,
+6 -6
View File
@@ -228,26 +228,26 @@ mod_doc() ->
"frequently their presence. However, the overhead is significantly "
"reduced by the use of caching, so you probably don't want "
"to set 'use_cache' to 'false'."), "",
?T("The module depends on 'mod_vcard'."), "",
?T("The module depends on _`mod_vcard`_."), "",
?T("NOTE: Nowadays https://xmpp.org/extensions/xep-0153.html"
"[XEP-0153] is used mostly as \"read-only\", i.e. modern "
"clients don't publish their avatars inside vCards. Thus "
"in the majority of cases the module is only used along "
"with 'mod_avatar' module for providing backward compatibility.")],
"with _`mod_avatar`_ for providing backward compatibility.")],
opts =>
[{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
+34 -9
View File
@@ -39,7 +39,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -375,7 +376,8 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Subscribed == true)) ->
{error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
if MaxItems > 0;
MaxItems == unlimited ->
Now = erlang:timestamp(),
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
@@ -402,6 +404,16 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
end
end.
remove_extra_items(Nidx, MaxItems) ->
{result, States} = get_states(Nidx),
Records = States ++ mnesia:read({pubsub_orphan, Nidx}),
ItemIds = lists:flatmap(fun(#pubsub_state{items = Is}) ->
Is;
(#pubsub_orphan{items = Is}) ->
Is
end, Records),
remove_extra_items(Nidx, MaxItems, ItemIds).
%% @doc <p>This function is used to remove extra items, most notably when the
%% maximum number of items has been reached.</p>
%% <p>This function is used internally by the core PubSub module, as no
@@ -420,6 +432,22 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
remove_expired_items(_Nidx, infinity) ->
{result, []};
remove_expired_items(Nidx, Seconds) ->
Items = mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx),
ExpT = misc:usec_to_now(
erlang:system_time(microsecond) - (Seconds * 1000000)),
ExpItems = lists:filtermap(
fun(#pubsub_item{itemid = {ItemId, _},
modification = {ModT, _}}) when ModT < ExpT ->
{true, ItemId};
(#pubsub_item{}) ->
false
end, Items),
del_items(Nidx, ExpItems),
{result, ExpItems}.
%% @doc <p>Triggers item deletion.</p>
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>
@@ -945,15 +973,12 @@ rsm_page(Count, Index, Offset, Items) ->
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> {MS,S,US};
_ -> Stamp
try xmpp_util:decode_timestamp(Stamp)
catch _:{bad_timestamp, _} ->
Stamp % We should return a proper error to the client instead.
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(Stamp) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
xmpp_util:encode_timestamp(Stamp).
transform({pubsub_state, {Id, Nidx}, Is, A, Ss}) ->
{pubsub_state, {Id, Nidx}, Nidx, Is, A, Ss};
+57 -14
View File
@@ -40,9 +40,10 @@
-include("translate.hrl").
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
create_node_permission/6, create_node/2, delete_node/1, purge_node/2,
subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -247,7 +248,8 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
or (Subscribed == true)) ->
{error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
if MaxItems > 0;
MaxItems == unlimited ->
Now = erlang:timestamp(),
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
@@ -258,20 +260,23 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
{result, _} ->
{error, xmpp:err_forbidden()};
_ ->
Items = [ItemId | itemids(Nidx, GenKey)],
{result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
OldIds = maybe_remove_extra_items(Nidx, MaxItems,
GenKey, ItemId),
set_item(#pubsub_item{
itemid = {ItemId, Nidx},
creation = {Now, GenKey},
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, OI}}
{result, {default, broadcast, OldIds}}
end;
true ->
{result, {default, broadcast, []}}
end
end.
remove_extra_items(Nidx, MaxItems) ->
remove_extra_items(Nidx, MaxItems, itemids(Nidx)).
remove_extra_items(_Nidx, unlimited, ItemIds) ->
{result, {ItemIds, []}};
remove_extra_items(Nidx, MaxItems, ItemIds) ->
@@ -280,6 +285,23 @@ remove_extra_items(Nidx, MaxItems, ItemIds) ->
del_items(Nidx, OldItems),
{result, {NewItems, OldItems}}.
remove_expired_items(_Nidx, infinity) ->
{result, []};
remove_expired_items(Nidx, Seconds) ->
ExpT = encode_now(
misc:usec_to_now(
erlang:system_time(microsecond) - (Seconds * 1000000))),
case ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where nodeid=%(Nidx)d "
"and creation < %(ExpT)s")) of
{selected, RItems} ->
ItemIds = [ItemId || {ItemId} <- RItems],
del_items(Nidx, ItemIds),
{result, ItemIds};
_ ->
{result, []}
end.
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
SubKey = jid:tolower(Publisher),
GenKey = jid:remove_resource(SubKey),
@@ -862,6 +884,18 @@ first_in_list(Pred, [H | T]) ->
_ -> first_in_list(Pred, T)
end.
itemids(Nidx) ->
case catch
ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s from pubsub_item where "
"nodeid=%(Nidx)d order by modification desc"))
of
{selected, RItems} ->
[ItemId || {ItemId} <- RItems];
_ ->
[]
end.
itemids(Nidx, {_U, _S, _R} = JID) ->
SJID = encode_jid(JID),
SJIDLike = <<(encode_jid_like(JID))/binary, "/%">>,
@@ -933,6 +967,16 @@ update_subscription(Nidx, JID, Subscription) ->
"-affiliation='n'"
]).
-spec maybe_remove_extra_items(mod_pubsub:nodeIdx(),
non_neg_integer() | unlimited, ljid(),
mod_pubsub:itemId()) -> [mod_pubsub:itemId()].
maybe_remove_extra_items(_Nidx, unlimited, _GenKey, _ItemId) ->
[];
maybe_remove_extra_items(Nidx, MaxItems, GenKey, ItemId) ->
ItemIds = [ItemId | itemids(Nidx, GenKey)],
{result, {_NewIds, OldIds}} = remove_extra_items(Nidx, MaxItems, ItemIds),
OldIds.
-spec decode_jid(SJID :: binary()) -> ljid().
decode_jid(SJID) ->
jid:tolower(jid:decode(SJID)).
@@ -1037,15 +1081,14 @@ rsm_page(Count, Index, Offset, Items) ->
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> encode_now({MS,S,US});
_ -> Stamp
try xmpp_util:decode_timestamp(Stamp) of
Now ->
encode_now(Now)
catch _:{bad_timestamp, _} ->
Stamp % We should return a proper error to the client instead.
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(decode_now(Stamp)) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
xmpp_util:encode_timestamp(decode_now(Stamp)).
encode_now({T1, T2, T3}) ->
<<(misc:i2l(T1, 6))/binary, ":",
+8 -1
View File
@@ -35,7 +35,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -135,9 +136,15 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems) ->
node_flat:remove_extra_items(Nidx, MaxItems).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat:remove_extra_items(Nidx, MaxItems, ItemIds).
remove_expired_items(Nidx, Seconds) ->
node_flat:remove_expired_items(Nidx, Seconds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_flat:delete_item(Nidx, Publisher, PublishModel, ItemId).
+8 -1
View File
@@ -37,7 +37,8 @@
-export([init/3, terminate/2, options/0, features/0,
create_node_permission/6, create_node/2, delete_node/1,
purge_node/2, subscribe_node/8, unsubscribe_node/4,
publish_item/7, delete_item/4, remove_extra_items/3,
publish_item/7, delete_item/4,
remove_extra_items/2, remove_extra_items/3, remove_expired_items/2,
get_entity_affiliations/2, get_node_affiliations/1,
get_affiliation/2, set_affiliation/3,
get_entity_subscriptions/2, get_node_subscriptions/1,
@@ -92,9 +93,15 @@ publish_item(Nidx, Publisher, Model, MaxItems, ItemId, Payload, PubOpts) ->
node_flat_sql:publish_item(Nidx, Publisher, Model, MaxItems, ItemId,
Payload, PubOpts).
remove_extra_items(Nidx, MaxItems) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems).
remove_extra_items(Nidx, MaxItems, ItemIds) ->
node_flat_sql:remove_extra_items(Nidx, MaxItems, ItemIds).
remove_expired_items(Nidx, Seconds) ->
node_flat_sql:remove_expired_items(Nidx, Seconds).
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
node_flat_sql:delete_item(Nidx, Publisher, PublishModel, ItemId).
+10 -1
View File
@@ -46,7 +46,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@@ -98,6 +99,14 @@ get_nodes(Host, Limit) ->
{Nodes, _} -> Nodes
end.
get_all_nodes({_U, _S, _R} = Owner) ->
Host = jid:tolower(jid:remove_resource(Owner)),
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'});
get_all_nodes(Host) ->
mnesia:match_object(#pubsub_node{nodeid = {Host, '_'}, _ = '_'})
++ mnesia:match_object(#pubsub_node{nodeid = {{'_', Host, '_'}, '_'},
_ = '_'}).
get_parentnodes(Host, Node, _From) ->
case catch mnesia:read({pubsub_node, {Host, Node}}) of
[Record] when is_record(Record, pubsub_node) ->
+30 -1
View File
@@ -45,7 +45,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@@ -165,6 +166,34 @@ get_nodes(Host, Limit) ->
[]
end.
get_all_nodes({_U, _S, _R} = JID) ->
SubKey = jid:tolower(JID),
GenKey = jid:remove_resource(SubKey),
EncKey = node_flat_sql:encode_jid(GenKey),
Pattern = <<(node_flat_sql:encode_jid_like(GenKey))/binary, "/%">>,
case ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(EncKey)s "
"or host like %(Pattern)s %ESCAPE")) of
{selected, RItems} ->
[raw_to_node(GenKey, Item) || Item <- RItems];
_ ->
[]
end;
get_all_nodes(Host) ->
Pattern1 = <<"%@", Host/binary>>,
Pattern2 = <<"%@", Host/binary, "/%">>,
case ejabberd_sql:sql_query_t(
?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d "
"from pubsub_node where host=%(Host)s "
"or host like %(Pattern1)s "
"or host like %(Pattern2)s %ESCAPE")) of
{selected, RItems} ->
[raw_to_node(Host, Item) || Item <- RItems];
_ ->
[]
end.
get_parentnodes(Host, Node, _From) ->
case get_node(Host, Node) of
Record when is_record(Record, pubsub_node) ->
+5 -1
View File
@@ -38,7 +38,8 @@
-export([init/3, terminate/2, options/0, set_node/1,
get_node/3, get_node/2, get_node/1, get_nodes/2,
get_nodes/1, get_parentnodes/3, get_parentnodes_tree/3,
get_nodes/1, get_all_nodes/1,
get_parentnodes/3, get_parentnodes_tree/3,
get_subnodes/3, get_subnodes_tree/3, create_node/6,
delete_node/2]).
@@ -71,6 +72,9 @@ get_nodes(Host) ->
get_nodes(_Host, _Limit) ->
[].
get_all_nodes(_Host) ->
[].
get_parentnodes(_Host, _Node, _From) ->
[].
+14 -1
View File
@@ -191,13 +191,26 @@ base_url(Server, Path) ->
_ -> Url
end.
-ifdef(HAVE_URI_STRING).
uri_hack(Str) ->
case uri_string:normalize("%25") of
"%" -> % This hack around bug in httpc >21 <23.2
binary:replace(Str, <<"%25">>, <<"%2525">>, [global]);
_ -> Str
end.
-else.
uri_hack(Str) ->
Str.
-endif.
url(Url, []) ->
Url;
url(Url, Params) ->
L = [<<"&", (iolist_to_binary(Key))/binary, "=",
(misc:url_encode(Value))/binary>>
|| {Key, Value} <- Params],
<<$&, Encoded/binary>> = iolist_to_binary(L),
<<$&, Encoded0/binary>> = iolist_to_binary(L),
Encoded = uri_hack(Encoded0),
<<Url/binary, $?, Encoded/binary>>.
url(Server, Path, Params) ->
case binary:split(base_url(Server, Path), <<"?">>) of
+15 -1
View File
@@ -489,6 +489,14 @@ wait_for_complete(Config, N) ->
end
end, error, [0, 100, 200, 2000, 5000, 10000]).
xevent_stored(#message{body = [], subject = []}, _) -> false;
xevent_stored(#message{type = T}, _) when T /= chat, T /= normal -> false;
xevent_stored(_, #xevent{id = undefined}) -> true;
xevent_stored(_, #xevent{offline = true}) -> true;
xevent_stored(_, #xevent{delivered = true}) -> true;
xevent_stored(_, #xevent{displayed = true}) -> true;
xevent_stored(_, _) -> false.
message_iterator(Config) ->
ServerJID = server_jid(Config),
ChatStates = [[#chatstate{type = composing}]],
@@ -511,8 +519,14 @@ message_iterator(Config) ->
fun(#message{type = error}) -> true;
(#message{type = groupchat}) -> false;
(#message{sub_els = [#hint{type = store}|_]}) when MamEnabled -> true;
(#message{sub_els = [#hint{type = 'no-store'}|_]}) -> false;
(#message{sub_els = [#offline{}|_]}) when not MamEnabled -> false;
(#message{sub_els = [_, #xevent{id = I}]}) when I /= undefined, not MamEnabled -> false;
(#message{sub_els = [#hint{type = store}, #xevent{} = Event | _]} = Msg) when not MamEnabled ->
xevent_stored(Msg#message{body = body, type = chat}, Event);
(#message{sub_els = [#xevent{} = Event]} = Msg) when not MamEnabled ->
xevent_stored(Msg, Event);
(#message{sub_els = [_, #xevent{} = Event | _]} = Msg) when not MamEnabled ->
xevent_stored(Msg, Event);
(#message{sub_els = [#xevent{id = I}]}) when I /= undefined, not MamEnabled -> false;
(#message{sub_els = [#hint{type = store}|_]}) -> true;
(#message{sub_els = [#hint{type = 'no-store'}|_]}) -> false;
+9 -1
View File
@@ -224,13 +224,21 @@ get_items(Config, Version) ->
sub_els = [#roster_query{ver = Version}]}) of
#iq{type = result,
sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
{NewVersion, Items};
{NewVersion, normalize_items(Items)};
#iq{type = result, sub_els = []} ->
{empty, []};
#iq{type = error} = Err ->
xmpp:get_error(Err)
end.
normalize_items(Items) ->
Items2 =
lists:map(
fun(I) ->
I#roster_item{groups = lists:sort(I#roster_item.groups)}
end, Items),
lists:sort(Items2).
get_item(Config, JID) ->
case get_items(Config) of
{_Ver, Items} when is_list(Items) ->
+2 -1
View File
@@ -42,7 +42,8 @@ INTRUDER()
{
NUMBERS=$(echo "$INPUT" | grep -o . | tr '\n' ' ')
SORTED_UNIQ_NUM=$(echo "${NUMBERS[@]}" | sort -u | tr '\n' ' ')
RANDOM_DIGITS=$(echo 123456789 | grep -o . | sort -R | tr '\n' ' ')
SORT_RANDOM_CMD="$( ( echo x|sort -R >&/dev/null && echo "sort -R" ) || ( echo x|shuf >&/dev/null && echo shuf ) || echo cat)"
RANDOM_DIGITS=$(echo 123456789 | grep -o . | eval "$SORT_RANDOM_CMD" | tr '\n' ' ')
INTRUDER=-1
for i in $RANDOM_DIGITS