Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 601fcba4cb | |||
| cc3a9f7722 | |||
| 296ef8287f | |||
| b5dab24679 | |||
| 319414b985 | |||
| 6b0058c89c | |||
| 855c828d1f | |||
| c72ef537ee | |||
| 11e86811a0 | |||
| 2183f60917 | |||
| 05f0fbf195 | |||
| 853da159de | |||
| 643ae7e5f9 | |||
| 322e25d18e | |||
| bdd8ba115e | |||
| 71ad7c368d | |||
| 8be0f8a0b0 | |||
| e9e678a994 | |||
| a89152a3d7 | |||
| 46d5ab369f | |||
| 81906b74ed | |||
| 74b0f64645 | |||
| 50948d1619 | |||
| 38f1132192 | |||
| 50ef49d190 | |||
| 3669ac8aed | |||
| 7f8519c0af | |||
| 859ba3e0c2 | |||
| 94c2a115d5 | |||
| 634b646711 | |||
| 2a9dc2c7b2 | |||
| ef93a5359b | |||
| efb1fc9b3f | |||
| aa5faf1f36 | |||
| 4723283896 | |||
| 36187e07d0 | |||
| a9583b43c3 | |||
| ca54f81f58 | |||
| b4399291ef | |||
| a8df58f056 | |||
| 54a89b39fb | |||
| 4a931b42ab | |||
| 15d73b9d20 | |||
| c2d4f73893 | |||
| 31b85351f2 | |||
| c900f0ad83 | |||
| b2e6749fd2 | |||
| 2437dc4e06 | |||
| 115e7d08aa | |||
| d9ddbe0212 | |||
| 512285e48d | |||
| 642e7ecc29 | |||
| ce5a8acaf7 | |||
| 3d9a5a1635 | |||
| 3469a51f58 | |||
| d4b30957a3 | |||
| cc377bbebf | |||
| eec836239f | |||
| 941d51a6e7 | |||
| e3243fa35b | |||
| 70512c7116 | |||
| 3c896d1c6a | |||
| b455d93c69 | |||
| 017b2feac1 | |||
| 5f47860ee1 | |||
| 795498fa45 | |||
| aa1717ee77 | |||
| db6d3e63f2 | |||
| 0304428d95 | |||
| 74b80bfe08 | |||
| 90ce65e4dc | |||
| d4d9771a71 | |||
| 70ee294079 | |||
| 5f4d17621f | |||
| 8f4179050b | |||
| fddacd51a4 | |||
| e726ba9a8b | |||
| da1673e264 | |||
| 7cd34d3709 | |||
| ec7fd05987 | |||
| 34a58863e3 | |||
| 3237a955e5 | |||
| 8e7489c2be | |||
| df5202a2f0 | |||
| db3a5d8915 | |||
| 6110f213de | |||
| e99fe98db4 | |||
| b07e28be2c | |||
| 937d5fe495 | |||
| 01141e5f69 | |||
| 15569d0b13 | |||
| 918806006c | |||
| d5e030a638 | |||
| 47f1beca0c | |||
| a565b0e4c1 | |||
| 10d1704899 | |||
| bc7c8e3952 | |||
| 4e35515a8c | |||
| d2a3fe3ed2 | |||
| 9bd1b39f9f | |||
| 20a01a25e4 | |||
| 68e69debff | |||
| 7a333eecbe | |||
| e274bcc87d | |||
| c42cb2bbac | |||
| 9ee7054823 | |||
| 65e16dcac1 | |||
| 97c2d6a29d | |||
| 93cdee80ed | |||
| 11055f61a6 | |||
| af99799e8a | |||
| 926f60b2ed | |||
| 0fdbb03f54 | |||
| bd0332c716 | |||
| 45007809f6 | |||
| 0e0fdb440c | |||
| 2016cf547f | |||
| ead87e3727 | |||
| f56739fd9f | |||
| c5f2b389c3 | |||
| 4cc95dda42 | |||
| 0bfbe3e154 | |||
| 9bd6b11007 | |||
| c013a59d16 | |||
| aa02c4de1e | |||
| fb9e2b9603 | |||
| a70bdc0776 | |||
| 936460212f | |||
| 8ac51e63b5 | |||
| 090a8e3c95 | |||
| 101cce0c1e | |||
| a935302a19 | |||
| 9a0ff13cc2 | |||
| ef933c07cc | |||
| df5291e4bd | |||
| 7a8c0331c1 | |||
| 28e37bcaad | |||
| 8a3344e78a | |||
| ce95f1f25a | |||
| f1739ce34d | |||
| 1add1de23b | |||
| e25bdba16d | |||
| 7c63cd1000 | |||
| 4192190a96 | |||
| a733ba311c | |||
| fe472a63a0 | |||
| 25b78b73d0 | |||
| b978a47925 | |||
| b44b1304b8 | |||
| a4fd756eae | |||
| 8d4c1e3617 | |||
| 54f5db851d | |||
| 35042ebc6a | |||
| 6c2dfd3d31 | |||
| 543b874a10 | |||
| 4258d3dc24 | |||
| 949649e3a9 | |||
| 82d95ac81d | |||
| 3124644315 |
@@ -68,8 +68,8 @@ jobs:
|
||||
|
||||
- name: Prepare databases
|
||||
run: |
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test -i /mssql.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test -i /mssql.sql
|
||||
sudo systemctl start mysql.service
|
||||
sudo systemctl start postgresql.service
|
||||
mysql -u root -proot -e "CREATE DATABASE ejabberd_test;"
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
- name: Remove syntax_tools from release
|
||||
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
|
||||
|
||||
- name: Cache rebar
|
||||
- name: Cache Hex.pm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@@ -133,6 +133,8 @@ jobs:
|
||||
- run: make xref
|
||||
- run: make dialyzer
|
||||
- run: make test-eunit
|
||||
- run: make elvis
|
||||
if: matrix.otp >= 23
|
||||
|
||||
- name: Check Production Release
|
||||
run: |
|
||||
@@ -145,12 +147,30 @@ jobs:
|
||||
cat $RE/logs/ejabberd.log
|
||||
grep -q "is stopped in" $RE/logs/ejabberd.log
|
||||
|
||||
- name: Check Development Release
|
||||
- name: Start Development Release
|
||||
run: |
|
||||
make dev
|
||||
RE=_build/dev/rel/ejabberd
|
||||
sed -i 's/starttls_required: true/starttls_required: false/g' $RE/conf/ejabberd.yml
|
||||
$RE/bin/ejabberdctl start
|
||||
$RE/bin/ejabberdctl started
|
||||
$RE/bin/ejabberdctl register admin localhost admin
|
||||
grep -q "is started in" $RE/logs/ejabberd.log
|
||||
|
||||
- name: Run XMPP Interoperability Tests against CI server.
|
||||
if: matrix.otp == '26'
|
||||
continue-on-error: true
|
||||
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.4.0
|
||||
with:
|
||||
domain: 'localhost'
|
||||
adminAccountUsername: 'admin'
|
||||
adminAccountPassword: 'admin'
|
||||
disabledSpecifications: RFC6121,XEP-0030,XEP-0045,XEP-0054,XEP-0060,XEP-0080,XEP-0115,XEP-0118,XEP-0215,XEP-0347,XEP-0363,XEP-0384
|
||||
|
||||
- name: Stop Development Release
|
||||
if: always()
|
||||
run: |
|
||||
RE=_build/dev/rel/ejabberd
|
||||
$RE/bin/ejabberdctl stop
|
||||
$RE/bin/ejabberdctl stopped
|
||||
cat $RE/logs/ejabberd.log
|
||||
@@ -256,12 +276,12 @@ jobs:
|
||||
run: |
|
||||
[[ -d logs ]] && rm -rf logs
|
||||
[[ -d _build/test/logs ]] && rm -rf _build/test/logs || true
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -Q "drop database [ejabberd_test];"
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -Q "drop login [ejabberd_test];"
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -Q "drop database [ejabberd_test];"
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -Q "drop login [ejabberd_test];"
|
||||
mysql -u root -proot -e "DROP DATABASE ejabberd_test;"
|
||||
sudo -u postgres psql -c "DROP DATABASE ejabberd_test;"
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools/bin/sqlcmd -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test -i /mssql.new.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
|
||||
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd -C -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test -i /mssql.new.sql
|
||||
mysql -u root -proot -e "CREATE DATABASE ejabberd_test;"
|
||||
mysql -u root -proot -e "GRANT ALL ON ejabberd_test.*
|
||||
TO 'ejabberd_test'@'localhost';"
|
||||
|
||||
@@ -61,6 +61,13 @@ jobs:
|
||||
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
|
||||
libsqlite3-dev libwebp-dev libyaml-dev
|
||||
|
||||
- name: Cache Hex.pm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/rebar3/
|
||||
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
./autogen.sh
|
||||
@@ -204,6 +211,13 @@ jobs:
|
||||
sed -i 's|^{deps, \[\(.*\)|{deps, [{decimal, ".*", {git, "https://github.com/ericmj/decimal", {branch, "main"}}},\n \1|g' rebar.config
|
||||
cat rebar.config
|
||||
|
||||
- name: Cache Hex.pm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/rebar3/
|
||||
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
./autogen.sh
|
||||
@@ -347,6 +361,13 @@ jobs:
|
||||
run: |
|
||||
mix deps.unlock jose
|
||||
|
||||
- name: Cache Hex.pm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.hex/
|
||||
key: ${{matrix.otp}}-${{hashFiles('mix.exs')}}
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
./autogen.sh
|
||||
|
||||
Vendored
+17
@@ -1,6 +1,23 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Relive (Vim)",
|
||||
"type": "erlang",
|
||||
"request": "launch",
|
||||
"runinterminal": [
|
||||
"./rebar3", "shell",
|
||||
"--apps", "ejabberd",
|
||||
"--config", "rel/relive.config",
|
||||
"--script", "rel/relive.escript",
|
||||
"--name", "ejabberd@localhost",
|
||||
"--setcookie", "COOKIE"
|
||||
],
|
||||
"projectnode": "ejabberd@localhost",
|
||||
"cookie": "COOKIE",
|
||||
"timeout": 900,
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"name": "Relive",
|
||||
"type": "erlang",
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
[ ! -f Makefile ] \
|
||||
&& ./autogen.sh \
|
||||
&& ./configure --with-rebar=./rebar3 \
|
||||
&& ./configure --with-rebar=rebar3 \
|
||||
&& make deps
|
||||
|
||||
make relive
|
||||
|
||||
+166
-31
@@ -1,3 +1,138 @@
|
||||
## Version 24.10
|
||||
|
||||
#### Miscelanea
|
||||
|
||||
- `ejabberd_c2s`: Optionally allow unencrypted SASL2
|
||||
- `ejabberd_system_monitor`: Handle call by `gen_event:swap_handler` ([#4233](https://github.com/processone/ejabberd/issues/4233))
|
||||
- `ejabberd_http_ws`: Remove support for old websocket connection protocol
|
||||
- `ejabberd_stun`: Omit `auth_realm` log message
|
||||
- `ext_mod`: Handle `info` message when contrib module transfers table ownership
|
||||
- `mod_block_strangers`: Add feature announcement to disco-info ([#4039](https://github.com/processone/ejabberd/issues/4039))
|
||||
- `mod_mam`: Advertise XEP-0424 feature in server disco-info ([#3340](https://github.com/processone/ejabberd/issues/3340))
|
||||
- `mod_muc_admin`: Better handling of malformed jids in `send_direct_invitation` command
|
||||
- `mod_muc_rtbl`: Fix call to `gen_server:stop` ([#4260](https://github.com/processone/ejabberd/issues/4260))
|
||||
- `mod_privilege`: Support "IQ permission" from XEP-0356 0.4.1 ([#3889](https://github.com/processone/ejabberd/issues/3889))
|
||||
- `mod_pubsub`: Don't blindly echo PEP notification
|
||||
- `mod_pubsub`: Skip non-delivery errors for local pubsub generated notifications
|
||||
- `mod_pubsub`: Fall back to default plugin options
|
||||
- `mod_pubsub`: Fix choice of node config defaults
|
||||
- `mod_pubsub`: Fix merging of default node options
|
||||
- `mod_pubsub`: Fix default node config parsing
|
||||
- `mod_register`: Support to block IPs in a vhost using `append_host_config` ([#4038](https://github.com/processone/ejabberd/issues/4038))
|
||||
- `mod_s2s_bidi`: Add support for S2S Bidirectional
|
||||
- `mod_scram_upgrade`: Add support for SCRAM upgrade tasks
|
||||
- `mod_vcard`: Return error stanza when storage doesn't support vcard update ([#4266](https://github.com/processone/ejabberd/issues/4266))
|
||||
- `mod_vcard`: Return explicit error stanza when user attempts to modify other's vcard
|
||||
- Minor improvements to support `mod_tombstones` (#2456)
|
||||
- Update `fast_xml` to use `use_maps` and remove obsolete elixir files
|
||||
- Update `fast_tls` and `xmpp` to improve s2s fallback for invalid direct tls connections
|
||||
- `make-binaries`: Bump dependency versions: Elixir 1.17.2, OpenSSL 3.3.2, ...
|
||||
|
||||
#### Administration
|
||||
- `ejabberdctl`: If `ERLANG_NODE` lacks host, add hostname ([#4288](https://github.com/processone/ejabberd/issues/4288))
|
||||
- `ejabberd_app`: At server start, log Erlang and Elixir versions
|
||||
- MySQL: Fix column type in the schema update of `archive` table in schema update
|
||||
|
||||
#### Commands API
|
||||
- `get_mam_count`: New command to get number of archived messages for an account
|
||||
- `set_presence`: Return error when session not found
|
||||
- `update`: Fix command output
|
||||
- Add `mam` and `offline` tags to the related purge commands
|
||||
|
||||
#### Code Quality
|
||||
- Fix warnings about unused macro definitions reported by Erlang LS
|
||||
- Fix Elvis report: Fix dollar space syntax
|
||||
- Fix Elvis report: Remove spaces in weird places
|
||||
- Fix Elvis report: Don't use ignored variables
|
||||
- Fix Elvis report: Remove trailing whitespace characters
|
||||
- Define the types of options that `opt_type.sh` cannot derive automatically
|
||||
- `ejabberd_http_ws`: Fix dialyzer warnings
|
||||
- `mod_matrix_gw`: Remove useless option `persist`
|
||||
- `mod_privilege`: Replace `try...catch` with a clean alternative
|
||||
|
||||
#### Development Help
|
||||
- `elvis.config`: Fix file syntax, set vim mode, disable many tests
|
||||
- `erlang_ls.config`: Let it find paths, update to Erlang 26, enable crossref
|
||||
- `hooks_deps`: Hide false-positive warnings about `gen_mod`
|
||||
- `Makefile`: Add support for `make elvis` when using rebar3
|
||||
- `.vscode/launch.json`: Experimental support for debugging with Neovim
|
||||
- CI: Add Elvis tests
|
||||
- CI: Add XMPP Interop tests
|
||||
- Runtime: Cache hex.pm archive from rebar3 and mix
|
||||
|
||||
#### Documentation
|
||||
- Add links in top-level options documentation to their Docs website sections
|
||||
- Document which SQL servers can really use `update_sql_schema`
|
||||
- Improve documentation of `ldap_servers` and `ldap_backups` options ([#3977](https://github.com/processone/ejabberd/issues/3977))
|
||||
- `mod_register`: Document behavior when `access` is set to `none` ([#4078](https://github.com/processone/ejabberd/issues/4078))
|
||||
|
||||
#### Elixir
|
||||
- Handle case when elixir support is enabled but not available
|
||||
- Start ExSync manually to ensure it's started if (and only if) Relive
|
||||
- `mix.exs`: Fix `mix release` error: `logger` being regular and included application ([#4265](https://github.com/processone/ejabberd/issues/4265))
|
||||
- `mix.exs`: Remove from `extra_applications` the apps already defined in `deps` ([#4265](https://github.com/processone/ejabberd/issues/4265))
|
||||
|
||||
#### WebAdmin
|
||||
- Add links in user page to offline and roster pages
|
||||
- Add new "MAM Archive" page to webadmin
|
||||
- Improve many pages to handle when modules are disabled
|
||||
- `mod_admin_extra`: Move some webadmin pages to their modules
|
||||
|
||||
## Version 24.07
|
||||
|
||||
#### Core
|
||||
|
||||
- `ejabberd_options`: Add trailing `@` to `@VERSION@` parsing
|
||||
- `mod_http_api`: Fix problem parsing tuples when using OTP 27 json library ([#4242](https://github.com/processone/ejabberd/issues/4242))
|
||||
- `mod_http_api`: Restore args conversion of `{"k":"v"}` to tuple lists
|
||||
- `mod_matrix_gw`: Add misc:json_encode_With_kv_lists and use it in matrix sign function
|
||||
- `mod_muc`: Output `muc#roominfo_avatarhash` in room disco info as per updated XEP-0486 ([#4234](https://github.com/processone/ejabberd/issues/4234))
|
||||
- `mod_muc`: Improve cross version handling of muc retractions
|
||||
- `node_pep`: Add missing feature `item-ids` to node_pep
|
||||
- `mod_register`: Send welcome message as `chat` too ([#4246](https://github.com/processone/ejabberd/issues/4246))
|
||||
- `ejabberd_hooks`: Support for ejabberd hook subscribers, useful for [mod_prometheus](https://github.com/processone/ejabberd-contrib/tree/master/mod_prometheus)
|
||||
- `ejabberd.app`: Don't add `iex` to included_applications
|
||||
- `make-installers`: Fix path in scripts in regular user install ([#4258](https://github.com/processone/ejabberd/issues/4258))
|
||||
- Test: New tests for API commands
|
||||
|
||||
#### Documentation
|
||||
|
||||
- `mod_matrix_gw`: Fix `matrix_id_as_jid` option documentation
|
||||
- `mod_register`: Add example configuration of `welcome_message` option
|
||||
- `mix.exs`: Add ejabberd example config files to the hex package
|
||||
- Update `CODE_OF_CONDUCT.md`
|
||||
|
||||
#### ext_mod
|
||||
|
||||
- Fetch dependencies from hex.pm when mix is available
|
||||
- files_to_path is deprecated, use compile_to_path
|
||||
- Compile all Elixir files in a library with one function call
|
||||
- Improve error result when problem compiling elixir file
|
||||
- Handle case when contrib module has no `*.ex` and no `*.erl`
|
||||
- `mix.exs`: Include Elixir's Logger in the OTP release, useful for [mod_libcluster](https://github.com/processone/ejabberd-contrib/tree/master/mod_libcluster)
|
||||
|
||||
#### Logs
|
||||
|
||||
- Print message when starting ejabberd application fails
|
||||
- Use error_logger when printing startup failure message
|
||||
- Use proper format depending on the formatter ([#4256](https://github.com/processone/ejabberd/issues/4256))
|
||||
|
||||
#### SQL
|
||||
|
||||
- Add option `update_sql_schema_timeout` to allow schema update use longer timeouts
|
||||
- Add ability to specify custom timeout for sql operations
|
||||
- Allow to configure number of restart in `sql_transaction()`
|
||||
- Make sql query in testsuite compatible with pg9.1
|
||||
- In `mysql.sql`, fix update instructions for the `archive` table, `origin_id` column ([#4259](https://github.com/processone/ejabberd/issues/4259))
|
||||
|
||||
#### WebAdmin
|
||||
|
||||
- `ejabberd.yml.example`: Add `api_permissions` group for webadmin ([#4249](https://github.com/processone/ejabberd/issues/4249))
|
||||
- Don't use host from url in webadmin, prefer host used for authentication
|
||||
- Fix number of accounts shown in the online-users page
|
||||
- Fix crash when viewing old shared roster groups ([#4245](https://github.com/processone/ejabberd/issues/4245))
|
||||
- Support groupid with spaces when making shared roster result ([#4245](https://github.com/processone/ejabberd/issues/4245))
|
||||
|
||||
## Version 24.06
|
||||
|
||||
#### Core
|
||||
@@ -5,49 +140,49 @@
|
||||
- `econf`: Add ability to use additional custom errors when parsing options
|
||||
- `ejabberd_logger`: Reloading configuration will update logger settings
|
||||
- `gen_mod`: Add support to specify a hook global, not vhost-specific
|
||||
- [`mod_configure`](https://docs.ejabberd.im/admin/configuration/modules/#mod_configure): Retract `Get User Password` command to update [XEP-0133](https://xmpp.org/extensions/xep-0133.html) 1.3.0
|
||||
- [`mod_conversejs`](https://docs.ejabberd.im/admin/configuration/modules/#mod_conversejs): Simplify support for `@HOST@` in `default_domain` option ([#4167](https://github.com/processone/issues/4167))
|
||||
- [`mod_mam`](https://docs.ejabberd.im/admin/configuration/modules/#mod_mam): Document that [XEP-0441](https://xmpp.org/extensions/xep-0441.html) is implemented as well
|
||||
- [`mod_mam`](https://docs.ejabberd.im/admin/configuration/modules/#mod_mam): Update support for [XEP-0425](https://xmpp.org/extensions/xep-0425.html) version 0.3.0, keep supporting 0.2.1 ([#4193](https://github.com/processone/issues/4193))
|
||||
- [`mod_matrix_gw`](https://docs.ejabberd.im/admin/configuration/modules/#mod_matrix_gw): Fix support for `@HOST@` in `matrix_domain` option ([#4167](https://github.com/processone/issues/4167))
|
||||
- [`mod_muc_log`](https://docs.ejabberd.im/admin/configuration/modules/#mod_muc_log): Hide join/leave lines, add method to show them
|
||||
- [`mod_muc_log`](https://docs.ejabberd.im/admin/configuration/modules/#mod_muc_log): Support `allowpm` introduced in 2bd61ab
|
||||
- [`mod_muc_room`](https://docs.ejabberd.im/admin/configuration/modules/#mod_muc_room): Use ejabberd hooks instead of function calls to `mod_muc_log` ([#4191](https://github.com/processone/issues/4191))
|
||||
- [`mod_private`](https://docs.ejabberd.im/admin/configuration/modules/#mod_private): Cope with bookmark decoding errors
|
||||
- [`mod_vcard_xupdate`](https://docs.ejabberd.im/admin/configuration/modules/#mod_vcard_xupdate): Send hash after avatar get set for first time
|
||||
- `prosody2ejabberd`: Handle the `approved` attribute. As feature isn't implemented, discard it ([#4188](https://github.com/processone/issues/4188))
|
||||
- `mod_configure`: Retract `Get User Password` command to update XEP-0133 1.3.0
|
||||
- `mod_conversejs`: Simplify support for `@HOST@` in `default_domain` option ([#4167](https://github.com/processone/ejabberd/issues/4167))
|
||||
- `mod_mam`: Document that XEP-0441 is implemented as well
|
||||
- `mod_mam`: Update support for XEP-0425 version 0.3.0, keep supporting 0.2.1 ([#4193](https://github.com/processone/ejabberd/issues/4193))
|
||||
- `mod_matrix_gw`: Fix support for `@HOST@` in `matrix_domain` option ([#4167](https://github.com/processone/ejabberd/issues/4167))
|
||||
- `mod_muc_log`: Hide join/leave lines, add method to show them
|
||||
- `mod_muc_log`: Support `allowpm` introduced in 2bd61ab
|
||||
- `mod_muc_room`: Use ejabberd hooks instead of function calls to `mod_muc_log` ([#4191](https://github.com/processone/ejabberd/issues/4191))
|
||||
- `mod_private`): Cope with bookmark decoding errors
|
||||
- `mod_vcard_xupdate`: Send hash after avatar get set for first time
|
||||
- `prosody2ejabberd`: Handle the `approved` attribute. As feature isn't implemented, discard it ([#4188](https://github.com/processone/ejabberd/issues/4188))
|
||||
|
||||
#### SQL
|
||||
|
||||
- [`update_sql_schema`](https://docs.ejabberd.im/admin/configuration/toplevel/#update_sql_schema): Enable this option by default
|
||||
- `update_sql_schema`: Enable this option by default
|
||||
- CI: Don't load database schema files for mysql and pgsql
|
||||
- Support Unix Domain Socket with updated p1_pgsql and p1_mysql ([#3716](https://github.com/processone/issues/3716))
|
||||
- Support Unix Domain Socket with updated p1_pgsql and p1_mysql ([#3716](https://github.com/processone/ejabberd/issues/3716))
|
||||
- Fix handling of `mqtt_pub` table definition from `mysql.sql` and fix `should_update_schema/1` in `ejabberd_sql_schema.erl`
|
||||
- Don't start sql connection pools for unknown hosts
|
||||
- Add `update_primary_key` command to sql schema updater
|
||||
- Fix crash running [`export2sql`](https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#export2sql) when MAM enabled but MUC disabled
|
||||
- Fix crash running `export2sql` when MAM enabled but MUC disabled
|
||||
- Improve detection of types in odbc
|
||||
|
||||
#### Commands API
|
||||
|
||||
- New ban commands use private storage to keep ban information ([#4201](https://github.com/processone/issues/4201))
|
||||
- [`join_cluster_here`](https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#join_cluster_here): New command to join a remote node into our local cluster
|
||||
- Don't name integer and string results in API examples ([#4198](https://github.com/processone/issues/4198))
|
||||
- [`get_user_subscriptions`](https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#get_user_subscriptions): Fix validation of user field in that command
|
||||
- [`mod_admin_extra`](https://docs.ejabberd.im/admin/configuration/modules/#mod_admin_extra): Handle case when `mod_private` is not enabled ([#4201](https://github.com/processone/issues/4201))
|
||||
- [`mod_muc_admin`](https://docs.ejabberd.im/admin/configuration/modules/#mod_muc_admin): Improve validation of arguments in several commands
|
||||
- New ban commands use private storage to keep ban information ([#4201](https://github.com/processone/ejabberd/issues/4201))
|
||||
- `join_cluster_here`: New command to join a remote node into our local cluster
|
||||
- Don't name integer and string results in API examples ([#4198](https://github.com/processone/ejabberd/issues/4198))
|
||||
- `get_user_subscriptions`: Fix validation of user field in that command
|
||||
- `mod_admin_extra`: Handle case when `mod_private` is not enabled ([#4201](https://github.com/processone/ejabberd/issues/4201))
|
||||
- `mod_muc_admin`: Improve validation of arguments in several commands
|
||||
|
||||
#### Compile
|
||||
|
||||
- `ejabberdctl`: Comment ERTS_VSN variable when not used ([#4194](https://github.com/processone/issues/4194))
|
||||
- `ejabberdctl`: Comment ERTS_VSN variable when not used ([#4194](https://github.com/processone/ejabberd/issues/4194))
|
||||
- `ejabberdctl`: Fix iexlive after `make prod` when using Elixir
|
||||
- `ejabberdctl`: If `INET_DIST_INTERFACE` is IPv6, set required option ([#4189](https://github.com/processone/issues/4189))
|
||||
- `ejabberdctl`: If `INET_DIST_INTERFACE` is IPv6, set required option ([#4189](https://github.com/processone/ejabberd/issues/4189))
|
||||
- `ejabberdctl`: Make native dynamic node names work when using fully qualified domain names
|
||||
- `rebar.config.script`: Support relaxed dependency version ([#4192](https://github.com/processone/issues/4192))
|
||||
- `rebar.config.script`: Support relaxed dependency version ([#4192](https://github.com/processone/ejabberd/issues/4192))
|
||||
- `rebar.config`: Update deps version to rebar3's relaxed versioning
|
||||
- `rebar.lock`: Track file, now that rebar3 uses loose dependency versioning
|
||||
- `configure.ac`: When using rebar3, unlock dependencies that are disabled ([#4212](https://github.com/processone/issues/4212))
|
||||
- `configure.ac`: When using rebar3 with old Erlang, unlock some dependencies ([#4213](https://github.com/processone/issues/4213))
|
||||
- `configure.ac`: When using rebar3, unlock dependencies that are disabled ([#4212](https://github.com/processone/ejabberd/issues/4212))
|
||||
- `configure.ac`: When using rebar3 with old Erlang, unlock some dependencies ([#4213](https://github.com/processone/ejabberd/issues/4213))
|
||||
- `mix:exs`: Move `xmpp` from `included_applications` to `applications`
|
||||
|
||||
#### Dependencies
|
||||
@@ -57,7 +192,7 @@
|
||||
- Jiffy: Use Json module when Erlang/OTP 27, jiffy with older ones
|
||||
- Jose: Update to the new 1.11.10 for Erlang/OTP higher than 23
|
||||
- Luerl: Update to 1.2.0 when OTP same or higher than 20, simplifies commit a09f222
|
||||
- P1_acme: Update to support Jose 1.11.10 and Ipv6 support ([#4170](https://github.com/processone/issues/4170))
|
||||
- P1_acme: Update to support Jose 1.11.10 and Ipv6 support ([#4170](https://github.com/processone/ejabberd/issues/4170))
|
||||
- P1_acme: Update to use Erlang's json library instead of jiffy when OTP 27
|
||||
- Port_compiler: Update to 1.15.0 that supports Erlang/OTP 27.0
|
||||
|
||||
@@ -89,7 +224,7 @@
|
||||
- make-binaries: Bump OpenSSL to 3.3.1
|
||||
- make-binaries: Bump Linux-PAM to 1.6.1
|
||||
- make-binaries: Bump Expat to 2.6.2
|
||||
- make-binaries: Revert temporarily an OTP commit that breaks MSSQL ([#4178](https://github.com/processone/issues/4178))
|
||||
- make-binaries: Revert temporarily an OTP commit that breaks MSSQL ([#4178](https://github.com/processone/ejabberd/issues/4178))
|
||||
- CONTAINER.md: Invalid `CTL_ON_CREATE` usage in docker-compose example
|
||||
|
||||
#### WebAdmin
|
||||
@@ -116,8 +251,8 @@
|
||||
- Support tls-exporter channel binding
|
||||
- Support XEP-0474: SASL SCRAM Downgrade Protection
|
||||
- Fix presenting features and returning results of inline bind2 elements
|
||||
- [`disable_sasl_scram_downgrade_protection`](https://docs.ejabberd.im/admin/configuration/toplevel/#disable-sasl-scram-downgrade-protection): New option to disable XEP-0474
|
||||
- [`negotiation_timeout`](https://docs.ejabberd.im/admin/configuration/toplevel/#negotiation-timeout): Increase default value from 30s to 2m
|
||||
- `disable_sasl_scram_downgrade_protection`: New option to disable XEP-0474
|
||||
- `negotiation_timeout`: Increase default value from 30s to 2m
|
||||
- mod_carboncopy: Teach how to interact with bind2 inline requests
|
||||
|
||||
#### Other:
|
||||
@@ -155,10 +290,10 @@
|
||||
- ejabberdctl: Rework temporary node name generation
|
||||
- ejabberdctl: Print argument description, examples and note in help
|
||||
- ejabberdctl: Document exclusive ejabberdctl commands like all the others
|
||||
- Commands: Add a new [`muc_sub`](https://docs.ejabberd.im/developer/ejabberd-api/admin-tags/#muc-sub) tag to all the relevant commands
|
||||
- Commands: Add a new `muc_sub` tag to all the relevant commands
|
||||
- Commands: Improve syntax of many commands documentation
|
||||
- Commands: Use list arguments in many commands that used separators
|
||||
- Commands: [`set_presence`](https://docs.ejabberd.im/developer/ejabberd-api/admin-api/#set-presence): switch priority argument from string to integer
|
||||
- Commands: `set_presence`: switch priority argument from string to integer
|
||||
- ejabberd_commands: Add the command API version as [a tag `vX`](https://docs.ejabberd.im/developer/ejabberd-api/admin-tags/#v1)
|
||||
- ejabberd_ctl: Add support for list and tuple arguments
|
||||
- ejabberd_xmlrpc: Fix support for restuple error response
|
||||
|
||||
@@ -22,6 +22,21 @@ Examples of unacceptable behavior by participants include:
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Guidelines for Respectful and Efficient Communication on Issues, Discussions, and PRs
|
||||
|
||||
To ensure that our maintainers can efficiently manage issues and provide timely updates, we kindly ask that all comments on GitHub tickets remain relevant to the topic of the issue. Please avoid posting comments solely to ping maintainers or ask for updates. If you need information on the status of an issue, consider the following:
|
||||
|
||||
- **Check the Issue Timeline:** Review the existing comments and updates on the issue before posting.
|
||||
- **Use Reactions:** If you want to show that you are interested in an issue, use GitHub's reaction feature (e.g., thumbs up) instead of commenting.
|
||||
- **Be Patient:** Understand that maintainers may be working on multiple tasks and will provide updates as soon as possible.
|
||||
|
||||
Additionally, please be aware that:
|
||||
|
||||
- **User Responses:** Users who report issues may no longer be using the software, may have switched to other projects, or may simply be busy. It is their right not to respond to follow-up questions or comments.
|
||||
- **Maintainer Priorities:** Maintainers have the right to define their own priorities and schedule. They will address issues based on their availability and the project's needs.
|
||||
|
||||
By following these guidelines, you help us maintain a productive and respectful environment for everyone involved.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
[](https://github.com/processone/ejabberd/tags)
|
||||
[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
|
||||
|
||||
[](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
|
||||
[](https://hub.docker.com/r/ejabberd/ecs/)
|
||||
|
||||
`ejabberd` Container Image
|
||||
==========================
|
||||
|
||||
@@ -642,6 +642,13 @@ dialyzer: erlang_plt deps_plt ejabberd_plt
|
||||
endif
|
||||
endif
|
||||
|
||||
#.
|
||||
#' elvis
|
||||
#
|
||||
|
||||
elvis:
|
||||
$(REBAR) lint
|
||||
|
||||
#.
|
||||
#' test
|
||||
#
|
||||
@@ -699,6 +706,7 @@ help:
|
||||
@echo " indent Indent source code using erlang-mode [emacs]"
|
||||
@echo ""
|
||||
@echo " dialyzer Run Dialyzer static analyzer"
|
||||
@echo " elvis Run Elvis source code style reviewer [rebar3]"
|
||||
@echo " hooks Run hooks validator"
|
||||
@echo " test Run Common Tests suite [rebar3]"
|
||||
@echo " test-eunit Run EUnit suite [rebar3]"
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.59)
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 24.06` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 24.10` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="9.0.5 (Erlang/OTP 20.0)"
|
||||
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
|
||||
|
||||
|
||||
+20
-2
@@ -566,6 +566,15 @@
|
||||
<xmpp:note>mod_carboncopy</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0288.html"/>
|
||||
<xmpp:version>1.0.1</xmpp:version>
|
||||
<xmpp:since>24.10</xmpp:since>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:note>mod_s2s_bidi</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
|
||||
@@ -623,8 +632,8 @@
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0356.html"/>
|
||||
<xmpp:version>0.2.1</xmpp:version>
|
||||
<xmpp:since>16.09</xmpp:since>
|
||||
<xmpp:version>0.4.1</xmpp:version>
|
||||
<xmpp:since>24.10</xmpp:since>
|
||||
<xmpp:status></xmpp:status>
|
||||
<xmpp:note>mod_privilege</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
@@ -791,6 +800,15 @@
|
||||
<xmpp:note></xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0480.html"/>
|
||||
<xmpp:version>0.1</xmpp:version>
|
||||
<xmpp:since>24.10</xmpp:since>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:note>mod_scram_upgrade</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0485.html"/>
|
||||
|
||||
@@ -111,10 +111,13 @@ access_rules:
|
||||
|
||||
api_permissions:
|
||||
"console commands":
|
||||
from:
|
||||
- ejabberd_ctl
|
||||
from: ejabberd_ctl
|
||||
who: all
|
||||
what: "*"
|
||||
"webadmin commands":
|
||||
from: ejabberd_web_admin
|
||||
who: admin
|
||||
what: "*"
|
||||
"admin access":
|
||||
who:
|
||||
access:
|
||||
@@ -224,6 +227,7 @@ modules:
|
||||
ip_access: trusted_network
|
||||
mod_roster:
|
||||
versioning: true
|
||||
mod_s2s_bidi: {}
|
||||
mod_s2s_dialback: {}
|
||||
mod_shared_roster: {}
|
||||
mod_stream_mgmt:
|
||||
|
||||
@@ -66,6 +66,7 @@ done
|
||||
# shellcheck source=ejabberdctl.cfg.example
|
||||
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
||||
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%@*}" ] && ERLANG_NODE="$ERLANG_NODE@$(hostname -s)"
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
||||
: "${SPOOL_DIR:="{{spool_dir}}"}"
|
||||
: "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}"
|
||||
|
||||
+37
-21
@@ -5,38 +5,54 @@
|
||||
{config,
|
||||
[#{dirs => ["src"],
|
||||
filter => "*.erl",
|
||||
ignore => ['ELDAPv3', eldap_filter_yecc],
|
||||
ruleset => erl_files,
|
||||
rules => [{elvis_style, line_length, #{limit => 100,
|
||||
skip_comments => false}},
|
||||
rules => [{elvis_text_style, line_length, #{limit => 1000, skip_comments => false}},
|
||||
{elvis_text_style, no_tabs, disable},
|
||||
{elvis_style, no_debug_call, disable},
|
||||
{elvis_style, operator_spaces, disable},
|
||||
{elvis_style, atom_naming_convention, disable},
|
||||
{elvis_style, consistent_variable_casing, disable},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 70}},
|
||||
{elvis_style, export_used_types, disable},
|
||||
{elvis_style, function_naming_convention, disable},
|
||||
{elvis_style, god_modules, #{limit => 300}},
|
||||
{elvis_style, invalid_dynamic_call, disable},
|
||||
{elvis_style, variable_naming_convention, #{ regex => ".*" }},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
|
||||
]
|
||||
{elvis_style, macro_module_names, disable},
|
||||
{elvis_style, macro_names, disable},
|
||||
{elvis_style, max_function_arity, disable}, % #{max_arity => 15}},
|
||||
{elvis_style, nesting_level, disable},
|
||||
{elvis_style, no_author, disable},
|
||||
{elvis_style, no_catch_expressions, disable},
|
||||
{elvis_style, no_debug_call, disable},
|
||||
{elvis_style, no_if_expression, disable},
|
||||
{elvis_style, no_import, disable},
|
||||
{elvis_style, no_match_in_condition, disable},
|
||||
{elvis_style, no_nested_try_catch, disable},
|
||||
{elvis_style, no_single_clause_case, disable},
|
||||
{elvis_style, no_spec_with_records, disable},
|
||||
{elvis_style, no_throw, disable},
|
||||
{elvis_style, operator_spaces, disable},
|
||||
{elvis_style, param_pattern_matching, disable},
|
||||
{elvis_style, private_data_types, disable},
|
||||
{elvis_style, variable_naming_convention, disable}
|
||||
]
|
||||
},
|
||||
|
||||
%#{dirs => ["include"],
|
||||
% filter => "*.hrl",
|
||||
% ruleset => hrl_files},
|
||||
|
||||
#{dirs => ["."],
|
||||
filter => "Makefile.in",
|
||||
ruleset => makefiles,
|
||||
rules => [{elvis_style, line_length, #{limit => 100,
|
||||
rules => [{elvis_text_style, line_length, #{limit => 400,
|
||||
skip_comments => false}},
|
||||
{elvis_style, no_tabs, disable},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
|
||||
]
|
||||
},
|
||||
#{dirs => ["."],
|
||||
filter => "rebar.config",
|
||||
ruleset => rebar_config,
|
||||
rules => [{elvis_style, line_length, #{limit => 100,
|
||||
skip_comments => false}},
|
||||
{elvis_style, no_tabs, disable},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
|
||||
]
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
].
|
||||
|
||||
%% vim: set filetype=erlang tabstop=8:
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
otp_path: "/usr/lib/erlang"
|
||||
plt_path: "_build/default/rebar3_24.3.3_plt"
|
||||
#otp_path: "/usr/lib/erlang"
|
||||
#plt_path: "_build/default/rebar3_24.3.3_plt"
|
||||
#code_reload:
|
||||
# node: ejabberd@localhost
|
||||
apps_dirs:
|
||||
@@ -14,12 +14,12 @@ macros:
|
||||
- name: DEPRECATED_GET_STACKTRACE
|
||||
- name: HAVE_ERL_ERROR
|
||||
- name: HAVE_URI_STRING
|
||||
- name: OTP_BELOW_25
|
||||
- name: OTP_BELOW_27
|
||||
- name: SIP
|
||||
- name: STUN
|
||||
diagnostics:
|
||||
# enabled:
|
||||
# - crossref
|
||||
enabled:
|
||||
- crossref
|
||||
disabled:
|
||||
# - dialyzer
|
||||
- unused_includes # Otherwise it complains about unused logger.hrl
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
allow_visitor_nickchange = true :: boolean(),
|
||||
public = true :: boolean(),
|
||||
public_list = true :: boolean(),
|
||||
persistent = false :: boolean(),
|
||||
persistent = false :: boolean() | {destroying, boolean()},
|
||||
moderated = true :: boolean(),
|
||||
captcha_protected = false :: boolean(),
|
||||
members_by_default = true :: boolean(),
|
||||
|
||||
+245
-69
@@ -2,12 +2,12 @@
|
||||
.\" Title: ejabberd.yml
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
|
||||
.\" Date: 06/27/2024
|
||||
.\" Date: 10/28/2024
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "EJABBERD\&.YML" "5" "06/27/2024" "\ \&" "\ \&"
|
||||
.TH "EJABBERD\&.YML" "5" "10/28/2024" "\ \&" "\ \&"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
@@ -82,12 +82,12 @@ 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/24\&.06/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/24\&.10/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"
|
||||
.sp
|
||||
This section describes top level options of ejabberd 24\&.06\&. The options that changed in this version are marked with 🟤\&.
|
||||
This section describes top level options of ejabberd 24\&.10\&. The options that changed in this version are marked with 🟤\&.
|
||||
.PP
|
||||
\fBaccess_rules\fR: \fI{AccessName: {allow|deny: ACLRules|ACLName}}\fR
|
||||
.RS 4
|
||||
@@ -138,7 +138,8 @@ access_rules:
|
||||
.PP
|
||||
\fBacl\fR: \fI{ACLName: {ACLType: ACLValue}}\fR
|
||||
.RS 4
|
||||
The option defines access control lists: named sets of rules which are used to match against different targets (such as a JID or an IP address)\&. Every set of rules has name
|
||||
This option defines
|
||||
\fI\&.\&./configuration/basic\&.md#acl|access control lists\fR: named sets of rules which are used to match against different targets (such as a JID or an IP address)\&. Every set of rules has name
|
||||
\fIACLName\fR: it can be any string except
|
||||
\fIall\fR
|
||||
or
|
||||
@@ -319,7 +320,9 @@ 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:
|
||||
Define what
|
||||
\fIauthentication\&.md#anonymous\-login\-and\-sasl\-anonymous|anonymous\fR
|
||||
protocol will be used:
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
@@ -368,11 +371,8 @@ Define the permissions for API access\&. Please consult the ejabberd Docs web
|
||||
.PP
|
||||
\fBappend_host_config\fR: \fI{Host: Options}\fR
|
||||
.RS 4
|
||||
To define specific ejabberd modules in a virtual host, you can define the global
|
||||
\fImodules\fR
|
||||
option with the common modules, and later add specific modules to certain virtual hosts\&. To accomplish that,
|
||||
\fIappend_host_config\fR
|
||||
option can be used\&.
|
||||
Add a few specific options to a certain
|
||||
\fI\&.\&./configuration/basic\&.md#virtual\-hosting|virtual host\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBauth_cache_life_time\fR: \fItimeout()\fR
|
||||
@@ -412,7 +412,9 @@ and certificate based authentication)\&. This helps with processing offline mess
|
||||
.PP
|
||||
\fBauth_method\fR: \fI[mnesia | sql | anonymous | external | jwt | ldap | pam, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
A list of authentication methods to use\&. If several methods are defined, authentication is considered successful as long as authentication of at least one of the methods succeeds\&. The default value is
|
||||
A list of
|
||||
\fIauthentication\&.md|authentication\fR
|
||||
methods to use\&. If several methods are defined, authentication is considered successful as long as authentication of at least one of the methods succeeds\&. The default value is
|
||||
\fI[mnesia]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -682,7 +684,8 @@ A list of Erlang nodes to connect on ejabberd startup\&. This option is mostly i
|
||||
.PP
|
||||
\fBdefault_db\fR: \fImnesia | sql\fR
|
||||
.RS 4
|
||||
Default persistent storage for ejabberd\&. Modules and other components (e\&.g\&. authentication) may have its own value\&. The default value is
|
||||
\fIdatabase\&.md#default\-database|Default database\fR
|
||||
to store persistent data in ejabberd\&. Modules and other components (e\&.g\&. authentication) may have its own value\&. The default value is
|
||||
\fImnesia\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -694,7 +697,8 @@ Default volatile (in\-memory) storage for ejabberd\&. Modules and other componen
|
||||
.PP
|
||||
\fBdefine_macro\fR: \fI{MacroName: MacroValue}\fR
|
||||
.RS 4
|
||||
Defines a macro\&. The value can be any valid arbitrary YAML value\&. For convenience, it\(cqs recommended to define a
|
||||
Defines a
|
||||
\fI\&.\&./configuration/file\-format\&.md#macros\-in\-configuration\-file|macro\fR\&. The value can be any valid arbitrary YAML value\&. For convenience, it\(cqs recommended to define a
|
||||
\fIMacroName\fR
|
||||
in capital letters\&. Duplicated macros are not allowed\&. Macros are processed after additional configuration files have been included, so it is possible to use macros that are defined in configuration files included before the usage\&. It is possible to use a
|
||||
\fIMacroValue\fR
|
||||
@@ -742,7 +746,9 @@ which enables this extension\&.
|
||||
.PP
|
||||
\fBdomain_balancing\fR: \fI{Domain: Options}\fR
|
||||
.RS 4
|
||||
An algorithm to load balance the components that are plugged on an ejabberd cluster\&. It means that you can plug one or several instances of the same component on each ejabberd node and that the traffic will be automatically distributed\&. The algorithm to deliver messages to the component(s) can be specified by this option\&. For any component connected as
|
||||
An algorithm to
|
||||
\fI\&.\&./guide/clustering\&.md#service\-load\-balancing|load\-balance\fR
|
||||
the components that are plugged on an ejabberd cluster\&. It means that you can plug one or several instances of the same component on each ejabberd node and that the traffic will be automatically distributed\&. The algorithm to deliver messages to the component(s) can be specified by this option\&. For any component connected as
|
||||
\fIDomain\fR, available
|
||||
\fIOptions\fR
|
||||
are:
|
||||
@@ -821,18 +827,22 @@ Define the base URI when performing ReST requests\&. The default value is:
|
||||
.PP
|
||||
\fBextauth_pool_name\fR: \fIName\fR
|
||||
.RS 4
|
||||
Define the pool name appendix, so the full pool name will be
|
||||
Define the pool name appendix in
|
||||
\fIauthentication\&.md#external\-script|external auth\fR, so the full pool name will be
|
||||
\fIextauth_pool_Name\fR\&. The default value is the hostname\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBextauth_pool_size\fR: \fISize\fR
|
||||
.RS 4
|
||||
The option defines the number of instances of the same external program to start for better load balancing\&. The default is the number of available CPU cores\&.
|
||||
The option defines the number of instances of the same
|
||||
\fIauthentication\&.md#external\-script|external auth\fR
|
||||
program to start for better load balancing\&. The default is the number of available CPU cores\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBextauth_program\fR: \fIPath\fR
|
||||
.RS 4
|
||||
Indicate in this option the full path to the external authentication script\&. The script must be executable by ejabberd\&.
|
||||
Indicate in this option the full path to the
|
||||
\fIauthentication\&.md#external\-script|external authentication script\fR\&. The script must be executable by ejabberd\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBfqdn\fR: \fIDomain\fR
|
||||
@@ -851,7 +861,8 @@ for backward compatibility\&.
|
||||
.RS 4
|
||||
The option is used to redefine
|
||||
\fIOptions\fR
|
||||
for virtual host
|
||||
for
|
||||
\fI\&.\&./configuration/basic\&.md#virtual\-hosting|virtual host\fR
|
||||
\fIHost\fR\&. In the example below LDAP authentication method will be used on virtual host
|
||||
\fIdomain\&.tld\fR
|
||||
and SQL method will be used on virtual host
|
||||
@@ -882,7 +893,9 @@ host_config:
|
||||
.PP
|
||||
\fBhosts\fR: \fI[Domain1, Domain2, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
The option defines a list containing one or more domains that
|
||||
List of one or more
|
||||
\fI\&.\&./configuration/basic\&.md#host\-names|host names\fR
|
||||
(or domains) that
|
||||
\fIejabberd\fR
|
||||
will serve\&. This is a
|
||||
\fBmandatory\fR
|
||||
@@ -891,7 +904,9 @@ option\&.
|
||||
.PP
|
||||
\fBinclude_config_file\fR: \fI[Filename, \&.\&.\&.] | {Filename: Options}\fR
|
||||
.RS 4
|
||||
Read additional configuration from
|
||||
Read and
|
||||
\fI\&.\&./configuration/file\-format\&.md#include\-additional\-files|include additional file\fR
|
||||
from
|
||||
\fIFilename\fR\&. If the value is provided in
|
||||
\fI{Filename: Options}\fR
|
||||
format, the
|
||||
@@ -922,7 +937,9 @@ at start time\&. The default value is an empty list of modules:
|
||||
.PP
|
||||
\fBjwt_auth_only_rule\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
This ACL rule defines accounts that can use only this auth method, even if others are also defined in the ejabberd configuration file\&. In other words: if there are several auth methods enabled for this host (JWT, SQL, \&...), users that match this rule can only use JWT\&. The default value is
|
||||
This ACL rule defines accounts that can use only the
|
||||
\fIauthentication\&.md#jwt\-authentication|JWT\fR
|
||||
auth method, even if others are also defined in the ejabberd configuration file\&. In other words: if there are several auth methods enabled for this host (JWT, SQL, \&...), users that match this rule can only use JWT\&. The default value is
|
||||
\fInone\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -930,18 +947,24 @@ This ACL rule defines accounts that can use only this auth method, even if other
|
||||
.RS 4
|
||||
By default, the JID is defined in the
|
||||
\fI"jid"\fR
|
||||
JWT field\&. In this option you can specify other JWT field name where the JID is defined\&.
|
||||
JWT field\&. In this option you can specify other
|
||||
\fIauthentication\&.md#jwt\-authentication|JWT\fR
|
||||
field name where the JID is defined\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBjwt_key\fR: \fIFilePath\fR
|
||||
.RS 4
|
||||
Path to the file that contains the JWK Key\&. The default value is
|
||||
Path to the file that contains the
|
||||
\fIauthentication\&.md#jwt\-authentication|JWT\fR
|
||||
key\&. The default value is
|
||||
\fIundefined\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBlanguage\fR: \fILanguage\fR
|
||||
.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
|
||||
Define the
|
||||
\fI\&.\&./configuration/basic\&.md#default\-language|default language\fR
|
||||
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\&. The default value is
|
||||
\fI"en"\fR\&.
|
||||
@@ -949,9 +972,10 @@ attribute, the specified language is used\&. The default value is
|
||||
.PP
|
||||
\fBldap_backups\fR: \fI[Host, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
A list of IP addresses or DNS names of LDAP backup servers\&. When no servers listed in
|
||||
A list of IP addresses or DNS names of LDAP backup servers (see
|
||||
\fI\&.\&./configuration/ldap\&.md#ldap\-connection|LDAP connection\fR)\&. When no servers listed in
|
||||
\fIldap_servers\fR
|
||||
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\(cqt try to reconnect back to the main servers when they become operational again, so the only way to restore these connections is to restart ejabberd\&. This limitation might be fixed in future releases\&.
|
||||
option are reachable, ejabberd connects to these backup servers\&. The default is an empty list, i\&.e\&. no backup servers specified\&. Please notice that ejabberd only connects to the next server when the existing connection is lost; it doesn\(cqt detect when a previously\-attempted server becomes available again\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBldap_base\fR: \fIBase\fR
|
||||
@@ -1022,7 +1046,8 @@ Bind Distinguished Name\&. The default value is an empty string, which means "an
|
||||
.PP
|
||||
\fBldap_servers\fR: \fI[Host, \&.\&.\&.]\fR
|
||||
.RS 4
|
||||
A list of IP addresses or DNS names of your LDAP servers\&. The default value is
|
||||
A list of IP addresses or DNS names of your LDAP servers (see
|
||||
\fI\&.\&./configuration/ldap\&.md#ldap\-connection|LDAP connection\fR)\&. ejabberd connects immediately to all of them, and reconnects infinitely if connection is lost\&. The default value is
|
||||
\fI[localhost]\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -1114,7 +1139,8 @@ The size (in bytes) of a log file to trigger rotation\&. If set to
|
||||
.PP
|
||||
\fBloglevel\fR: \fInone | emergency | alert | critical | error | warning | notice | info | debug\fR
|
||||
.RS 4
|
||||
Verbosity of log files generated by ejabberd\&. The default value is
|
||||
Verbosity of ejabberd
|
||||
\fI\&.\&./configuration/basic\&.md#logging|logging\fR\&. The default value is
|
||||
\fIinfo\fR\&. NOTE: previous versions of ejabberd had log levels defined in numeric format (\fI0\&.\&.5\fR)\&. The numeric values are still accepted for backward compatibility, but are not recommended\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -1126,9 +1152,9 @@ This option specifies the maximum number of elements in the queue of the FSM (Fi
|
||||
.PP
|
||||
\fBmodules\fR: \fI{Module: Options}\fR
|
||||
.RS 4
|
||||
The option for modules configuration\&. See
|
||||
\fImodules\&.md|Modules\fR
|
||||
section for details\&.
|
||||
Set all the
|
||||
\fImodules\&.md|modules\fR
|
||||
configuration options\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBnegotiation_timeout\fR: \fItimeout()\fR
|
||||
@@ -1147,10 +1173,9 @@ This option can be used to tune tick time parameter of
|
||||
.PP
|
||||
\fBnew_sql_schema\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Whether to use
|
||||
\fInew\fR
|
||||
SQL schema\&. All schemas are located at
|
||||
https://github\&.com/processone/ejabberd/tree/24\&.06/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
|
||||
Whether to use the
|
||||
\fIdatabase\&.md#default\-and\-new\-schemas|new SQL schema\fR\&. All schemas are located at
|
||||
https://github\&.com/processone/ejabberd/tree/24\&.10/sql\&. There are two schemas available\&. The default legacy schema stores one XMPP domain into one ejabberd database\&. The
|
||||
\fInew\fR
|
||||
schema can handle several XMPP domains in a single ejabberd database\&. Using this
|
||||
\fInew\fR
|
||||
@@ -1296,13 +1321,17 @@ seconds\&.
|
||||
.PP
|
||||
\fBpam_service\fR: \fIName\fR
|
||||
.RS 4
|
||||
This option defines the PAM service name\&. Refer to the PAM documentation of your operation system for more information\&. The default value is
|
||||
This option defines the
|
||||
\fIauthentication\&.md#pam\-authentication|PAM\fR
|
||||
service name\&. Refer to the PAM documentation of your operation system for more information\&. The default value is
|
||||
\fIejabberd\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBpam_userinfotype\fR: \fIusername | jid\fR
|
||||
.RS 4
|
||||
This option defines what type of information about the user ejabberd provides to the PAM service: only the username, or the user\(cqs JID\&. Default is
|
||||
This option defines what type of information about the user ejabberd provides to the
|
||||
\fIauthentication\&.md#pam\-authentication|PAM\fR
|
||||
service: only the username, or the user\(cqs JID\&. Default is
|
||||
\fIusername\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -1336,36 +1365,47 @@ option where file queues will be placed\&. The default value is
|
||||
.PP
|
||||
\fBredis_connect_timeout\fR: \fItimeout()\fR
|
||||
.RS 4
|
||||
A timeout to wait for the connection to be re\-established to the Redis server\&. The default is
|
||||
A timeout to wait for the connection to be re\-established to the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server\&. The default is
|
||||
\fI1 second\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBredis_db\fR: \fINumber\fR
|
||||
.RS 4
|
||||
Redis database number\&. The default is
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
database number\&. The default is
|
||||
\fI0\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBredis_password\fR: \fIPassword\fR
|
||||
.RS 4
|
||||
The password to the Redis server\&. The default is an empty string, i\&.e\&. no password\&.
|
||||
The password to the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server\&. The default is an empty string, i\&.e\&. no password\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBredis_pool_size\fR: \fINumber\fR
|
||||
.RS 4
|
||||
The number of simultaneous connections to the Redis server\&. The default value is
|
||||
The number of simultaneous connections to the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server\&. The default value is
|
||||
\fI10\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBredis_port\fR: \fI1\&.\&.65535\fR
|
||||
.RS 4
|
||||
The port where the Redis server is accepting connections\&. The default is
|
||||
The port where the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server is accepting connections\&. The default is
|
||||
\fI6379\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBredis_queue_type\fR: \fIram | file\fR
|
||||
.RS 4
|
||||
The type of request queue for the Redis server\&. See description of
|
||||
The type of request queue for the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server\&. See description of
|
||||
\fIqueue_type\fR
|
||||
option for the explanation\&. The default value is the value defined in
|
||||
\fIqueue_type\fR
|
||||
@@ -1376,7 +1416,9 @@ if the latter is not set\&.
|
||||
.PP
|
||||
\fBredis_server\fR: \fIHostname\fR
|
||||
.RS 4
|
||||
A hostname or an IP address of the Redis server\&. The default is
|
||||
A hostname or an IP address of the
|
||||
\fIdatabase\&.md#redis|Redis\fR
|
||||
server\&.The default is
|
||||
\fIlocalhost\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -1579,7 +1621,8 @@ XEP\-0138) or not\&. The default value is
|
||||
.PP
|
||||
\fBshaper\fR: \fI{ShaperName: Rate}\fR
|
||||
.RS 4
|
||||
The option defines a set of shapers\&. Every shaper is assigned a name
|
||||
The option defines a set of
|
||||
\fI\&.\&./configuration/basic\&.md#shapers|shapers\fR\&. Every shaper is assigned a name
|
||||
\fIShaperName\fR
|
||||
that can be used in other parts of the configuration file, such as
|
||||
\fIshaper_rules\fR
|
||||
@@ -1611,7 +1654,9 @@ shaper:
|
||||
.PP
|
||||
\fBshaper_rules\fR: \fI{ShaperRuleName: {Number|ShaperName: ACLRule|ACLName}}\fR
|
||||
.RS 4
|
||||
An entry allowing to declaring shaper to use for matching user/hosts\&. Semantics is similar to
|
||||
This option defines
|
||||
\fI\&.\&./configuration/basic\&.md#shaper\-rules|shaper rules\fR
|
||||
to use for matching user/hosts\&. Semantics is similar to
|
||||
\fIaccess_rules\fR
|
||||
option, the only difference is that instead using
|
||||
\fIallow\fR
|
||||
@@ -1772,7 +1817,7 @@ or
|
||||
if the latter is not set\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBsql_server 🟤\fR: \fIHost | IP Address | ODBC Connection String | Unix Socket Path\fR
|
||||
\fBsql_server\fR: \fIHost | IP Address | ODBC Connection String | Unix Socket Path\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: improved in 24\&.06\&. The hostname or IP address of the SQL server\&. For
|
||||
@@ -1855,13 +1900,19 @@ to allow all proxies, or specify a list of IPs, possibly with masks\&. The defau
|
||||
header if you enable this option as, otherwise, the client can set it itself and as a result the IP value cannot be trusted for security rules in ejabberd\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBupdate_sql_schema 🟤\fR: \fItrue | false\fR
|
||||
\fBupdate_sql_schema\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: updated in 24\&.06\&. Allow ejabberd to update SQL schema\&. This option was added in ejabberd 23\&.10, and enabled by default since 24\&.06\&. The default value is
|
||||
about this option: updated in 24\&.06\&. Allow ejabberd to update SQL schema in MySQL, PostgreSQL and SQLite databases\&. This option was added in ejabberd 23\&.10, and enabled by default since 24\&.06\&. The default value is
|
||||
\fItrue\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBupdate_sql_schema_timeout\fR: \fItimeout()\fR
|
||||
.RS 4
|
||||
\fINote\fR
|
||||
about this option: added in 24\&.07\&. Time allocated to SQL schema update queries\&. The default value is set to 5 minutes\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBuse_cache\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
Enable or disable cache\&. The default is
|
||||
@@ -1913,7 +1964,7 @@ seconds\&.
|
||||
.RE
|
||||
.SH "MODULES"
|
||||
.sp
|
||||
This section describes modules options of ejabberd 24\&.06\&. The modules that changed in this version are marked with 🟤\&.
|
||||
This section describes modules options of ejabberd 24\&.10\&. The modules that changed in this version are marked with 🟤\&.
|
||||
.SS "mod_adhoc"
|
||||
.sp
|
||||
This module implements XEP\-0050: Ad\-Hoc Commands\&. It\(cqs an auxiliary module and is only needed by some of the other modules\&.
|
||||
@@ -1946,7 +1997,7 @@ Details for some commands:
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
\fIban\-acount\fR: This command kicks all the connected sessions of the account from the server\&. It also changes their password to a randomly generated one, so they can\(cqt login anymore unless a server administrator changes their password again\&. It is possible to define the reason of the ban\&. The new password also includes the reason and the date and time of the ban\&. See an example below\&.
|
||||
\fIban_account\fR: This command kicks all the connected sessions of the account from the server\&. It also changes their password to a randomly generated one, so they can\(cqt login anymore unless a server administrator changes their password again\&. It is possible to define the reason of the ban\&. The new password also includes the reason and the date and time of the ban\&. See an example below\&.
|
||||
.RE
|
||||
.sp
|
||||
.RS 4
|
||||
@@ -1971,7 +2022,7 @@ or similar\&. If you use other Operating System, place the file on the same dire
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
\fIsrg\-create\fR: If you want to put a group Name with blankspaces, use the characters "\*(Aq and \*(Aq" to define when the Name starts and ends\&. See an example below\&.
|
||||
\fIsrg_create\fR: If you want to put a group Name with blank spaces, use the characters "\*(Aq and \*(Aq" to define when the Name starts and ends\&. See an example below\&.
|
||||
.RE
|
||||
.sp
|
||||
The module has no options\&.
|
||||
@@ -2025,19 +2076,19 @@ With this call, the sessions of the local account which JID is boby@example\&.or
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
ejabberdctl vhost example\&.org ban\-account boby "Spammed rooms"
|
||||
ejabberdctl vhost example\&.org ban_account boby "Spammed rooms"
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.sp
|
||||
Call to srg\-create using double\-quotes and single\-quotes:
|
||||
Call to srg_create using double\-quotes and single\-quotes:
|
||||
.sp
|
||||
.if n \{\
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
ejabberdctl srg\-create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g1
|
||||
ejabberdctl srg_create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g1
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
@@ -3541,7 +3592,7 @@ option, but applied to this module only\&.
|
||||
.PP
|
||||
\fBuser_mucsub_from_muc_archive\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
When this option is disabled, for each individual subscriber a separa mucsub message is stored\&. With this option enabled, when a user fetches archive virtual mucsub, messages are generated from muc archives\&. The default value is
|
||||
When this option is disabled, for each individual subscriber a separate mucsub message is stored\&. With this option enabled, when a user fetches archive virtual mucsub, messages are generated from muc archives\&. The default value is
|
||||
\fIfalse\fR\&.
|
||||
.RE
|
||||
.RE
|
||||
@@ -3590,11 +3641,11 @@ is replaced with the hostname\&. The default value is
|
||||
\fBmatrix_id_as_jid\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
If set to
|
||||
\fIfalse\fR, all packets failing to be delivered via an XMPP server\-to\-server connection will then be routed to the Matrix gateway by translating a Jabber ID
|
||||
\fItrue\fR, all packets failing to be delivered via an XMPP server\-to\-server connection will then be routed to the Matrix gateway by translating a Jabber ID
|
||||
\fIuser@matrixdomain\&.tld\fR
|
||||
to a Matrix user identifier
|
||||
\fI@user:matrixdomain\&.tld\fR\&. When set to
|
||||
\fItrue\fR, messages must be explicitly sent to the matrix gateway service Jabber ID to be routed to a remote Matrix server\&. In this case, to send a message to Matrix user
|
||||
\fIfalse\fR, messages must be explicitly sent to the matrix gateway service Jabber ID to be routed to a remote Matrix server\&. In this case, to send a message to Matrix user
|
||||
\fI@user:matrixdomain\&.tld\fR, the client must send a message to the JID
|
||||
\fIuser%\fR\fImatrixdomain\&.tld@matrix\&.myxmppdomain\fR\fI\&.tld\fR, where
|
||||
\fImatrix\&.myxmppdomain\&.tld\fR
|
||||
@@ -4405,7 +4456,7 @@ 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\&. The default value is
|
||||
This option defines after how many users in the room, it is considered overcrowded\&. When a MUC room is considered overcrowded, presence broadcasts are limited to reduce load, traffic and excessive presence "storm" received by participants\&. The default value is
|
||||
\fI1000\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
@@ -5013,9 +5064,9 @@ This option defines which access rule will be enforced to limit the maximum numb
|
||||
.PP
|
||||
\fBbounce_groupchat\fR: \fItrue | false\fR
|
||||
.RS 4
|
||||
This option is use the disable an optimisation that avoids bouncing error messages when groupchat messages could not be stored as offline\&. It will reduce chat room load, without any drawback in standard use cases\&. You may change default value only if you have a custom module which uses offline hook after
|
||||
This option is use the disable an optimization that avoids bouncing error messages when groupchat messages could not be stored as offline\&. It will reduce chat room load, without any drawback in standard use cases\&. You may change default value only if you have a custom module which uses offline hook after
|
||||
\fImod_offline\fR\&. This option can be useful for both standard MUC and MucSub, but the bounce is much more likely to happen in the context of MucSub, so it is even more important to have it on large MucSub services\&. The default value is
|
||||
\fIfalse\fR, meaning the optimisation is enabled\&.
|
||||
\fIfalse\fR, meaning the optimization is enabled\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBcache_life_time\fR: \fItimeout()\fR
|
||||
@@ -5356,6 +5407,8 @@ option, but applied to this module only\&.
|
||||
.RE
|
||||
.SS "mod_privilege"
|
||||
.sp
|
||||
\fINote\fR about this option: improved in 24\&.10\&.
|
||||
.sp
|
||||
This module is an implementation of XEP\-0356: Privileged Entity\&. This extension allows components to have privileged access to other entity data (send messages on behalf of the server or on behalf of a user, get/set user roster, access presence information, etc\&.)\&. This may be used to write powerful external components, for example implementing an external PEP or MAM service\&.
|
||||
.sp
|
||||
By default a component does not have any privileged access\&. It is worth noting that the permissions grant access to the component to a specific data type for all users of the virtual host on which \fImod_privilege\fR is loaded\&.
|
||||
@@ -5402,6 +5455,36 @@ This module is complementary to \fImod_delegation\fR, but can also be used separ
|
||||
\fBAvailable options:\fR
|
||||
.RS 4
|
||||
.PP
|
||||
\fBiq\fR: \fI{Namespace: Options}\fR
|
||||
.RS 4
|
||||
This option defines namespaces and their IQ permissions\&. By default no permissions are given\&. The
|
||||
\fIOptions\fR
|
||||
are:
|
||||
.PP
|
||||
\fBboth\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
Allows sending IQ stanzas of type
|
||||
\fIget\fR
|
||||
and
|
||||
\fIset\fR\&. The default value is
|
||||
\fInone\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBget\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
Allows sending IQ stanzas of type
|
||||
\fIget\fR\&. The default value is
|
||||
\fInone\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBset\fR: \fIAccessName\fR
|
||||
.RS 4
|
||||
Allows sending IQ stanzas of type
|
||||
\fIset\fR\&. The default value is
|
||||
\fInone\fR\&.
|
||||
.RE
|
||||
.RE
|
||||
.PP
|
||||
\fBmessage\fR: \fIOptions\fR
|
||||
.RS 4
|
||||
This option defines permissions for messages\&. By default no permissions are given\&. The
|
||||
@@ -5474,6 +5557,9 @@ Sets write access to a user\(cqs roster\&. The default value is
|
||||
.nf
|
||||
modules:
|
||||
mod_privilege:
|
||||
iq:
|
||||
http://jabber\&.org/protocol/pubsub:
|
||||
get: all
|
||||
roster:
|
||||
get: all
|
||||
presence:
|
||||
@@ -5949,7 +6035,7 @@ modules:
|
||||
.RE
|
||||
.SS "mod_push"
|
||||
.sp
|
||||
This module implements the XMPP server\(cqs part of the push notification solution specified in XEP\-0357: Push Notifications\&. It does not generate, for example, APNS or FCM notifications directly\&. Instead, it\(cqs designed to work with so\-called "app servers" operated by third\-party vendors of mobile apps\&. Those app servers will usually trigger notification delivery to the user\(cqs mobile device using platform\-dependant backend services such as FCM or APNS\&.
|
||||
This module implements the XMPP server\(cqs part of the push notification solution specified in XEP\-0357: Push Notifications\&. It does not generate, for example, APNS or FCM notifications directly\&. Instead, it\(cqs designed to work with so\-called "app servers" operated by third\-party vendors of mobile apps\&. Those app servers will usually trigger notification delivery to the user\(cqs mobile device using platform\-dependent backend services such as FCM or APNS\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
@@ -6109,7 +6195,10 @@ This module reads also the top\-level \fIregistration_timeout\fR option defined
|
||||
.RS 4
|
||||
Specify rules to restrict what usernames can be registered\&. If a rule returns
|
||||
\fIdeny\fR
|
||||
on the requested username, registration of that user name is denied\&. There are no restrictions by default\&.
|
||||
on the requested username, registration of that user name is denied\&. There are no restrictions by default\&. If
|
||||
\fIAccessName\fR
|
||||
is
|
||||
\fInone\fR, then registering new accounts using In\-Band Registration is disabled and the corresponding stream feature is not announced to clients\&.
|
||||
.RE
|
||||
.PP
|
||||
\fBaccess_from\fR: \fIAccessName\fR
|
||||
@@ -6176,6 +6265,24 @@ Set a welcome message that is sent to each newly registered account\&. The messa
|
||||
\fISubject\fR
|
||||
and text
|
||||
\fIBody\fR\&.
|
||||
.sp
|
||||
\fBExample\fR:
|
||||
.sp
|
||||
.if n \{\
|
||||
.RS 4
|
||||
.\}
|
||||
.nf
|
||||
modules:
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: |\-
|
||||
Hi!
|
||||
Welcome to this XMPP server
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.RE
|
||||
.SS "mod_register_web"
|
||||
@@ -6347,6 +6454,33 @@ modules:
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_s2s_bidi"
|
||||
.sp
|
||||
\fINote\fR about this option: added in 24\&.10\&.
|
||||
.sp
|
||||
The module adds support for XEP\-0288: Bidirectional Server\-to\-Server Connections that allows using single s2s connection to communicate in both directions\&.
|
||||
.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
|
||||
modules:
|
||||
mod_s2s_bidi: {}
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_s2s_dialback"
|
||||
.sp
|
||||
The module adds support for XEP\-0220: Server Dialback to provide server identity verification based on DNS\&.
|
||||
@@ -6406,6 +6540,48 @@ modules:
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_scram_upgrade"
|
||||
.sp
|
||||
\fINote\fR about this option: added in 24\&.10\&.
|
||||
.sp
|
||||
The module adds support for XEP\-0480: SASL Upgrade Tasks that allows users to upgrade passwords to more secure representation\&.
|
||||
.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
|
||||
\fBoffered_upgrades\fR: \fIlist(sha256, sha512)\fR
|
||||
.RS 4
|
||||
List with upgrade types that should be offered
|
||||
.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
|
||||
modules:
|
||||
mod_scram_upgrade:
|
||||
offered_upgrades:
|
||||
\- sha256
|
||||
\- sha512
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.\}
|
||||
.RE
|
||||
.SS "mod_service_log"
|
||||
.sp
|
||||
This module forwards copies of all stanzas to remote XMPP servers or components\&. Every stanza is encapsulated into <forwarded/> element as described in XEP\-0297: Stanza Forwarding\&.
|
||||
@@ -7119,7 +7295,7 @@ The protocol extension is deferred and seems like even a few clients that were s
|
||||
The module has no options\&.
|
||||
.SS "mod_stream_mgmt"
|
||||
.sp
|
||||
This module adds support for XEP\-0198: Stream Management\&. This protocol allows active management of an XML stream between two XMPP entities, including features for stanza acknowledgements and stream resumption\&.
|
||||
This module adds support for XEP\-0198: Stream Management\&. This protocol allows active management of an XML stream between two XMPP entities, including features for stanza acknowledgments and stream resumption\&.
|
||||
.sp
|
||||
.it 1 an-trap
|
||||
.nr an-no-space-flag 1
|
||||
@@ -7131,7 +7307,7 @@ This module adds support for XEP\-0198: Stream Management\&. This protocol allow
|
||||
.PP
|
||||
\fBack_timeout\fR: \fItimeout()\fR
|
||||
.RS 4
|
||||
A time to wait for stanza acknowledgements\&. Setting it to
|
||||
A time to wait for stanza acknowledgments\&. Setting it to
|
||||
\fIinfinity\fR
|
||||
effectively disables the timeout\&. The default value is
|
||||
\fI1\fR
|
||||
@@ -7804,7 +7980,7 @@ Should the operating system be revealed or not\&. The default value is
|
||||
.RE
|
||||
.SH "LISTENERS"
|
||||
.sp
|
||||
This section describes listeners options of ejabberd 24\&.06\&.
|
||||
This section describes listeners options of ejabberd 24\&.10\&.
|
||||
.sp
|
||||
TODO
|
||||
.SH "AUTHOR"
|
||||
@@ -7812,13 +7988,13 @@ TODO
|
||||
ProcessOne\&.
|
||||
.SH "VERSION"
|
||||
.sp
|
||||
This document describes the configuration file of ejabberd 24\&.06\&. Configuration options of other ejabberd versions may differ significantly\&.
|
||||
This document describes the configuration file of ejabberd 24\&.10\&. 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/24\&.06/ejabberd\&.yml\&.example
|
||||
Default configuration file: https://github\&.com/processone/ejabberd/blob/24\&.10/ejabberd\&.yml\&.example
|
||||
.sp
|
||||
Main site: https://ejabberd\&.im
|
||||
.sp
|
||||
|
||||
@@ -43,9 +43,8 @@ defmodule Ejabberd.MixProject do
|
||||
|
||||
def application do
|
||||
[mod: {:ejabberd_app, []},
|
||||
applications: [:idna, :inets, :kernel, :sasl, :ssl, :stdlib, :mix,
|
||||
:fast_tls, :fast_xml, :fast_yaml, :jose,
|
||||
:p1_utils, :stringprep, :syntax_tools, :yconf, :xmpp]
|
||||
extra_applications: [:inets, :kernel, :sasl, :ssl, :stdlib, :syntax_tools,
|
||||
:logger, :mix]
|
||||
++ cond_apps(),
|
||||
included_applications: [:mnesia, :os_mon,
|
||||
:cache_tab, :eimp, :mqtree, :p1_acme,
|
||||
@@ -135,8 +134,8 @@ defmodule Ejabberd.MixProject do
|
||||
{:dialyxir, "~> 1.2", only: [:test], runtime: false},
|
||||
{:eimp, "~> 1.0"},
|
||||
{:ex_doc, "~> 0.31", only: [:dev, :edoc], runtime: false},
|
||||
{:fast_tls, ">= 1.1.18"},
|
||||
{:fast_xml, ">= 1.1.51"},
|
||||
{:fast_tls, "~> 1.1.22"},
|
||||
{:fast_xml, "~> 1.1.53"},
|
||||
{:fast_yaml, "~> 1.0"},
|
||||
{:idna, "~> 6.0"},
|
||||
{:mqtree, "~> 1.0"},
|
||||
@@ -145,7 +144,7 @@ defmodule Ejabberd.MixProject do
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:pkix, "~> 1.0"},
|
||||
{:stringprep, ">= 1.0.26"},
|
||||
{:xmpp, ">= 1.8.2"},
|
||||
{:xmpp, "~> 1.9"},
|
||||
{:yconf, "~> 1.0"}]
|
||||
++ cond_deps()
|
||||
end
|
||||
@@ -166,7 +165,7 @@ defmodule Ejabberd.MixProject do
|
||||
{Mix.env() == :translations,
|
||||
{:ejabberd_po, git: "https://github.com/processone/ejabberd-po.git"}},
|
||||
{Mix.env() == :dev,
|
||||
{:exsync, "~> 0.2"}},
|
||||
{:exsync, "~> 0.2", optional: true, runtime: false}},
|
||||
{config(:redis), {:eredis, "~> 1.2.0"}},
|
||||
{config(:sip), {:esip, "~> 1.0"}},
|
||||
{config(:zlib), {:ezlib, "~> 1.0"}},
|
||||
@@ -184,7 +183,6 @@ defmodule Ejabberd.MixProject do
|
||||
|
||||
defp cond_apps do
|
||||
for {:true, app} <- [{config(:stun), :stun},
|
||||
{Map.has_key?(System.get_env(), "RELIVE"), :exsync},
|
||||
{if_version_below(~c"27", true), :jiffy},
|
||||
{config(:tools), :observer}], do:
|
||||
app
|
||||
@@ -209,6 +207,7 @@ defmodule Ejabberd.MixProject do
|
||||
[# These are the default files included in the package
|
||||
files: ["include", "lib", "priv", "sql", "src",
|
||||
"COPYING", "README.md",
|
||||
"ejabberd.yml.example", "config/runtime.exs",
|
||||
"mix.exs", "rebar.config", "rebar.config.script", "vars.config"],
|
||||
maintainers: ["ProcessOne"],
|
||||
licenses: ["GPL-2.0-or-later"],
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
%{
|
||||
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
|
||||
"cache_tab": {:hex, :cache_tab, "1.0.31", "e4097b50a6f373ab1e0a5f01bab0bef6626771a4cd6c93404ed6d54810e11fbc", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8582b60a4a09b247ef86355ba9e07fce9e11edc0345a775c9171f971c72b6351"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
|
||||
"eimp": {:hex, :eimp, "1.0.23", "aaf32efab061143403dadbaa63e699ef8e81702fbfa96fd436d5e9be4d6a8d3a", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "907c780023cb2893e4fc4bdbe6a4f02c355913862ac67f0ecc26605e816b628a"},
|
||||
"epam": {:hex, :epam, "1.0.14", "aa0b85d27f4ef3a756ae995179df952a0721237e83c6b79d644347b75016681a", [:rebar3], [], "hexpm", "2f3449e72885a72a6c2a843f561add0fc2f70d7a21f61456930a547473d4d989"},
|
||||
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm", "d9b5abef2c2c8aba8f32aa018203e0b3dc8b1157773b254ab1d4c2002317f1e1"},
|
||||
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
|
||||
"esip": {:hex, :esip, "1.0.53", "482786a79d8f5b0aefc60ca68b8c6c7f074f6ede2653705e4206c6779edc5a56", [:rebar3], [{:fast_tls, "1.1.20", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.13", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "a9a1bd5ea52b0e2d1b1d1fec5fb7f0301e90610db1ecfeb205a18cc4e1cb3fc7"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"},
|
||||
"exsync": {:hex, :exsync, "0.4.1", "0a14fe4bfcb80a509d8a0856be3dd070fffe619b9ba90fec13c58b316c176594", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "cefb22aa805ec97ffc5b75a4e1dc54bcaf781e8b32564bf74abbe5803d1b5178"},
|
||||
"esip": {:hex, :esip, "1.0.56", "63c0fdc667be751714e1e5c14621a9334f21b60ac1bb68be889454ca9ca021b7", [:rebar3], [{:fast_tls, "1.1.22", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.15", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "9ef3660cef93b623f7368dcd5c79f4e704358631909e6dd464e335378815da1f"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
|
||||
"ezlib": {:hex, :ezlib, "1.0.13", "3c7f62862850a241159c10b218ecf580bce54d0890601b65144dacc2633be2b0", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "9ee62ab3f8ed55a0fd11a9569fcb8e458683f95575417272192b069f092abfbb"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.1.20", "d6f12d9ae4fe57e880b144b912e735af89343a8463d39b7eb4be3f6ca6163879", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d09a12472a56a34c5eecaaed33ea283f00fcdf9dc2e8282ecbaae827f13fc21b"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.52", "0289daafbf1190b0e53b444d4885cccf41e4b05768d4b3acc76dd8d143668e10", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "795192390e06d2b65016a6990bbfa5727f4a26d2914808b1c3c9a32eedcd1bfd"},
|
||||
"fast_tls": {:hex, :fast_tls, "1.1.22", "44356b256afad4399c2fc5059a3066669dafd8bd4e4e796c9c1cf8910ddd265e", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "e65779aefb7ab15c4755230fef8077e687d20cc5a3984a5974f9f657e8e2485b"},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.53", "1ef4f6e5995bcfa94800a46b460c3400c19c0a533948b12200a2e2fb1a2be427", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "5064336d6f363eee5097aa5dc5ced9b67f05152f2e6b8520fd50d268c2ab839c"},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.37", "f71d472fbf787ccd161b914d1eb486116a0f4f2e835337a378fbd31b59d2e74b", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "8de868721bf7e2172414f7d3148ede0f3c922b496455cd625dd5c4429515a769"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
"jiffy": {:hex, :jiffy, "1.1.2", "a9b6c9a7ec268e7cf493d028f0a4c9144f59ccb878b1afe42841597800840a1b", [:rebar3], [], "hexpm", "bb61bc42a720bbd33cb09a410e48bb79a61012c74cb8b3e75f26d988485cf381"},
|
||||
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
|
||||
"luerl": {:hex, :luerl, "1.2.0", "60f05f4240f0e7c148ddb79b67b8ff972734aad237aa74c83d0748b8214c8ef0", [:rebar3], [], "hexpm", "9cafd4f6094ff0f5a9d278fd81d60d3e026c820bdfb6cacd4b1bd909f21b525d"},
|
||||
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
|
||||
"mqtree": {:hex, :mqtree, "1.0.17", "82f54b8f2d22b4445db1d6cccb7fe9ead049d61410c29e32475f3ceb3ee62a89", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "5fe8b7cf8fbc4783d0fceb94654ac2bbf3242a58cd0397d249ded8ae021be2a3"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||
"p1_acme": {:hex, :p1_acme, "1.0.23", "791aef0f79dc7f768b228808250c349fa9ce585cd8779da50ca93106eb3394d0", [:rebar3], [{:base64url, "~> 1.0", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~> 1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.15", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "8ce196f26e3d22ea10b7809122950465878c127f80767e325207aed7e8d0dd59"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.24", "0ed1e098c5a4525032448c65a2715f30980aae725615a4d255fd25f26bb22507", [:rebar3], [], "hexpm", "f058865f64257f507a2c6a5aff369b1375dbcb30b3d4258dad4f1b3eaffb655f"},
|
||||
"p1_acme": {:hex, :p1_acme, "1.0.24", "e19876618eb0be22815aca99640cb88cd5c86e4239b8e8dc15b4e5d7854ef7e2", [:rebar3], [{:base64url, "~> 1.0", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "~> 1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "~> 1.0.15", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "f1451d706595ef997ab1ca17162ddac58f874ac97e315a5fadbe3cfa26148002"},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.25", "875d4cbdc7c9990270df3292cce2514e4c18a9fdfd19bef258cb4d0c45b4f243", [:rebar3], [], "hexpm", "e6187ffae95b726098e88f3ee6f2344ac259ce2c26e0ee403b05feef341ae434"},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.14", "1c5f82535574de87e2059695ac4b91f8f9aebacbc1c80287dae6f02552d47aea", [:rebar3], [], "hexpm", "1fd3ac474e43722d9d5a87c6df8d36f698ed87af7bb81cbbb66361451d99ae8f"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.26", "d3c3748c3638a1d7db5644e4fc63a6da7614b3009e172ef92a01d0217c3bec65", [:rebar3], [{:xmpp, "~> 1.8.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "85f230db530333106b8a1f9e5d5af032e6c3dd23b432e03d68e9d29013a6dcfc"},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.28", "08eca7d2afc81ba6d757b572f4a57ef3a2383b0c7b785fde38184bc368376d54", [:rebar3], [{:xmpp, "~> 1.9.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "655bb75df036ace55ddce17ca741143c42e0667c6206ac27a4dcbc65f71ac9ef"},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.26", "67b0c4ac9fa3ba3ef563b31aa111b0a004439a37fac85e027f1c3617e1c7ec6c", [:rebar3], [], "hexpm", "d0379e8c1156b98bd64f8129c1de022fcca4f2fdb7486ce73bf0ed2c3376b04c"},
|
||||
"pkix": {:hex, :pkix, "1.0.10", "d3bfadf7b7cfe2a3636f1b256c9cce5f646a07ce31e57ee527668502850765a0", [:rebar3], [], "hexpm", "e02164f83094cb124c41b1ab28988a615d54b9adc38575f00f19a597a3ac5d0e"},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.15", "e819defd280145c328457d7af897d2e45e8e5270e18812ee30b607c99cdd21af", [:rebar3], [], "hexpm", "3c0ba4e13322c2ad49de4e2ddd28311366adde54beae8dba9d9e3888f69d2857"},
|
||||
"stringprep": {:hex, :stringprep, "1.0.30", "46cf0ff631b3e7328f61f20b454d59428d87738f25d709798b5dcbb9b83c23f1", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "f6fc9b3384a03877830f89b2f38580caf3f4a27448a4a333d6a8c3975c220b9a"},
|
||||
"stun": {:hex, :stun, "1.2.13", "c3e855f10f6b0c3ac150bce3d6c96c04a85207a3a5c7a7207876d8b36db2b0a4", [:rebar3], [{:fast_tls, "1.1.20", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "9cf4191491a60573ed6197e636530af1d25c9b064845aabed0c02f780d33ea3f"},
|
||||
"stun": {:hex, :stun, "1.2.15", "eec510af6509201ff97f1f2c87b7977c833bf29c04e985383370ec21f04e4ccf", [:rebar3], [{:fast_tls, "1.1.22", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "f6d8a541a29fd13f2ce658b676c0cc661262b96e045b52def1644b75ebc0edef"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"xmpp": {:hex, :xmpp, "1.8.2", "7c26fae7ca83b307bab99624595dce706d427a17eed9c6305980550f8120a3a3", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "53a9f85ad44103a358dc173225bc96d08076d4e78506f87d838337b2fa3b381f"},
|
||||
"xmpp": {:hex, :xmpp, "1.9.0", "d92446bf51d36adda02db63b963fe6d4a1ede33e59b38a43d9b90afd20c25b74", [:rebar3], [{:ezlib, "~> 1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "~> 1.1.19", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "~> 1.1.51", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "c1b91be74a9a9503afa6766f756477516920ffbfeea0c260c2fa171355f53c27"},
|
||||
"yconf": {:hex, :yconf, "1.0.16", "d59521d66ff89f219411b6e9277cd6feec7cc6fce11554e67de02a8d0a470479", [:rebar3], [{:fast_yaml, "1.0.37", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "e947813273f38711c7b2e5a8e4acc9a51c7bbe854f744a345f60300b38586c89"},
|
||||
}
|
||||
|
||||
@@ -111,6 +111,11 @@
|
||||
{"Dynamically specify a replyto of the item publisher","Динамично задаване на отговор към публикувалия елемента"}.
|
||||
{"Edit Properties","Редактиране на свойства"}.
|
||||
{"Either approve or decline the voice request.","Одобрете или отхвърлете заявката за гласова връзка."}.
|
||||
{"ejabberd HTTP Upload service","ejabberd HTTP Upload услуга"}.
|
||||
{"ejabberd MUC module","ejabberd MUC модул"}.
|
||||
{"ejabberd Multicast service","ejabberd Multicast услуга"}.
|
||||
{"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe модул"}.
|
||||
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams модул"}.
|
||||
{"ejabberd vCard module","ejabberd vCard модул"}.
|
||||
{"ejabberd Web Admin","Уеб администрация на ejabberd"}.
|
||||
{"ejabberd","ejabberd"}.
|
||||
@@ -158,6 +163,12 @@
|
||||
{"Get User Statistics","Покажи статистика за потребителя"}.
|
||||
{"Given Name","Име"}.
|
||||
{"Grant voice to this person?","Предоставяне на глас за потребителя?"}.
|
||||
{"has been banned","е със забранен достъп"}.
|
||||
{"has been kicked because of a system shutdown","е отстранен поради изключване на системата"}.
|
||||
{"has been kicked because of an affiliation change","е отстранен поради промяна на принадлежността"}.
|
||||
{"has been kicked because the room has been changed to members-only","е изгонен, защото стаята е променена и е само за членове"}.
|
||||
{"has been kicked","е отстранен"}.
|
||||
{"Hash of the vCard-temp avatar of this room","Хеш на временния vCard аватар на тази стая"}.
|
||||
{"Hat title","Заглавие на шапката"}.
|
||||
{"Hat URI","URI адрес за шапка"}.
|
||||
{"Hats limit exceeded","Превишен е лимитът за шапка"}.
|
||||
@@ -190,6 +201,7 @@
|
||||
{"Invalid 'previd' value","Невалидна стойност на 'previd'"}.
|
||||
{"Invitations are not allowed in this conference","Поканите не са разрешени в тази конференция"}.
|
||||
{"IP addresses","IP адреси"}.
|
||||
{"is now known as","е известен като"}.
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не е позволено да изпращате съобщения за грешки в стаята. Участникът (~s) е изпратил съобщение за грешка (~s) и е бил отстранен от стаята"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Изпращането на лични съобщения от тип \"групов чат\" не е разрешено"}.
|
||||
{"It is not allowed to send private messages to the conference","Изпращането на лични съобщения до конференцията не е разрешено"}.
|
||||
@@ -199,6 +211,7 @@
|
||||
{"JID normalization failed","Нормализирането на JID е неуспешно"}.
|
||||
{"Joined MIX channels of ~ts","Свързани MIX канали на ~ts"}.
|
||||
{"Joined MIX channels:","Свързани MIX канали:"}.
|
||||
{"joins the room","се присъединява към стаята"}.
|
||||
{"July","Юли"}.
|
||||
{"June","Юни"}.
|
||||
{"Just created","Току що създаден"}.
|
||||
@@ -208,8 +221,10 @@
|
||||
{"Last month","Миналия месец"}.
|
||||
{"Last year","Миналата година"}.
|
||||
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Най-малко значимите битове SHA-256 хеш на текст трябва да са равни на шестнайсетичния етикет"}.
|
||||
{"leaves the room","напуска стаята"}.
|
||||
{"List of users with hats","Списък на потребителите с шапки"}.
|
||||
{"List users with hats","Избройте потребителите с шапки"}.
|
||||
{"Logged Out","Излязъл"}.
|
||||
{"Logging","Регистриране на събития"}.
|
||||
{"Make participants list public","Направи списъка с участниците публичен"}.
|
||||
{"Make room CAPTCHA protected","Защити стаята с CAPTCHA"}.
|
||||
@@ -360,6 +375,7 @@
|
||||
{"Previous session PID has exited","Предишният PID на сесията е излязъл"}.
|
||||
{"Previous session PID is dead","PID от предишната сесия не съществува"}.
|
||||
{"Previous session timed out","Времето на предишната сесия изтече"}.
|
||||
{"private, ","частна, "}.
|
||||
{"Public","Публичен"}.
|
||||
{"Publish model","Модел за публикуване"}.
|
||||
{"Publish-Subscribe","Публикуване-Абониране"}.
|
||||
@@ -423,6 +439,7 @@
|
||||
{"Set message of the day on all hosts and send to online users","Задавай съобщение на деня на всички хостове и изпрати на онлайн потребителите"}.
|
||||
{"Shared Roster Groups","Споделени групи от списъци с контакти"}.
|
||||
{"Show Integral Table","Покажи интегрална таблица"}.
|
||||
{"Show Occupants Join/Leave","Покажи участници Влязъл/Напускнал"}.
|
||||
{"Show Ordinary Table","Покажи обикновена таблица"}.
|
||||
{"Shut Down Service","Изключи услугата"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 байтови потоци"}.
|
||||
@@ -482,6 +499,7 @@
|
||||
{"The number of unread or undelivered messages","Броят непрочетени или недоставени съобщения"}.
|
||||
{"The password contains unacceptable characters","Паролата съдържа недопустими символи"}.
|
||||
{"The password is too weak","Паролата е твърде слаба"}.
|
||||
{"the password is","паролата е"}.
|
||||
{"The password of your XMPP account was successfully changed.","Паролата на вашия XMPP профил беше успешно променена."}.
|
||||
{"The password was not changed","Паролата не е променена"}.
|
||||
{"The passwords are different","Паролите са различни"}.
|
||||
@@ -555,6 +573,7 @@
|
||||
{"Value of '~s' should be datetime string","Стойността на '~s' трябва да бъде низ за дата и час"}.
|
||||
{"Value of '~s' should be integer","Стойността на '~s' трябва да бъде цяло число"}.
|
||||
{"Value 'set' of 'type' attribute is not allowed","Стойността 'set' на атрибут 'type' не е разрешена"}.
|
||||
{"vCard User Search","vCard търсене на потребител"}.
|
||||
{"View joined MIX channels","Вижте присъединените MIX канали"}.
|
||||
{"Virtual Hosts","Виртуални хостове"}.
|
||||
{"Visitors are not allowed to change their nicknames in this room","Посетителите нямат право да променят псевдонимите си в тази стая"}.
|
||||
@@ -584,6 +603,7 @@
|
||||
{"XMPP Show Value of Chat","XMPP покажи стойност на Чат"}.
|
||||
{"XMPP Show Value of DND (Do Not Disturb)","XMPP покажи стойност на DND (Не ме безпокой)"}.
|
||||
{"XMPP Show Value of XA (Extended Away)","XMPP покажи стойност на Продължително отсъствие"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","XMPP URI на асоцииран Publish-Subscribe нод"}.
|
||||
{"You are being removed from the room because of a system shutdown","Премахнати сте от стаята поради изключване на системата"}.
|
||||
{"You are not allowed to send private messages","Нямате право да изпращате лични съобщения"}.
|
||||
{"You are not joined to the channel","Не сте присъединени към канала"}.
|
||||
@@ -594,6 +614,9 @@
|
||||
{"You need a client that supports x:data and CAPTCHA to register","За да се регистрирате Ви е нужен клиент, който поддържа x:data и CAPTCHA"}.
|
||||
{"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 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 акаунт, беше успешно дерегистриран."}.
|
||||
{"You're not allowed to create nodes","Нямате право да създавате нодове"}.
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
{"has been kicked because of an affiliation change","ha sigut expulsat a causa d'un canvi d'afiliació"}.
|
||||
{"has been kicked because the room has been changed to members-only","ha sigut expulsat perquè la sala ara és només per a membres"}.
|
||||
{"has been kicked","ha sigut expulsat"}.
|
||||
{"Hash of the vCard-temp avatar of this room","Hash del avatar a vCard-temp d'esta sala"}.
|
||||
{"Hat title","Títol del barret"}.
|
||||
{"Hat URI","URI del barret"}.
|
||||
{"Hats limit exceeded","El límit de tràfic ha sigut sobrepassat"}.
|
||||
@@ -223,6 +224,7 @@
|
||||
{"leaves the room","surt de la sala"}.
|
||||
{"List of users with hats","Llista d'usuaris amb barrets"}.
|
||||
{"List users with hats","Llista d'usuaris amb barrets"}.
|
||||
{"Logged Out","Desconectat"}.
|
||||
{"Logging","Registre"}.
|
||||
{"Make participants list public","Crear una llista de participants pública"}.
|
||||
{"Make room CAPTCHA protected","Crear una sala protegida per CAPTCHA"}.
|
||||
@@ -437,6 +439,7 @@
|
||||
{"Set message of the day on all hosts and send to online users","Escriure missatge del dia en tots els hosts i enviar-ho als usuaris connectats"}.
|
||||
{"Shared Roster Groups","Grups de contactes compartits"}.
|
||||
{"Show Integral Table","Mostrar Taula Integral"}.
|
||||
{"Show Occupants Join/Leave","Mostrar Entrades/Eixides dels Ocupants"}.
|
||||
{"Show Ordinary Table","Mostrar Taula Ordinaria"}.
|
||||
{"Shut Down Service","Apager el Servei"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 Bytestreams"}.
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
{"has been kicked because of an affiliation change","ha sido expulsado por un cambio de su afiliación"}.
|
||||
{"has been kicked because the room has been changed to members-only","ha sido expulsado porque la sala es ahora solo para miembros"}.
|
||||
{"has been kicked","ha sido expulsado"}.
|
||||
{"Hash of the vCard-temp avatar of this room","Hash del avatar vCard-temp de esta sala"}.
|
||||
{"Hat title","Título del sombrero"}.
|
||||
{"Hat URI","Dirección del sombrero"}.
|
||||
{"Hats limit exceeded","Se ha excedido el límite de sombreros"}.
|
||||
@@ -223,6 +224,7 @@
|
||||
{"leaves the room","sale de la sala"}.
|
||||
{"List of users with hats","Lista de usuarios con sombreros"}.
|
||||
{"List users with hats","Listar usuarios con sombreros"}.
|
||||
{"Logged Out","Desconectad@"}.
|
||||
{"Logging","Histórico de mensajes"}.
|
||||
{"Make participants list public","La lista de participantes es pública"}.
|
||||
{"Make room CAPTCHA protected","Proteger la sala con CAPTCHA"}.
|
||||
@@ -437,6 +439,7 @@
|
||||
{"Set message of the day on all hosts and send to online users","Poner mensaje del día en todos los dominios y enviar a los usuarios conectados"}.
|
||||
{"Shared Roster Groups","Grupos Compartidos"}.
|
||||
{"Show Integral Table","Mostrar Tabla Integral"}.
|
||||
{"Show Occupants Join/Leave","Mostrar personas activas Entrar/Salir"}.
|
||||
{"Show Ordinary Table","Mostrar Tabla Ordinaria"}.
|
||||
{"Shut Down Service","Detener el servicio"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 Bytestreams"}.
|
||||
|
||||
+7
-4
@@ -9,19 +9,19 @@
|
||||
{"A description of the node","Una descrizione del nodo"}.
|
||||
{"A friendly name for the node","Un nome comodo per il nodo"}.
|
||||
{"A password is required to enter this room","Per entrare in questa stanza è necessaria una password"}.
|
||||
{"A Web Page","Un Pagina Web"}.
|
||||
{"A Web Page","Una pagina web"}.
|
||||
{"Accept","Accettare"}.
|
||||
{"Access denied by service policy","Accesso impedito dalle politiche del servizio"}.
|
||||
{"Access denied by service policy","Accesso negato dalle politiche del servizio"}.
|
||||
{"Access model","Modello di accesso"}.
|
||||
{"Account doesn't exist","L'account non esiste"}.
|
||||
{"Action on user","Azione sull'utente"}.
|
||||
{"Add a hat to a user","Aggiungere un cappello a un utente"}.
|
||||
{"Add User","Aggiungere un Utente"}.
|
||||
{"Add User","Aggiungere un utente"}.
|
||||
{"Administration of ","Amministrazione di "}.
|
||||
{"Administration","Amministrazione"}.
|
||||
{"Administrator privileges required","Sono richiesti privilegi di amministratore"}.
|
||||
{"All activity","Tutta l'attività"}.
|
||||
{"All Users","Tutti gli Utenti"}.
|
||||
{"All Users","Tutti gli utenti"}.
|
||||
{"Allow subscription","Consenti iscrizione"}.
|
||||
{"Allow this Jabber ID to subscribe to this pubsub node?","Consentire a questo ID Jabber di iscriversi a questo nodo pubsub?"}.
|
||||
{"Allow this person to register with the room?","Permettere a questa persona di registrarsi con la stanza?"}.
|
||||
@@ -223,6 +223,7 @@
|
||||
{"leaves the room","esce dalla stanza"}.
|
||||
{"List of users with hats","Elenco degli utenti con cappelli"}.
|
||||
{"List users with hats","Elenca gli utenti con cappelli"}.
|
||||
{"Logged Out","Disconnesso"}.
|
||||
{"Logging","Registrazione"}.
|
||||
{"Make participants list public","Rendere pubblica la lista dei partecipanti"}.
|
||||
{"Make room CAPTCHA protected","Rendere la stanza protetta da CAPTCHA"}.
|
||||
@@ -437,6 +438,7 @@
|
||||
{"Set message of the day on all hosts and send to online users","Impostare il messaggio del giorno (MOTD) su tutti gli host e inviarlo agli utenti online"}.
|
||||
{"Shared Roster Groups","Gruppi di liste di contatti comuni"}.
|
||||
{"Show Integral Table","Mostrare la tabella integrale"}.
|
||||
{"Show Occupants Join/Leave","Mostra gli occupanti che si uniscono/escono"}.
|
||||
{"Show Ordinary Table","Mostrare la tabella normale"}.
|
||||
{"Shut Down Service","Terminare il servizio"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 flussi di byte"}.
|
||||
@@ -531,6 +533,7 @@
|
||||
{"Too many <list/> elements","Troppi elementi <list/>"}.
|
||||
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Troppe (~p) autenticazioni non riuscite da questo indirizzo IP (~s). L'indirizzo verrà sbloccato alle ~s UTC"}.
|
||||
{"Too many receiver fields were specified","Sono stati specificati troppi campi del ricevitore"}.
|
||||
{"Too many unacked stanzas","Troppe stanze non riconosciute"}.
|
||||
{"Too many users in this conference","Troppi utenti in questa conferenza"}.
|
||||
{"Traffic rate limit is exceeded","Limite di traffico superato"}.
|
||||
{"~ts's Offline Messages Queue","La Coda dei Messaggi Offline di ~ts"}.
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
{"has been kicked because of an affiliation change","foi desconectado porque por afiliação inválida"}.
|
||||
{"has been kicked because the room has been changed to members-only","foi desconectado porque a política da sala mudou, só membros são permitidos"}.
|
||||
{"has been kicked","foi removido"}.
|
||||
{"Hash of the vCard-temp avatar of this room","Hash do avatar do vCard-temp desta sala"}.
|
||||
{"Hat title","Título do chapéu"}.
|
||||
{"Hat URI","URI do chapéu"}.
|
||||
{"Hats limit exceeded","O limite dos chapéus foi excedido"}.
|
||||
@@ -223,6 +224,7 @@
|
||||
{"leaves the room","Sair da sala"}.
|
||||
{"List of users with hats","Lista dos usuários com chapéus"}.
|
||||
{"List users with hats","Lista os usuários com chapéus"}.
|
||||
{"Logged Out","Desconectado"}.
|
||||
{"Logging","Registrando no log"}.
|
||||
{"Make participants list public","Tornar pública a lista de participantes"}.
|
||||
{"Make room CAPTCHA protected","Tornar protegida a senha da sala"}.
|
||||
@@ -437,6 +439,7 @@
|
||||
{"Set message of the day on all hosts and send to online users","Definir mensagem do dia em todos os hosts e enviar para os usuários online"}.
|
||||
{"Shared Roster Groups","Grupos Shared Roster"}.
|
||||
{"Show Integral Table","Mostrar Tabela Integral"}.
|
||||
{"Show Occupants Join/Leave","Mostrar a entrada e a saída de ocupantes"}.
|
||||
{"Show Ordinary Table","Mostrar Tabela Ordinária"}.
|
||||
{"Shut Down Service","Parar Serviço"}.
|
||||
{"SOCKS5 Bytestreams","Bytestreams SOCKS5"}.
|
||||
|
||||
+1
-1
@@ -222,7 +222,7 @@
|
||||
{"Logging","Журналювання"}.
|
||||
{"Make participants list public","Зробити список учасників видимим всім"}.
|
||||
{"Make room CAPTCHA protected","Зробити кімнату захищеною капчею"}.
|
||||
{"Make room members-only","Кімната тільки для зареєтрованых учасників"}.
|
||||
{"Make room members-only","Кімната тільки для зареєтрованих учасників"}.
|
||||
{"Make room moderated","Зробити кімнату модерованою"}.
|
||||
{"Make room password protected","Зробити кімнату захищеною паролем"}.
|
||||
{"Make room persistent","Зробити кімнату постійною"}.
|
||||
|
||||
+154
-151
@@ -33,17 +33,17 @@
|
||||
{"Allow visitors to send private messages to","允许访客发送私信至"}.
|
||||
{"Allow visitors to send status text in presence updates","允许访客在在线状态更新中发送状态文本"}.
|
||||
{"Allow visitors to send voice requests","允许访客发送发言请求"}.
|
||||
{"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.","与定义群聊成员资格相关联的 LDAP 群组;按群组特定于实现或特定于部署的定义,应该是一个 LDAP 专有名称。"}.
|
||||
{"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.","与定义群聊成员资格相关联的 LDAP 组;根据组的特定实施或特定部署的定义,使用 LDAP 专有名称。"}.
|
||||
{"Announcements","公告"}.
|
||||
{"Answer associated with a picture","与图片相关的答案"}.
|
||||
{"Answer associated with a video","与视频相关的答案"}.
|
||||
{"Answer associated with speech","与讲话相关的答案"}.
|
||||
{"Answer to a question","问题的答案"}.
|
||||
{"Anyone in the specified roster group(s) may subscribe and retrieve items","指定花名册群组中的人可以订阅并检索内容项"}.
|
||||
{"Anyone may associate leaf nodes with the collection","任何人都可以将叶子节点与集合关联"}.
|
||||
{"Anyone in the specified roster group(s) may subscribe and retrieve items","指定花名册组中的任何人都可以订阅和检索项目"}.
|
||||
{"Anyone may associate leaf nodes with the collection","任何人都可以将叶节点与集合关联"}.
|
||||
{"Anyone may publish","任何人都可以发布"}.
|
||||
{"Anyone may subscribe and retrieve items","任何人都可以订阅和检索内容项"}.
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","对全部或来源进行了状态订阅的任何人均可订阅并检索内容项"}.
|
||||
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","任何拥有 both 或 from 的在线状态订阅的用户都可以订阅和检索项目"}.
|
||||
{"Anyone with Voice","任何有发言权的人"}.
|
||||
{"Anyone","任何人"}.
|
||||
{"April","四月"}.
|
||||
@@ -82,7 +82,7 @@
|
||||
{"Chatroom is stopped","群聊已停止"}.
|
||||
{"Chatrooms","群聊"}.
|
||||
{"Choose a username and password to register with this server","请选择要在此服务器中注册的用户名和密码"}.
|
||||
{"Choose storage type of tables","请选择表单的存储类型"}.
|
||||
{"Choose storage type of tables","选择表的存储类型"}.
|
||||
{"Choose whether to approve this entity's subscription.","选择是否批准此实体的订阅。"}.
|
||||
{"City","城市"}.
|
||||
{"Client acknowledged more stanzas than sent by server","客户端确认的节数多于服务器发送的节数"}.
|
||||
@@ -94,7 +94,7 @@
|
||||
{"Country","国家/地区"}.
|
||||
{"Current Discussion Topic","当前讨论话题"}.
|
||||
{"Database failure","数据库失败"}.
|
||||
{"Database Tables Configuration at ","数据库表格配置位于 "}.
|
||||
{"Database Tables Configuration at ","数据库表配置在 "}.
|
||||
{"Database","数据库"}.
|
||||
{"December","十二月"}.
|
||||
{"Default users as participants","默认用户为参与者"}.
|
||||
@@ -103,21 +103,21 @@
|
||||
{"Delete User","删除用户"}.
|
||||
{"Deliver event notifications","传递事件通知"}.
|
||||
{"Deliver payloads with event notifications","用事件通知传递有效负载"}.
|
||||
{"Disc only copy","仅磁盘复制"}.
|
||||
{"Disc only copy","仅磁盘副本"}.
|
||||
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","不要将密码告诉任何人,甚至是 XMPP 服务的管理员。"}.
|
||||
{"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"}.
|
||||
{"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.","批准或拒绝发言请求。"}.
|
||||
{"ejabberd HTTP Upload service","ejabberd HTTP 上传服务"}.
|
||||
{"ejabberd MUC module","ejabberd MUC 模块"}.
|
||||
{"ejabberd Multicast service","ejabberd 多重映射服务"}.
|
||||
{"ejabberd Publish-Subscribe module","ejabberd 发布—订阅模块"}.
|
||||
{"ejabberd Publish-Subscribe module","ejabberd 发布–订阅模块"}.
|
||||
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 字节流模块"}.
|
||||
{"ejabberd vCard module","ejabberd vCard 模块"}.
|
||||
{"ejabberd Web Admin","ejabberd Web 管理员"}.
|
||||
{"ejabberd Web Admin","ejabberd Web 管理"}.
|
||||
{"ejabberd","ejabberd"}.
|
||||
{"Email Address","电子邮件地址"}.
|
||||
{"Email","电子邮件"}.
|
||||
@@ -134,14 +134,14 @@
|
||||
{"Enter the text you see","请输入您看到的文本"}.
|
||||
{"Erlang XMPP Server","Erlang XMPP 服务器"}.
|
||||
{"Exclude Jabber IDs from CAPTCHA challenge","从验证码挑战中排除 Jabber ID"}.
|
||||
{"Export all tables as SQL queries to a file:","将所有表以 SQL 查询语句导出到文件:"}.
|
||||
{"Export all tables as SQL queries to a file:","将所有表以 SQL 查询导出到文件:"}.
|
||||
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","将服务器中所有用户的数据导出到 PIEFXIS 文件(XEP-0227):"}.
|
||||
{"Export data of users in a host to PIEFXIS files (XEP-0227):","将主机中用户的数据导出到 PIEFXIS 文件(XEP-0227):"}.
|
||||
{"External component failure","外部组件故障"}.
|
||||
{"External component timeout","外部组件超时"}.
|
||||
{"Failed to activate bytestream","激活字节流失败"}.
|
||||
{"Failed to extract JID from your voice request approval","无法从您的发言请求批准中提取 JID"}.
|
||||
{"Failed to map delegated namespace to external component","未能将代理命名空间映射到外部组件"}.
|
||||
{"Failed to extract JID from your voice request approval","无法从发言请求批准中提取 JID"}.
|
||||
{"Failed to map delegated namespace to external component","未能将委托命名空间映射到外部组件"}.
|
||||
{"Failed to parse HTTP response","HTTP 响应解析失败"}.
|
||||
{"Failed to process option '~s'","无法处理选项“~s”"}.
|
||||
{"Family Name","姓氏"}.
|
||||
@@ -149,7 +149,7 @@
|
||||
{"February","二月"}.
|
||||
{"File larger than ~w bytes","文件大于 ~w 字节"}.
|
||||
{"Fill in the form to search for any matching XMPP User","填写表单以搜索任何匹配的 XMPP 用户"}.
|
||||
{"Friday","星期五"}.
|
||||
{"Friday","周五"}.
|
||||
{"From ~ts","来自 ~ts"}.
|
||||
{"Full List of Room Admins","群聊管理员完整列表"}.
|
||||
{"Full List of Room Owners","群聊所有者完整列表"}.
|
||||
@@ -160,14 +160,15 @@
|
||||
{"Get Number of Registered Users","获取注册用户数"}.
|
||||
{"Get Pending","获取待处理"}.
|
||||
{"Get User Last Login Time","获取用户上次登录时间"}.
|
||||
{"Get User Statistics","获取用户统计"}.
|
||||
{"Get User Statistics","获取用户统计数据"}.
|
||||
{"Given Name","中间名"}.
|
||||
{"Grant voice to this person?","授予此用户发言权?"}.
|
||||
{"has been banned","已被封禁"}.
|
||||
{"has been kicked because of a system shutdown","因系统关机而被踢出"}.
|
||||
{"has been kicked because of an affiliation change","由于从属关系的改变而踢出"}.
|
||||
{"has been kicked because of a system shutdown","因系统关闭而被踢出"}.
|
||||
{"has been kicked because of an affiliation change","由于从属关系的更改而被踢出"}.
|
||||
{"has been kicked because the room has been changed to members-only","被踢出,因为群聊已更改为仅成员进入"}.
|
||||
{"has been kicked","已被踢出"}.
|
||||
{"Hash of the vCard-temp avatar of this room","此群聊 vCard-temp 头像的散列值"}.
|
||||
{"Hat title","头衔标题"}.
|
||||
{"Hat URI","头衔 URI"}.
|
||||
{"Hats limit exceeded","已超过头衔限制"}.
|
||||
@@ -180,13 +181,13 @@
|
||||
{"Import user data from jabberd14 spool file:","从 jabberd14 Spool 文件导入用户数据:"}.
|
||||
{"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 data from jabberd14 spool directory:","从 jabberd14 spool 目录导入用户数据:"}.
|
||||
{"Import Users from Dir at ","从以下位置的目录导入用户 "}.
|
||||
{"Import Users From jabberd14 Spool Files","从 jabberd14 Spool 文件导入用户"}.
|
||||
{"Improper domain part of 'from' attribute","“from”属性域名部分不正确"}.
|
||||
{"Improper message type","消息类型不正确"}.
|
||||
{"Incorrect CAPTCHA submit","提交的验证码不正确"}.
|
||||
{"Incorrect data form","数据形式不正确"}.
|
||||
{"Incorrect data form","数据表单不正确"}.
|
||||
{"Incorrect password","密码不正确"}.
|
||||
{"Incorrect value of 'action' attribute","“action”属性的值不正确"}.
|
||||
{"Incorrect value of 'action' in data form","数据表单中“action”的值不正确"}.
|
||||
@@ -202,11 +203,11 @@
|
||||
{"IP addresses","IP 地址"}.
|
||||
{"is now known as","现在昵称为"}.
|
||||
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","不允许向此群聊发送错误消息。参与者(~s)发送了错误消息(~s),被踢出了群聊"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","不允许发送“群聊”类型的私信"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","不允许发送“groupchat”类型的私信"}.
|
||||
{"It is not allowed to send private messages to the conference","不允许向群聊发送私信"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"January","一月"}.
|
||||
{"JID normalization denied by service policy","JID 规范化被服务策略拒绝"}.
|
||||
{"JID normalization denied by service policy","服务策略拒绝 JID 规范化"}.
|
||||
{"JID normalization failed","JID 规范化失败"}.
|
||||
{"Joined MIX channels of ~ts","加入了 ~ts 的 MIX 频道"}.
|
||||
{"Joined MIX channels:","加入了 MIX 频道:"}.
|
||||
@@ -223,29 +224,30 @@
|
||||
{"leaves the room","离开群聊"}.
|
||||
{"List of users with hats","有头衔用户的列表"}.
|
||||
{"List users with hats","有头衔用户列表"}.
|
||||
{"Logging","正在记录"}.
|
||||
{"Logged Out","已登出"}.
|
||||
{"Logging","日志记录"}.
|
||||
{"Make participants list public","公开参与者列表"}.
|
||||
{"Make room CAPTCHA protected","让群聊受验证码保护"}.
|
||||
{"Make room members-only","让群聊仅成员进入"}.
|
||||
{"Make room CAPTCHA protected","开启群聊验证码保护"}.
|
||||
{"Make room members-only","仅成员进入的群聊"}.
|
||||
{"Make room moderated","开启群聊发言审核"}.
|
||||
{"Make room password protected","让群聊受密码保护"}.
|
||||
{"Make room persistent","让群聊持续存在"}.
|
||||
{"Make room public searchable","让群聊可公开搜索"}.
|
||||
{"Make room password protected","开启群聊密码保护"}.
|
||||
{"Make room persistent","持续存在的群聊"}.
|
||||
{"Make room public searchable","可公开搜索的群聊"}.
|
||||
{"Malformed username","用户名格式不正确"}.
|
||||
{"MAM preference modification denied by service policy","MAM 偏好被服务策略拒绝"}.
|
||||
{"MAM preference modification denied by service policy","服务策略拒绝修改 MAM 首选项"}.
|
||||
{"March","三月"}.
|
||||
{"Max # of items to persist, or `max` for no specific limit other than a server imposed maximum","要持久化的最大项目数 #,或“max”表示除服务器施加的最大值之外没有特定限制"}.
|
||||
{"Max payload size in bytes","最大有效负载字节数"}.
|
||||
{"Max payload size in bytes","最大有效负载大小(字节)"}.
|
||||
{"Maximum file size","最大文件大小"}.
|
||||
{"Maximum Number of History Messages Returned by Room","群聊返回的聊天记录消息的最大值"}.
|
||||
{"Maximum number of items to persist","要持久化的最大项目数"}.
|
||||
{"Maximum Number of Occupants","最大占用人数"}.
|
||||
{"Maximum Number of Occupants","最大参与者人数"}.
|
||||
{"May","五月"}.
|
||||
{"Membership is required to enter this room","进入此群聊需要成员资格"}.
|
||||
{"Memorize your password, or write it in a paper placed in a safe place. In XMPP there isn't an automated way to recover your password if you forget it.","请记住密码,或将密码写在纸上,放在安全的地方。在 XMPP 中,如果您忘记密码,没有自动恢复密码的方法。"}.
|
||||
{"Mere Availability in XMPP (No Show Value)","XMPP 中的可用性(无显示值)"}.
|
||||
{"Message body","消息主体"}.
|
||||
{"Message not found in forwarded payload","转发的有效载荷中找不到消息"}.
|
||||
{"Message body","消息正文"}.
|
||||
{"Message not found in forwarded payload","在转发的有效负载中找不到消息"}.
|
||||
{"Messages from strangers are rejected","拒绝来自陌生人的消息"}.
|
||||
{"Messages of type headline","标题类型的消息"}.
|
||||
{"Messages of type normal","普通类型的消息"}.
|
||||
@@ -255,7 +257,7 @@
|
||||
{"Moderators Only","仅主持人"}.
|
||||
{"Moderator","主持人"}.
|
||||
{"Module failed to handle the query","模块无法处理查询"}.
|
||||
{"Monday","星期一"}.
|
||||
{"Monday","周一"}.
|
||||
{"Multicast","多重映射"}.
|
||||
{"Multiple <item/> elements are not allowed by RFC6121","按照 RFC6121,多个 <item/> 元素是不允许的"}.
|
||||
{"Multi-User Chat","多用户聊天"}.
|
||||
@@ -278,24 +280,24 @@
|
||||
{"No child elements found","没有找到子元素"}.
|
||||
{"No data form found","没有找到数据表单"}.
|
||||
{"No Data","没有数据"}.
|
||||
{"No features available","没有可用特征"}.
|
||||
{"No features available","没有可用功能"}.
|
||||
{"No <forwarded/> element found","未找到 <forwarded/> 元素"}.
|
||||
{"No hook has processed this command","没有任何钩子已处理此命令"}.
|
||||
{"No info about last activity found","未找到上次活动的信息"}.
|
||||
{"No hook has processed this command","没有钩子处理此命令"}.
|
||||
{"No info about last activity found","未找到有关上次活动的信息"}.
|
||||
{"No 'item' element found","未找到“item”元素"}.
|
||||
{"No items found in this query","在此查询中找不到任何项目"}.
|
||||
{"No limit","不限"}.
|
||||
{"No limit","没有限制"}.
|
||||
{"No module is handling this query","没有模块正在处理此查询"}.
|
||||
{"No node specified","未指定节点"}.
|
||||
{"No 'password' found in data form","在数据表单中找不到“password”"}.
|
||||
{"No 'password' found in this query","在此查询中找不到“password”"}.
|
||||
{"No 'path' found in data form","在数据表单中找不到“path”"}.
|
||||
{"No pending subscriptions found","未找到待处理的订阅"}.
|
||||
{"No privacy list with this name found","未找到带此名称的隐私列表"}.
|
||||
{"No private data found in this query","此查询中未发现私有数据"}.
|
||||
{"No running node found","没有找到运行中的节点"}.
|
||||
{"No privacy list with this name found","未找到具有此名称的隐私列表"}.
|
||||
{"No private data found in this query","在此查询中找不到专用数据"}.
|
||||
{"No running node found","找不到正在运行的节点"}.
|
||||
{"No services available","无可用服务"}.
|
||||
{"No statistics found for this item","未找到此项的统计数据"}.
|
||||
{"No statistics found for this item","未找到此项目的统计数据"}.
|
||||
{"No 'to' attribute found in the invitation","邀请中未找到“to”属性"}.
|
||||
{"Nobody","没有人"}.
|
||||
{"Node already exists","节点已存在"}.
|
||||
@@ -310,92 +312,92 @@
|
||||
{"Not allowed","不允许"}.
|
||||
{"Not Found","没有找到"}.
|
||||
{"Not subscribed","未订阅"}.
|
||||
{"Notify subscribers when items are removed from the node","当从节点删除内容条目时通知订阅者"}.
|
||||
{"Notify subscribers when the node configuration changes","当节点设置改变时通知订阅者"}.
|
||||
{"Notify subscribers when the node is deleted","当节点被删除时通知订阅者"}.
|
||||
{"Notify subscribers when items are removed from the node","从节点中移除项目时通知订阅者"}.
|
||||
{"Notify subscribers when the node configuration changes","节点配置更改时通知订阅者"}.
|
||||
{"Notify subscribers when the node is deleted","删除节点时通知订阅者"}.
|
||||
{"November","十一月"}.
|
||||
{"Number of answers required","所需答案数量"}.
|
||||
{"Number of occupants","占用人数"}.
|
||||
{"Number of occupants","参与者人数"}.
|
||||
{"Number of Offline Messages","离线消息数量"}.
|
||||
{"Number of online users","在线用户数"}.
|
||||
{"Number of registered users","注册用户数"}.
|
||||
{"Number of seconds after which to automatically purge items, or `max` for no specific limit other than a server imposed maximum","等待多少秒后自动清除项目,“max”表示除服务器施加的最大值外没有特定限制"}.
|
||||
{"Occupants are allowed to invite others","允许占用者邀请别人"}.
|
||||
{"Occupants are allowed to query others","允许占用者查询别人"}.
|
||||
{"Occupants May Change the Subject","占用者可以更改话题"}.
|
||||
{"Occupants are allowed to invite others","允许参与者邀请别人"}.
|
||||
{"Occupants are allowed to query others","允许参与者查询别人"}.
|
||||
{"Occupants May Change the Subject","参与者可以更改话题"}.
|
||||
{"October","十月"}.
|
||||
{"OK","确定"}.
|
||||
{"Old Password:","旧密码:"}.
|
||||
{"Online Users","在线用户"}.
|
||||
{"Online","在线"}.
|
||||
{"Only collection node owners may associate leaf nodes with the collection","只有集合节点所有者可以将叶子节点与集合关联"}.
|
||||
{"Only deliver notifications to available users","仅将通知发送给可发送的用户"}.
|
||||
{"Only collection node owners may associate leaf nodes with the collection","只有集合节点所有者可以将叶节点与集合关联"}.
|
||||
{"Only deliver notifications to available users","仅向在线用户发送通知"}.
|
||||
{"Only <enable/> or <disable/> tags are allowed","仅允许 <enable/> 或 <disable/> 标签"}.
|
||||
{"Only <list/> element is allowed in this query","此查询中只允许 <list/> 元素"}.
|
||||
{"Only members may query archives of this room","只有成员才能查询此群聊的存档"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","只有主持人和参与者才允许在此群聊更改话题"}.
|
||||
{"Only moderators are allowed to change the subject in this room","只有主持人才允许在此群聊更改话题"}.
|
||||
{"Only moderators are allowed to retract messages","只有主持人才允许撤回消息"}.
|
||||
{"Only moderators and participants are allowed to change the subject in this room","只允许主持人和参与者在此群聊更改话题"}.
|
||||
{"Only moderators are allowed to change the subject in this room","只允许主持人在此群聊更改话题"}.
|
||||
{"Only moderators are allowed to retract messages","只允许主持人撤回消息"}.
|
||||
{"Only moderators can approve voice requests","只有主持人才能批准发言请求"}.
|
||||
{"Only occupants are allowed to send messages to the conference","只有占用者才允许向群聊发送消息"}.
|
||||
{"Only occupants are allowed to send queries to the conference","只有占用者才允许向群聊发送查询"}.
|
||||
{"Only publishers may publish","只有发布人可以发布"}.
|
||||
{"Only service administrators are allowed to send service messages","只有服务管理员才允许发送服务消息"}.
|
||||
{"Only those on a whitelist may associate leaf nodes with the collection","仅白名单用户可以将叶节点与集合关联"}.
|
||||
{"Only those on a whitelist may subscribe and retrieve items","仅白名单用户可以订阅和检索内容项"}.
|
||||
{"Only occupants are allowed to send messages to the conference","只允许参与者向群聊发送消息"}.
|
||||
{"Only occupants are allowed to send queries to the conference","只允许参与者向群聊发送查询"}.
|
||||
{"Only publishers may publish","只有发布者才能发布"}.
|
||||
{"Only service administrators are allowed to send service messages","只允许服务管理员发送服务消息"}.
|
||||
{"Only those on a whitelist may associate leaf nodes with the collection","只有白名单上的那些可以将叶节点与集合关联"}.
|
||||
{"Only those on a whitelist may subscribe and retrieve items","只有白名单上的那些才可以订阅和检索项目"}.
|
||||
{"Organization Name","组织名称"}.
|
||||
{"Organization Unit","组织单位"}.
|
||||
{"Other Modules Available:","其他可用模块:"}.
|
||||
{"Outgoing s2s Connections","出站 s2s 连接"}.
|
||||
{"Outgoing s2s Connections","传出 s2s 连接"}.
|
||||
{"Owner privileges required","需要所有者权限"}.
|
||||
{"Packet relay is denied by service policy","包中继被服务策略拒绝"}.
|
||||
{"Packet relay is denied by service policy","服务策略拒绝数据包中继"}.
|
||||
{"Participant ID","参与者 ID"}.
|
||||
{"Participant","参与者"}.
|
||||
{"Password Verification","密码验证"}.
|
||||
{"Password Verification:","密码验证:"}.
|
||||
{"Password","密码"}.
|
||||
{"Password:","密码:"}.
|
||||
{"Path to Dir","目录的路径"}.
|
||||
{"Path to Dir","目录路径"}.
|
||||
{"Path to File","文件路径"}.
|
||||
{"Payload semantic type information","有效载荷语义类型信息"}.
|
||||
{"Payload semantic type information","有效负载语义类型信息"}.
|
||||
{"Period: ","持续时间: "}.
|
||||
{"Persist items to storage","持久化内容条目"}.
|
||||
{"Persist items to storage","将项目持久化到存储"}.
|
||||
{"Persistent","持久"}.
|
||||
{"Ping query is incorrect","Ping 查询不正确"}.
|
||||
{"Ping","Ping"}.
|
||||
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","注意:这些选项仅将备份内置的 Mnesia 数据库。如果您正在使用 ODBC 模块,您还需要分别备份您的数据库。"}.
|
||||
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","注意:这些选项只会备份内置的 Mnesia 数据库。如果使用 ODBC 模块,还需要单独备份 SQL 数据库。"}.
|
||||
{"Please, wait for a while before sending new voice request","请稍候,然后再发送新的发言请求"}.
|
||||
{"Pong","Pong"}.
|
||||
{"Possessing 'ask' attribute is not allowed by RFC6121","按照 RFC6121, 不允许有“ask”属性"}.
|
||||
{"Present real Jabber IDs to","将用户真实 JID 显示给"}.
|
||||
{"Previous session not found","上一个会话未找到"}.
|
||||
{"Previous session PID has been killed","上一个会话的 PID 已被杀掉"}.
|
||||
{"Previous session PID has exited","上一个会话的 PID 已退出"}.
|
||||
{"Previous session PID is dead","上一个会话的 PID 已死"}.
|
||||
{"Previous session timed out","上一个会话已超时"}.
|
||||
{"Previous session PID has been killed","上一个会话 PID 已终止"}.
|
||||
{"Previous session PID has exited","上一个会话 PID 已退出"}.
|
||||
{"Previous session PID is dead","上一个会话 PID 已失效"}.
|
||||
{"Previous session timed out","上一个会话超时"}.
|
||||
{"private, ","私人, "}.
|
||||
{"Public","公开"}.
|
||||
{"Publish model","发布模型"}.
|
||||
{"Publish-Subscribe","发布—订阅"}.
|
||||
{"Publish-Subscribe","发布–订阅"}.
|
||||
{"PubSub subscriber request","PubSub 订阅者请求"}.
|
||||
{"Purge all items when the relevant publisher goes offline","相关发布人离线后清除所有选项"}.
|
||||
{"Push record not found","没有找到推送记录"}.
|
||||
{"Purge all items when the relevant publisher goes offline","相关发布者离线后清除所有项目"}.
|
||||
{"Push record not found","推送记录未找到"}.
|
||||
{"Queries to the conference members are not allowed in this room","不允许在此群聊中查询群聊成员"}.
|
||||
{"Query to another users is forbidden","禁止查询其他用户"}.
|
||||
{"RAM and disc copy","内存与磁盘复制"}.
|
||||
{"RAM copy","内存(RAM)复制"}.
|
||||
{"Really delete message of the day?","确实要删除每日消息吗?"}.
|
||||
{"RAM and disc copy","RAM 和磁盘副本"}.
|
||||
{"RAM copy","RAM 副本"}.
|
||||
{"Really delete message of the day?","是否确定删除每日消息?"}.
|
||||
{"Receive notification from all descendent nodes","接收所有后代节点的通知"}.
|
||||
{"Receive notification from direct child nodes only","仅接收所有直接子节点的通知"}.
|
||||
{"Receive notification of new items only","仅接收新内容项的通知"}.
|
||||
{"Receive notification from direct child nodes only","仅接收直接子节点的通知"}.
|
||||
{"Receive notification of new items only","仅接收新项目的通知"}.
|
||||
{"Receive notification of new nodes only","仅接收新节点的通知"}.
|
||||
{"Recipient is not in the conference room","接收者不在群聊"}.
|
||||
{"Register an XMPP account","注册 XMPP 账号"}.
|
||||
{"Register","注册"}.
|
||||
{"Remote copy","远程复制"}.
|
||||
{"Remote copy","远程副本"}.
|
||||
{"Remove a hat from a user","移除用户头衔"}.
|
||||
{"Remove User","移除用户"}.
|
||||
{"Replaced by new connection","被新的连接替换"}.
|
||||
{"Replaced by new connection","替换为新连接"}.
|
||||
{"Request has timed out","请求已超时"}.
|
||||
{"Request is ignored","请求被忽略"}.
|
||||
{"Requested role","请求的角色"}.
|
||||
@@ -410,47 +412,48 @@
|
||||
{"Roles for which Presence is Broadcasted","广播在线状态的角色"}.
|
||||
{"Roles that May Send Private Messages","可以发送私信的角色"}.
|
||||
{"Room Configuration","群聊配置"}.
|
||||
{"Room creation is denied by service policy","群聊创建被服务策略拒绝"}.
|
||||
{"Room creation is denied by service policy","服务策略拒绝群聊创建"}.
|
||||
{"Room description","群聊描述"}.
|
||||
{"Room Occupants","群聊占用者"}.
|
||||
{"Room Occupants","群聊参与者"}.
|
||||
{"Room terminates","群聊终止"}.
|
||||
{"Room title","群聊标题"}.
|
||||
{"Roster groups allowed to subscribe","允许订阅的花名册组"}.
|
||||
{"Roster size","花名册大小"}.
|
||||
{"Running Nodes","运行中的节点"}.
|
||||
{"Running Nodes","正在运行的节点"}.
|
||||
{"~s invites you to the room ~s","~s 邀请您加入群聊 ~s"}.
|
||||
{"Saturday","星期六"}.
|
||||
{"Saturday","周六"}.
|
||||
{"Search from the date","从日期搜索"}.
|
||||
{"Search Results for ","搜索结果 "}.
|
||||
{"Search the text","搜索文本"}.
|
||||
{"Search until the date","搜索截至日期"}.
|
||||
{"Search users in ","在以下位置搜索用户 "}.
|
||||
{"Send announcement to all online users on all hosts","发送通知给所有主机的在线用户"}.
|
||||
{"Send announcement to all online users","发送通知给所有在线用户"}.
|
||||
{"Send announcement to all users on all hosts","发送通知给所有主机上的所有用户"}.
|
||||
{"Send announcement to all users","发送通知给所有用户"}.
|
||||
{"Send announcement to all online users on all hosts","向所有主机上的所有在线用户发送公告"}.
|
||||
{"Send announcement to all online users","向所有在线用户发送公告"}.
|
||||
{"Send announcement to all users on all hosts","向所有主机上的所有用户发送公告"}.
|
||||
{"Send announcement to all users","向所有用户发送公告"}.
|
||||
{"September","九月"}.
|
||||
{"Server:","服务器:"}.
|
||||
{"Service list retrieval timed out","服务列表检索超时"}.
|
||||
{"Session state copying timed out","会话状态复制超时"}.
|
||||
{"Set message of the day and send to online users","设置每日消息并发送给在线用户"}.
|
||||
{"Set message of the day on all hosts and send to online users","在所有主机上设置每日消息并发送给在线用户"}.
|
||||
{"Shared Roster Groups","共享的花名册组群"}.
|
||||
{"Show Integral Table","显示完整列表"}.
|
||||
{"Show Ordinary Table","显示普通列表"}.
|
||||
{"Shared Roster Groups","共享的花名册组"}.
|
||||
{"Show Integral Table","显示完整表"}.
|
||||
{"Show Occupants Join/Leave","显示参与者加入/离开"}.
|
||||
{"Show Ordinary Table","显示普通表"}.
|
||||
{"Shut Down Service","关闭服务"}.
|
||||
{"SOCKS5 Bytestreams","SOCKS5 字节流"}.
|
||||
{"Some XMPP clients can store your password in the computer, but you should do this only in your personal computer for safety reasons.","某些 XMPP 客户端可以将您的密码存储在计算机中,但出于安全考虑,您应该仅在个人计算机中存储密码。"}.
|
||||
{"Sources Specs:","源参数:"}.
|
||||
{"Specify the access model","指定访问范例"}.
|
||||
{"Sources Specs:","源规格:"}.
|
||||
{"Specify the access model","指定访问模型"}.
|
||||
{"Specify the event message type","指定事件消息类型"}.
|
||||
{"Specify the publisher model","指定发布人范例"}.
|
||||
{"Stanza id is not valid","节 id 无效"}.
|
||||
{"Specify the publisher model","指定发布者模型"}.
|
||||
{"Stanza id is not valid","节 ID 无效"}.
|
||||
{"Stanza ID","节 ID"}.
|
||||
{"Statically specify a replyto of the node owner(s)","静态指定节点所有者的回复"}.
|
||||
{"Stopped Nodes","已经停止的节点"}.
|
||||
{"Store binary backup:","存储为二进制备份:"}.
|
||||
{"Store plain text backup:","存储为普通文本备份:"}.
|
||||
{"Statically specify a replyto of the node owner(s)","静态指定节点所有者的 replyto"}.
|
||||
{"Stopped Nodes","已停止的节点"}.
|
||||
{"Store binary backup:","存储二进制备份:"}.
|
||||
{"Store plain text backup:","存储明文备份:"}.
|
||||
{"Stream management is already enabled","流管理已启用"}.
|
||||
{"Stream management is not enabled","流管理未启用"}.
|
||||
{"Subject","话题"}.
|
||||
@@ -459,12 +462,12 @@
|
||||
{"Subscribers may publish","订阅者可以发布"}.
|
||||
{"Subscription requests must be approved and only subscribers may retrieve items","订阅请求必须得到批准,只有订阅者才能检索项目"}.
|
||||
{"Subscriptions are not allowed","不允许订阅"}.
|
||||
{"Sunday","星期天"}.
|
||||
{"Sunday","周日"}.
|
||||
{"Text associated with a picture","与图片相关的文字"}.
|
||||
{"Text associated with a sound","与声音相关的文字"}.
|
||||
{"Text associated with a video","与视频相关的文字"}.
|
||||
{"Text associated with speech","与语音相关的文字"}.
|
||||
{"That nickname is already in use by another occupant","该昵称已被另一占用者使用了"}.
|
||||
{"That nickname is already in use by another occupant","该昵称已被另一参与者使用了"}.
|
||||
{"That nickname is registered by another person","该昵称已被另一用户注册了"}.
|
||||
{"The account already exists","此账号已存在"}.
|
||||
{"The account was not unregistered","此账号未注销"}.
|
||||
@@ -472,35 +475,35 @@
|
||||
{"The CAPTCHA is valid.","验证码有效。"}.
|
||||
{"The CAPTCHA verification has failed","验证码检查失败"}.
|
||||
{"The captcha you entered is wrong","您输入的验证码错误"}.
|
||||
{"The child nodes (leaf or collection) associated with a collection","关联集合的字节点(叶子或集合)"}.
|
||||
{"The collections with which a node is affiliated","加入结点的集合"}.
|
||||
{"The child nodes (leaf or collection) associated with a collection","与集合关联的子节点(叶或集合)"}.
|
||||
{"The collections with which a node is affiliated","节点所属的集合"}.
|
||||
{"The DateTime at which a leased subscription will end or has ended","租赁订阅将结束或已结束的日期时间"}.
|
||||
{"The datetime when the node was created","节点创建的日期时间"}.
|
||||
{"The default language of the node","该节点的默认语言"}.
|
||||
{"The datetime when the node was created","创建节点的日期时间"}.
|
||||
{"The default language of the node","节点的默认语言"}.
|
||||
{"The feature requested is not supported by the conference","群聊不支持请求的功能"}.
|
||||
{"The JID of the node creator","节点创建者的 JID"}.
|
||||
{"The JIDs of those to contact with questions","问题联系人的 JID"}.
|
||||
{"The JIDs of those with an affiliation of owner","拥有所有者从属关系的用户的 JID"}.
|
||||
{"The JIDs of those to contact with questions","有问题要联系的人的 JID"}.
|
||||
{"The JIDs of those with an affiliation of owner","与所有者的从属关系有关的用户 JID"}.
|
||||
{"The JIDs of those with an affiliation of publisher","与发布者的从属关系有关的用户 JID"}.
|
||||
{"The list of all online users","所有在线用户列表"}.
|
||||
{"The list of all users","所有用户列表"}.
|
||||
{"The list of all online users","所有在线用户的列表"}.
|
||||
{"The list of all users","所有用户的列表"}.
|
||||
{"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, or `max` for no specific limit other than a server imposed maximum","可以与集合相关联的最大子节点数,“max”表示除服务器施加的最大值外没有特定限制"}.
|
||||
{"The minimum number of milliseconds between sending any two notification digests","发送任何两个通知摘要之间的最小毫秒数"}.
|
||||
{"The name of the node","该节点的名称"}.
|
||||
{"The node is a collection node","该节点是集合节点"}.
|
||||
{"The node is a leaf node (default)","该节点是叶子节点(默认)"}.
|
||||
{"The maximum number of child nodes that can be associated with a collection, or `max` for no specific limit other than a server imposed maximum","可以与集合关联的子节点的最大数量,或“max”表示除服务器施加的最大值外没有特定限制"}.
|
||||
{"The minimum number of milliseconds between sending any two notification digests","发送任意两个通知摘要之间的最小毫秒数"}.
|
||||
{"The name of the node","节点的名称"}.
|
||||
{"The node is a collection node","节点是集合节点"}.
|
||||
{"The node is a leaf node (default)","节点是叶节点(默认)"}.
|
||||
{"The NodeID of the relevant node","相关节点的 NodeID"}.
|
||||
{"The number of pending incoming presence subscription requests","待处理的传入状态订阅请求数"}.
|
||||
{"The number of subscribers to the node","该节点的订阅用户数"}.
|
||||
{"The number of unread or undelivered messages","未读或未发送的消息数"}.
|
||||
{"The number of pending incoming presence subscription requests","待处理的传入在线状态订阅请求数"}.
|
||||
{"The number of subscribers to the node","节点的订阅者数量"}.
|
||||
{"The number of unread or undelivered messages","未读或未传递的消息数"}.
|
||||
{"The password contains unacceptable characters","密码包含不可接受的字符"}.
|
||||
{"The password is too weak","密码强度太弱"}.
|
||||
{"The password is too weak","密码太弱"}.
|
||||
{"the password is","密码是"}.
|
||||
{"The password of your XMPP account was successfully changed.","您的 XMPP 账号密码已成功更改。"}.
|
||||
{"The password was not changed","密码未更改"}.
|
||||
{"The passwords are different","密码不一致"}.
|
||||
{"The presence states for which an entity wants to receive notifications","实体要为其接收通知的状态"}.
|
||||
{"The passwords are different","密码不同"}.
|
||||
{"The presence states for which an entity wants to receive notifications","实体要接收通知的在线状态"}.
|
||||
{"The query is only allowed from local users","仅允许本地用户查询"}.
|
||||
{"The query must not contain <item/> elements","查询不能包含 <item/> 元素"}.
|
||||
{"The room subject can be modified by participants","群聊话题可由参与者修改"}.
|
||||
@@ -516,30 +519,30 @@
|
||||
{"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 room is not anonymous","此群聊不是匿名的"}.
|
||||
{"This room is not anonymous","此群聊是非匿名的"}.
|
||||
{"This service can not process the address: ~s","此服务无法处理地址:~s"}.
|
||||
{"Thursday","星期四"}.
|
||||
{"Thursday","周四"}.
|
||||
{"Time delay","时间延迟"}.
|
||||
{"Timed out waiting for stream resumption","等待流恢复超时"}.
|
||||
{"To register, visit ~s","要注册,请访问 ~s"}.
|
||||
{"To ~ts","发送到~ts"}.
|
||||
{"Token TTL","TTL 令牌"}.
|
||||
{"Too many active bytestreams","活跃的字节流太多"}.
|
||||
{"To ~ts","到 ~ts"}.
|
||||
{"Token TTL","令牌 TTL"}.
|
||||
{"Too many active bytestreams","活动字节流太多"}.
|
||||
{"Too many CAPTCHA requests","验证码请求太多"}.
|
||||
{"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","有太多 (~p) 失败的身份验证来自此 IP 地址 (~s),将在 UTC 时间 ~s 解除对该地址的屏蔽"}.
|
||||
{"Too many receiver fields were specified","指定的接收者字段太多"}.
|
||||
{"Too many unacked stanzas","未被确认的节太多"}.
|
||||
{"Too many unacked stanzas","太多未确认的节"}.
|
||||
{"Too many users in this conference","此群聊中的用户太多"}.
|
||||
{"Traffic rate limit is exceeded","已经超过传输率限制"}.
|
||||
{"Traffic rate limit is exceeded","超过流量速率限制"}.
|
||||
{"~ts's Offline Messages Queue","~ts 的离线消息队列"}.
|
||||
{"Tuesday","星期二"}.
|
||||
{"Tuesday","周二"}.
|
||||
{"Unable to generate a CAPTCHA","无法生成验证码"}.
|
||||
{"Unable to register route on existing local domain","在已存在的本地域上无法注册路由"}.
|
||||
{"Unauthorized","未认证的"}.
|
||||
{"Unexpected action","意外行为"}.
|
||||
{"Unable to register route on existing local domain","无法在现有本地域上注册路由"}.
|
||||
{"Unauthorized","未经授权"}.
|
||||
{"Unexpected action","意外的操作"}.
|
||||
{"Unexpected error condition: ~p","意外错误条件:~p"}.
|
||||
{"Uninstall","卸载"}.
|
||||
{"Unregister an XMPP account","注销 XMPP 账号"}.
|
||||
@@ -548,8 +551,8 @@
|
||||
{"Unsupported version","不支持的版本"}.
|
||||
{"Update message of the day (don't send)","更新每日消息(不发送)"}.
|
||||
{"Update message of the day on all hosts (don't send)","更新所有主机上的每日消息(不发送)"}.
|
||||
{"Update specs to get modules source, then install desired ones.","更新参数获取模块源,然后安装所需的模块。"}.
|
||||
{"Update Specs","更新参数"}.
|
||||
{"Update specs to get modules source, then install desired ones.","更新规格以获取模块源,然后安装所需的模块。"}.
|
||||
{"Update Specs","更新规格"}.
|
||||
{"Upgrade","升级"}.
|
||||
{"URL for Archived Discussion Logs","存档讨论日志的 URL"}.
|
||||
{"User already exists","用户已存在"}.
|
||||
@@ -566,33 +569,33 @@
|
||||
{"Users","用户"}.
|
||||
{"User","用户"}.
|
||||
{"Value 'get' of 'type' attribute is not allowed","不允许“type”属性的“get”值"}.
|
||||
{"Value of '~s' should be boolean","'~s' 的值应为布尔型"}.
|
||||
{"Value of '~s' should be datetime string","'~s' 的值应为日期时间字符串"}.
|
||||
{"Value of '~s' should be integer","'~s' 的值应为整数"}.
|
||||
{"Value of '~s' should be boolean","“~s”的值应为布尔值"}.
|
||||
{"Value of '~s' should be datetime string","“~s”的值应为日期时间字符串"}.
|
||||
{"Value of '~s' should be integer","“~s”的值应为整数"}.
|
||||
{"Value 'set' of 'type' attribute is not allowed","不允许“type”属性的“set”值"}.
|
||||
{"vCard User Search","vCard 用户搜索"}.
|
||||
{"View joined MIX channels","查看已加入的 MIX 频道"}.
|
||||
{"Virtual Hosts","虚拟主机"}.
|
||||
{"Visitors are not allowed to change their nicknames in this room","不允许访客在此群聊中更改其昵称"}.
|
||||
{"Visitors are not allowed to send messages to all occupants","不允许访客向所有占用者发送信息"}.
|
||||
{"Visitors are not allowed to send messages to all occupants","不允许访客向所有参与者发送消息"}.
|
||||
{"Visitor","访客"}.
|
||||
{"Voice requests are disabled in this conference","此群聊中禁用发言请求"}.
|
||||
{"Voice request","发言请求"}.
|
||||
{"Wednesday","星期三"}.
|
||||
{"When a new subscription is processed and whenever a subscriber comes online","当新的订阅被处理和当订阅者上线"}.
|
||||
{"When a new subscription is processed","当新的订阅被处理"}.
|
||||
{"When to send the last published item","何时发送最新发布的内容条目"}.
|
||||
{"Whether an entity wants to receive an XMPP message body in addition to the payload format","除有效载荷格式外,实体是否还希望接收 XMPP 消息正文"}.
|
||||
{"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually","实体是否要接收通知的摘要(汇总)或单独接收所有通知"}.
|
||||
{"Wednesday","周三"}.
|
||||
{"When a new subscription is processed and whenever a subscriber comes online","处理新订阅时和订阅者上线时"}.
|
||||
{"When a new subscription is processed","处理新订阅时"}.
|
||||
{"When to send the last published item","何时发送最后发布的项目"}.
|
||||
{"Whether an entity wants to receive an XMPP message body in addition to the payload format","除有效负载格式外,实体是否还希望接收 XMPP 消息正文"}.
|
||||
{"Whether an entity wants to receive digests (aggregations) of notifications or all notifications individually","实体是要接收通知摘要(汇总) 还是要单独接收所有通知"}.
|
||||
{"Whether an entity wants to receive or disable notifications","实体是否要接收或禁用通知"}.
|
||||
{"Whether owners or publisher should receive replies to items","所有者或发布者是否应收到对项目的回复"}.
|
||||
{"Whether the node is a leaf (default) or a collection","节点是叶子(默认)还是集合"}.
|
||||
{"Whether the node is a leaf (default) or a collection","节点是叶(默认)还是集合"}.
|
||||
{"Whether to allow subscriptions","是否允许订阅"}.
|
||||
{"Whether to make all subscriptions temporary, based on subscriber presence","是否根据订阅者的存在将所有订阅设为临时"}.
|
||||
{"Whether to make all subscriptions temporary, based on subscriber presence","是否根据订阅者的在线状态将所有订阅设为临时订阅"}.
|
||||
{"Whether to notify owners about new subscribers and unsubscribes","是否通知所有者新的订阅者和退订者"}.
|
||||
{"Who can send private messages","谁可以发送私信"}.
|
||||
{"Who may associate leaf nodes with a collection","谁可以将叶子节点与集合关联"}.
|
||||
{"Wrong parameters in the web formulary","网络配方中的参数错误"}.
|
||||
{"Who may associate leaf nodes with a collection","谁可以将叶节点与集合关联"}.
|
||||
{"Wrong parameters in the web formulary","web 公式中的参数错误"}.
|
||||
{"Wrong xmlns","错误的 xmlns"}.
|
||||
{"XMPP Account Registration","XMPP 账号注册"}.
|
||||
{"XMPP Domains","XMPP 域"}.
|
||||
@@ -600,12 +603,12 @@
|
||||
{"XMPP Show Value of Chat","XMPP 的聊天显示值"}.
|
||||
{"XMPP Show Value of DND (Do Not Disturb)","XMPP 的 DND(请勿打扰)显示值"}.
|
||||
{"XMPP Show Value of XA (Extended Away)","XMPP 的 XA(延长离开)显示值"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","关联发布—订阅节点的 XMPP URI"}.
|
||||
{"XMPP URI of Associated Publish-Subscribe Node","关联发布–订阅节点的 XMPP URI"}.
|
||||
{"You are being removed from the room because of a system shutdown","由于系统关闭,您将会从群聊中移除"}.
|
||||
{"You are not allowed to send private messages","不允许您发送私信"}.
|
||||
{"You are not joined to the channel","您未加入频道"}.
|
||||
{"You can later change your password using an XMPP client.","您之后可以使用 XMPP 客户端更改密码。"}.
|
||||
{"You have been banned from this room","您已被此群聊封禁"}.
|
||||
{"You have been banned from this room","禁止您进入此群聊"}.
|
||||
{"You have joined too many conferences","您加入了太多群聊"}.
|
||||
{"You must fill in field \"Nickname\" in the form","您必须在表单中填写“昵称”字段"}.
|
||||
{"You need a client that supports x:data and CAPTCHA to register","您需要支持 x:data 和验证码的客户端来注册"}.
|
||||
|
||||
+10
-9
@@ -33,11 +33,11 @@
|
||||
{if_var_true, redis,
|
||||
{eredis, "~> 1.2.0", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
|
||||
{if_var_true, sip,
|
||||
{esip, "~> 1.0.52", {git, "https://github.com/processone/esip", {tag, "1.0.53"}}}},
|
||||
{esip, "~> 1.0.52", {git, "https://github.com/processone/esip", {tag, "1.0.56"}}}},
|
||||
{if_var_true, zlib,
|
||||
{ezlib, "~> 1.0.12", {git, "https://github.com/processone/ezlib", {tag, "1.0.13"}}}},
|
||||
{fast_tls, "~> 1.1.19", {git, "https://github.com/processone/fast_tls", {tag, "1.1.20"}}},
|
||||
{fast_xml, "~> 1.1.51", {git, "https://github.com/processone/fast_xml", {tag, "1.1.52"}}},
|
||||
{fast_tls, "~> 1.1.19", {git, "https://github.com/processone/fast_tls", {tag, "1.1.22"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.53"}}},
|
||||
{fast_yaml, "~> 1.0.36", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.37"}}},
|
||||
{idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
|
||||
{if_version_below, "27",
|
||||
@@ -56,20 +56,20 @@
|
||||
{luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}}
|
||||
}},
|
||||
{mqtree, "~> 1.0.16", {git, "https://github.com/processone/mqtree", {tag, "1.0.17"}}},
|
||||
{p1_acme, "~> 1.0.23", {git, "https://github.com/processone/p1_acme", {tag, "1.0.23"}}},
|
||||
{p1_acme, "~> 1.0.23", {git, "https://github.com/processone/p1_acme", {tag, "1.0.24"}}},
|
||||
{if_var_true, mysql,
|
||||
{p1_mysql, "~> 1.0.24", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.24"}}}},
|
||||
{p1_mysql, "~> 1.0.24", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.25"}}}},
|
||||
{p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}},
|
||||
{if_var_true, pgsql,
|
||||
{p1_pgsql, "~> 1.1.26", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.26"}}}},
|
||||
{p1_pgsql, "~> 1.1.26", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.28"}}}},
|
||||
{p1_utils, "~> 1.0.25", {git, "https://github.com/processone/p1_utils", {tag, "1.0.26"}}},
|
||||
{pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}},
|
||||
{if_var_true, sqlite,
|
||||
{sqlite3, "~> 1.1.14", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}},
|
||||
{stringprep, "~> 1.0.29", {git, "https://github.com/processone/stringprep", {tag, "1.0.30"}}},
|
||||
{if_var_true, stun,
|
||||
{stun, "~> 1.2.12", {git, "https://github.com/processone/stun", {tag, "1.2.13"}}}},
|
||||
{xmpp, "~> 1.8.2", {git, "https://github.com/processone/xmpp", {tag, "1.8.2"}}},
|
||||
{stun, "~> 1.2.12", {git, "https://github.com/processone/stun", {tag, "1.2.15"}}}},
|
||||
{xmpp, "~> 1.9.0", {git, "https://github.com/processone/xmpp", {tag, "1.9.0"}}},
|
||||
{yconf, "~> 1.0.15", {git, "https://github.com/processone/yconf", {tag, "1.0.16"}}}
|
||||
]}.
|
||||
|
||||
@@ -156,7 +156,8 @@
|
||||
{branch, "consolidation_fix"}}}
|
||||
}]}}.
|
||||
{if_rebar3, {project_plugins, [configure_deps,
|
||||
{if_var_true, tools, rebar3_format}
|
||||
{if_var_true, tools, rebar3_format},
|
||||
{if_var_true, tools, rebar3_lint}
|
||||
]}}.
|
||||
{if_not_rebar3, {plugins, [
|
||||
deps_erl_opts, override_deps_versions2, override_opts, configure_deps
|
||||
|
||||
+57
-62
@@ -1,91 +1,86 @@
|
||||
{"1.2.0",
|
||||
[{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},1},
|
||||
{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.30">>},0},
|
||||
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.22">>},0},
|
||||
{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.31">>},0},
|
||||
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.23">>},0},
|
||||
{<<"epam">>,{pkg,<<"epam">>,<<"1.0.14">>},0},
|
||||
{<<"eredis">>,{pkg,<<"eredis">>,<<"1.2.0">>},0},
|
||||
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.52">>},0},
|
||||
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.12">>},0},
|
||||
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.19">>},0},
|
||||
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.51">>},0},
|
||||
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.36">>},0},
|
||||
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.56">>},0},
|
||||
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.13">>},0},
|
||||
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.22">>},0},
|
||||
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.53">>},0},
|
||||
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.37">>},0},
|
||||
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0},
|
||||
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.1">>},0},
|
||||
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.2">>},1},
|
||||
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.10">>},0},
|
||||
{<<"luerl">>,{pkg,<<"luerl">>,<<"1.2.0">>},0},
|
||||
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.16">>},0},
|
||||
{<<"p1_acme">>,
|
||||
{git,"https://github.com/processone/p1_acme",
|
||||
{ref,"176b4a8c67627c3229fbea3c05d82a5d567e6e49"}},
|
||||
0},
|
||||
{<<"p1_mysql">>,
|
||||
{git,"https://github.com/processone/p1_mysql",
|
||||
{ref,"0dccb755a60cda61d41e7b092b344d38d1ebc802"}},
|
||||
0},
|
||||
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.17">>},0},
|
||||
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.24">>},0},
|
||||
{<<"p1_mysql">>,{pkg,<<"p1_mysql">>,<<"1.0.25">>},0},
|
||||
{<<"p1_oauth2">>,{pkg,<<"p1_oauth2">>,<<"0.6.14">>},0},
|
||||
{<<"p1_pgsql">>,
|
||||
{git,"https://github.com/processone/p1_pgsql",
|
||||
{ref,"fe0bb2a2a2ae21cd9fc48b81520cfce508520a56"}},
|
||||
0},
|
||||
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.25">>},0},
|
||||
{<<"pkix">>,
|
||||
{git,"https://github.com/processone/pkix",
|
||||
{ref,"03be27c7168449bdfeb8c5b6cc0f800f71f8cfe9"}},
|
||||
0},
|
||||
{<<"sqlite3">>,{pkg,<<"sqlite3">>,<<"1.1.14">>},0},
|
||||
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.29">>},0},
|
||||
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.12">>},0},
|
||||
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.28">>},0},
|
||||
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.26">>},0},
|
||||
{<<"pkix">>,{pkg,<<"pkix">>,<<"1.0.10">>},0},
|
||||
{<<"sqlite3">>,{pkg,<<"sqlite3">>,<<"1.1.15">>},0},
|
||||
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.30">>},0},
|
||||
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.15">>},0},
|
||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
|
||||
{<<"xmpp">>,
|
||||
{git,"https://github.com/processone/xmpp",
|
||||
{ref,"a1fb778bd385d832f913e564558152ea507dac37"}},
|
||||
0},
|
||||
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.15">>},0}]}.
|
||||
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.9.0">>},0},
|
||||
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.16">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
|
||||
{<<"cache_tab">>, <<"6D35EECFB65FBE5FC85988503A27338D32DE01243F3FC8EA3EE7161AF08725A4">>},
|
||||
{<<"eimp">>, <<"FA9B376EF0B50E8455DB15C7C11DEA4522C6902E04412288AAB436D26335F6EB">>},
|
||||
{<<"cache_tab">>, <<"E4097B50A6F373AB1E0A5F01BAB0BEF6626771A4CD6C93404ED6D54810E11FBC">>},
|
||||
{<<"eimp">>, <<"AAF32EFAB061143403DADBAA63E699EF8E81702FBFA96FD436D5E9BE4D6A8D3A">>},
|
||||
{<<"epam">>, <<"AA0B85D27F4EF3A756AE995179DF952A0721237E83C6B79D644347B75016681A">>},
|
||||
{<<"eredis">>, <<"0B8E9CFC2C00FA1374CD107EA63B49BE08D933DF2CF175E6A89B73DD9C380DE4">>},
|
||||
{<<"esip">>, <<"A2840287C493A4280E6FBA57A257706843B025C315875E38B03FD07190E22DBA">>},
|
||||
{<<"ezlib">>, <<"FFE906BA10D03AAEE7977E1E0E81D9FFC3BB8B47FB9CD8E2E453507A2E56221F">>},
|
||||
{<<"fast_tls">>, <<"F52731A4B35259FA06CF23E2A0732920AD9EFCE7C3D68377F129A474998747BB">>},
|
||||
{<<"fast_xml">>, <<"A7F8C6942591632309099386D5C339C89997AC2BBDD1216F6C196DEE6D7828A9">>},
|
||||
{<<"fast_yaml">>, <<"65413A34A570FD4E205A460BA602E4EE7A682F35C22D2E1C839025DBF515105C">>},
|
||||
{<<"esip">>, <<"63C0FDC667BE751714E1E5C14621A9334F21B60AC1BB68BE889454CA9CA021B7">>},
|
||||
{<<"ezlib">>, <<"3C7F62862850A241159C10B218ECF580BCE54D0890601B65144DACC2633BE2B0">>},
|
||||
{<<"fast_tls">>, <<"44356B256AFAD4399C2FC5059A3066669DAFD8BD4E4E796C9C1CF8910DDD265E">>},
|
||||
{<<"fast_xml">>, <<"1EF4F6E5995BCFA94800A46B460C3400C19C0A533948B12200A2E2FB1A2BE427">>},
|
||||
{<<"fast_yaml">>, <<"F71D472FBF787CCD161B914D1EB486116A0F4F2E835337A378FBD31B59D2E74B">>},
|
||||
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
|
||||
{<<"jiffy">>, <<"ACA10F47AA91697BF24AB9582C74E00E8E95474C7EF9F76D4F1A338D0F5DE21B">>},
|
||||
{<<"jiffy">>, <<"A9B6C9A7EC268E7CF493D028F0A4C9144F59CCB878B1AFE42841597800840A1B">>},
|
||||
{<<"jose">>, <<"A903F5227417BD2A08C8A00A0CBCC458118BE84480955E8D251297A425723F83">>},
|
||||
{<<"luerl">>, <<"60F05F4240F0E7C148DDB79B67B8FF972734AAD237AA74C83D0748B8214C8EF0">>},
|
||||
{<<"mqtree">>, <<"F8F8B4971E4CA94313BA9BCAAA1AA1077DAABA5E3FD3468FFB491420A4CC3593">>},
|
||||
{<<"mqtree">>, <<"82F54B8F2D22B4445DB1D6CCCB7FE9EAD049D61410C29E32475F3CEB3EE62A89">>},
|
||||
{<<"p1_acme">>, <<"E19876618EB0BE22815ACA99640CB88CD5C86E4239B8E8DC15B4E5D7854EF7E2">>},
|
||||
{<<"p1_mysql">>, <<"875D4CBDC7C9990270DF3292CCE2514E4C18A9FDFD19BEF258CB4D0C45B4F243">>},
|
||||
{<<"p1_oauth2">>, <<"1C5F82535574DE87E2059695AC4B91F8F9AEBACBC1C80287DAE6F02552D47AEA">>},
|
||||
{<<"p1_utils">>, <<"2D39B5015A567BBD2CC7033EEB93A7C60D8C84EFE1EF69A3473FAA07FA268187">>},
|
||||
{<<"sqlite3">>, <<"F9EA0CFF8540865FDFDB7E24EEF34DC46677364B1C070896E99B5BF08C8A7FD7">>},
|
||||
{<<"stringprep">>, <<"02F23E8C3A219A3DFE40A22E908BECE3A2F68AF0FF599EA8A7B714ECB21E62EE">>},
|
||||
{<<"stun">>, <<"A65DF67A8AAAECB6A94D687977B2E9F161820819910CB97BBE26A3525356525B">>},
|
||||
{<<"p1_pgsql">>, <<"08ECA7D2AFC81BA6D757B572F4A57EF3A2383B0C7B785FDE38184BC368376D54">>},
|
||||
{<<"p1_utils">>, <<"67B0C4AC9FA3BA3EF563B31AA111B0A004439A37FAC85E027F1C3617E1C7EC6C">>},
|
||||
{<<"pkix">>, <<"D3BFADF7B7CFE2A3636F1B256C9CCE5F646A07CE31E57EE527668502850765A0">>},
|
||||
{<<"sqlite3">>, <<"E819DEFD280145C328457D7AF897D2E45E8E5270E18812EE30B607C99CDD21AF">>},
|
||||
{<<"stringprep">>, <<"46CF0FF631B3E7328F61F20B454D59428D87738F25D709798B5DCBB9B83C23F1">>},
|
||||
{<<"stun">>, <<"EEC510AF6509201FF97F1F2C87B7977C833BF29C04E985383370EC21F04E4CCF">>},
|
||||
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>},
|
||||
{<<"yconf">>, <<"E22998B3D7728270BDD06162A9515BD142B14FAE8927CBDBD3EF639C32AA6F7A">>}]},
|
||||
{<<"xmpp">>, <<"D92446BF51D36ADDA02DB63B963FE6D4A1EDE33E59B38A43D9B90AFD20C25B74">>},
|
||||
{<<"yconf">>, <<"D59521D66FF89F219411B6E9277CD6FEEC7CC6FCE11554E67DE02A8D0A470479">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
|
||||
{<<"cache_tab">>, <<"6D8A5E00D8F84C42627706A6DBEDB02E34D58495F3ED61935C8475CA0531CDA0">>},
|
||||
{<<"eimp">>, <<"B3B9FFB1D9A5F4A2BA88AC418A819164932D9A9D3A2FC3D32CA338CE855C4392">>},
|
||||
{<<"cache_tab">>, <<"8582B60A4A09B247EF86355BA9E07FCE9E11EDC0345A775C9171F971C72B6351">>},
|
||||
{<<"eimp">>, <<"907C780023CB2893E4FC4BDBE6A4F02C355913862AC67F0ECC26605E816B628A">>},
|
||||
{<<"epam">>, <<"2F3449E72885A72A6C2A843F561ADD0FC2F70D7A21F61456930A547473D4D989">>},
|
||||
{<<"eredis">>, <<"D9B5ABEF2C2C8ABA8F32AA018203E0B3DC8B1157773B254AB1D4C2002317F1E1">>},
|
||||
{<<"esip">>, <<"6F00165395900500AA262CE0297162D93931C78C1464D89FD0EDC6E3D6BC011F">>},
|
||||
{<<"ezlib">>, <<"30E94355FB42260AAB6E12582CB0C56BF233515E655C8AEAF48760E7561E4EBB">>},
|
||||
{<<"fast_tls">>, <<"DB34322C8782D4C5139CCB80709D8EC8C38089B44262EDD0C2F660AC495BD389">>},
|
||||
{<<"fast_xml">>, <<"7FCE41B7D1A4BA438A2D7A088DABE74A3CA0739F1AF2ABCB77E62DAF43E0409A">>},
|
||||
{<<"fast_yaml">>, <<"1ABE8F758FC2A86B08EDFF80BBC687CFD41EBC1412CFEC0EF4A0ACFCD032052F">>},
|
||||
{<<"esip">>, <<"9EF3660CEF93B623F7368DCD5C79F4E704358631909E6DD464E335378815DA1F">>},
|
||||
{<<"ezlib">>, <<"9EE62AB3F8ED55A0FD11A9569FCB8E458683F95575417272192B069F092ABFBB">>},
|
||||
{<<"fast_tls">>, <<"E65779AEFB7AB15C4755230FEF8077E687D20CC5A3984A5974F9F657E8E2485B">>},
|
||||
{<<"fast_xml">>, <<"5064336D6F363EEE5097AA5DC5CED9B67F05152F2E6B8520FD50D268C2AB839C">>},
|
||||
{<<"fast_yaml">>, <<"8DE868721BF7E2172414F7D3148EDE0F3C922B496455CD625DD5C4429515A769">>},
|
||||
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
|
||||
{<<"jiffy">>, <<"62E1F0581C3C19C33A725C781DFA88410D8BFF1BBAFC3885A2552286B4785C4C">>},
|
||||
{<<"jiffy">>, <<"BB61BC42A720BBD33CB09A410E48BB79A61012C74CB8B3E75F26D988485CF381">>},
|
||||
{<<"jose">>, <<"0D6CD36FF8BA174DB29148FC112B5842186B68A90CE9FC2B3EC3AFE76593E614">>},
|
||||
{<<"luerl">>, <<"9CAFD4F6094FF0F5A9D278FD81D60D3E026C820BDFB6CACD4B1BD909F21B525D">>},
|
||||
{<<"mqtree">>, <<"C87D1C95575DB65AF29B795C9DAA3BED43F5C1BF84072A74469659BCF53594EB">>},
|
||||
{<<"mqtree">>, <<"5FE8B7CF8FBC4783D0FCEB94654AC2BBF3242A58CD0397D249DED8AE021BE2A3">>},
|
||||
{<<"p1_acme">>, <<"F1451D706595EF997AB1CA17162DDAC58F874AC97E315A5FADBE3CFA26148002">>},
|
||||
{<<"p1_mysql">>, <<"E6187FFAE95B726098E88F3EE6F2344AC259CE2C26E0EE403B05FEEF341AE434">>},
|
||||
{<<"p1_oauth2">>, <<"1FD3AC474E43722D9D5A87C6DF8D36F698ED87AF7BB81CBBB66361451D99AE8F">>},
|
||||
{<<"p1_utils">>, <<"9219214428F2C6E5D3187FF8EB9A8783695C2427420BE9A259840E07ADA32847">>},
|
||||
{<<"sqlite3">>, <<"85054B6CA297343C159ED6794A473FF2C8EEABD854B6FE02F711C0BFD373CE86">>},
|
||||
{<<"stringprep">>, <<"928EBA304C3006EB1512110EBD7B87DB163B00859A09375A1E4466152C6C462A">>},
|
||||
{<<"stun">>, <<"A2055032B6D338D0454142004BCB12FAFB0C64AB1F273F1D0C6923EBBC8EDE40">>},
|
||||
{<<"p1_pgsql">>, <<"655BB75DF036ACE55DDCE17CA741143C42E0667C6206AC27A4DCBC65F71AC9EF">>},
|
||||
{<<"p1_utils">>, <<"D0379E8C1156B98BD64F8129C1DE022FCCA4F2FDB7486CE73BF0ED2C3376B04C">>},
|
||||
{<<"pkix">>, <<"E02164F83094CB124C41B1AB28988A615D54B9ADC38575F00F19A597A3AC5D0E">>},
|
||||
{<<"sqlite3">>, <<"3C0BA4E13322C2AD49DE4E2DDD28311366ADDE54BEAE8DBA9D9E3888F69D2857">>},
|
||||
{<<"stringprep">>, <<"F6FC9B3384A03877830F89B2F38580CAF3F4A27448A4A333D6A8C3975C220B9A">>},
|
||||
{<<"stun">>, <<"F6D8A541A29FD13F2CE658B676C0CC661262B96E045B52DEF1644B75EBC0EDEF">>},
|
||||
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>},
|
||||
{<<"yconf">>, <<"7FF2AB24D3C9833842716B9AAAA01A8F96641A7695CBB701B03445C4DEF01117">>}]}
|
||||
{<<"xmpp">>, <<"C1B91BE74A9A9503AFA6766F756477516920FFBFEEA0C260C2FA171355F53C27">>},
|
||||
{<<"yconf">>, <<"E947813273F38711C7B2E5A8E4ACC9A51C7BBE854F744A345F60300B38586C89">>}]}
|
||||
].
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ CREATE INDEX i_archive_sh_timestamp USING BTREE ON archive(server_host(191), tim
|
||||
CREATE INDEX i_archive_sh_username_origin_id USING BTREE ON archive(server_host(191), username(191), origin_id(191));
|
||||
|
||||
-- To update 'archive' from ejabberd <= 23.10:
|
||||
-- ALTER TABLE archive ADD COLUMN origin_id text NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE archive ADD COLUMN origin_id varchar(191) NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE archive ALTER COLUMN origin_id DROP DEFAULT;
|
||||
-- CREATE INDEX i_archive_sh_username_origin_id USING BTREE ON archive(server_host(191), username(191), origin_id(191));
|
||||
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ CREATE INDEX i_timestamp USING BTREE ON archive(timestamp);
|
||||
CREATE INDEX i_archive_username_origin_id USING BTREE ON archive(username(191), origin_id(191));
|
||||
|
||||
-- To update 'archive' from ejabberd <= 23.10:
|
||||
-- ALTER TABLE archive ADD COLUMN origin_id text NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE archive ADD COLUMN origin_id varchar(191) NOT NULL DEFAULT '';
|
||||
-- ALTER TABLE archive ALTER COLUMN origin_id DROP DEFAULT;
|
||||
-- CREATE INDEX i_archive_username_origin_id USING BTREE ON archive(username(191), origin_id(191));
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
{ok, Terms} ->
|
||||
Backends = [mssql, mysql, odbc, pgsql, redis, sqlite],
|
||||
EBs = lists:filter(fun(Backend) -> lists:member({Backend, true}, Terms) end, Backends),
|
||||
Elixirs = case lists:keyfind(elixir, 1, Terms) of
|
||||
{elixir, true} -> [elixir, iex, logger, mix];
|
||||
_ -> []
|
||||
Elixirs = case proplists:get_bool(elixir, Terms) of
|
||||
true -> [elixir, logger, mix];
|
||||
false -> []
|
||||
end,
|
||||
|
||||
ProfileEnvironmentVariable = os:getenv("REBAR_PROFILE"),
|
||||
|
||||
+4
-1
@@ -53,7 +53,10 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
start() ->
|
||||
application:ensure_all_started(ejabberd).
|
||||
case application:ensure_all_started(ejabberd) of
|
||||
{error, Err} -> error_logger:error_msg("Failed to start ejabberd application: ~p", [Err]);
|
||||
Ok -> Ok
|
||||
end.
|
||||
|
||||
stop() ->
|
||||
application:stop(ejabberd).
|
||||
|
||||
@@ -203,6 +203,7 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = update, tags = [server],
|
||||
desc = "Update the given module",
|
||||
longdesc = "To update all the possible modules, use `all`.",
|
||||
note = "improved in 24.10",
|
||||
module = ?MODULE, function = update,
|
||||
args_example = ["all"],
|
||||
args = [{module, string}],
|
||||
@@ -370,17 +371,17 @@ get_commands_spec() ->
|
||||
args = [{out, string}],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [offline, purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
args = [], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_messages, tags = [purge],
|
||||
#ejabberd_commands{name = delete_old_messages, tags = [offline, purge],
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args_desc = ["Number of days"],
|
||||
args_example = [31],
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_messages_batch, tags = [purge],
|
||||
#ejabberd_commands{name = delete_old_messages_batch, tags = [offline, purge],
|
||||
desc = "Delete offline messages older than DAYS",
|
||||
note = "added in 22.05",
|
||||
module = ?MODULE, function = delete_old_messages_batch,
|
||||
@@ -393,7 +394,7 @@ get_commands_spec() ->
|
||||
result = {res, restuple},
|
||||
result_desc = "Result tuple",
|
||||
result_example = {ok, <<"Removal of 5000 messages in progress">>}},
|
||||
#ejabberd_commands{name = delete_old_messages_status, tags = [purge],
|
||||
#ejabberd_commands{name = delete_old_messages_status, tags = [offline, purge],
|
||||
desc = "Status of delete old offline messages operation",
|
||||
note = "added in 22.05",
|
||||
module = ?MODULE, function = delete_old_messages_status,
|
||||
@@ -403,7 +404,7 @@ get_commands_spec() ->
|
||||
result = {status, string},
|
||||
result_desc = "Status test",
|
||||
result_example = "Operation in progress, delete 5000 messages"},
|
||||
#ejabberd_commands{name = abort_delete_old_messages, tags = [purge],
|
||||
#ejabberd_commands{name = abort_delete_old_messages, tags = [offline, purge],
|
||||
desc = "Abort currently running delete old offline messages operation",
|
||||
note = "added in 22.05",
|
||||
module = ?MODULE, function = delete_old_messages_abort,
|
||||
@@ -528,7 +529,7 @@ get_commands_spec() ->
|
||||
module = ejabberd_doc, function = man,
|
||||
args = [], result = {res, restuple}},
|
||||
|
||||
#ejabberd_commands{name = webadmin_host_user_queue, tags = [internal],
|
||||
#ejabberd_commands{name = webadmin_host_user_queue, tags = [offline, internal],
|
||||
desc = "Generate WebAdmin offline queue HTML",
|
||||
module = mod_offline, function = webadmin_host_user_queue,
|
||||
args = [{user, binary}, {host, binary}, {query, any}, {lang, binary}],
|
||||
@@ -746,7 +747,7 @@ update_module(ModuleNameString) ->
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, []} ->
|
||||
{ok, "Not updated: "++ModuleNameString};
|
||||
{ok, [{ModuleName, _}]} ->
|
||||
{ok, [ModuleName]} ->
|
||||
{ok, "Updated: "++ModuleNameString};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
+20
-1
@@ -59,10 +59,14 @@ start(normal, _Args) ->
|
||||
ejabberd_hooks:run(ejabberd_started, []),
|
||||
ejabberd:check_apps(),
|
||||
ejabberd_systemd:ready(),
|
||||
maybe_start_exsync(),
|
||||
{T2, _} = statistics(wall_clock),
|
||||
?INFO_MSG("ejabberd ~ts is started in the node ~p in ~.2fs",
|
||||
[ejabberd_option:version(),
|
||||
node(), (T2-T1)/1000]),
|
||||
maybe_print_elixir_version(),
|
||||
?INFO_MSG("~ts",
|
||||
[erlang:system_info(system_version)]),
|
||||
{ok, SupPid};
|
||||
Err ->
|
||||
?CRITICAL_MSG("Failed to start ejabberd application: ~p", [Err]),
|
||||
@@ -175,7 +179,11 @@ file_queue_init() ->
|
||||
-ifdef(ELIXIR_ENABLED).
|
||||
is_using_elixir_config() ->
|
||||
Config = ejabberd_config:path(),
|
||||
'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config).
|
||||
try 'Elixir.Ejabberd.ConfigUtil':is_elixir_config(Config) of
|
||||
B when is_boolean(B) -> B
|
||||
catch
|
||||
_:_ -> false
|
||||
end.
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case is_using_elixir_config() of
|
||||
@@ -194,8 +202,19 @@ start_elixir_application() ->
|
||||
ok -> ok;
|
||||
{error, _Msg} -> ?ERROR_MSG("Elixir application not started.", [])
|
||||
end.
|
||||
|
||||
maybe_start_exsync() ->
|
||||
case os:getenv("RELIVE") of
|
||||
"true" -> rpc:call(node(), 'Elixir.ExSync.Application', start, []);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
maybe_print_elixir_version() ->
|
||||
?INFO_MSG("Elixir ~ts", [maps:get(build, 'Elixir.System':build_info())]).
|
||||
-else.
|
||||
setup_if_elixir_conf_used() -> ok.
|
||||
register_elixir_config_hooks() -> ok.
|
||||
start_elixir_application() -> ok.
|
||||
maybe_start_exsync() -> ok.
|
||||
maybe_print_elixir_version() -> ok.
|
||||
-endif.
|
||||
|
||||
@@ -293,6 +293,8 @@ try_register(User, Server, Password) ->
|
||||
false ->
|
||||
case ejabberd_router:is_my_host(LServer) of
|
||||
true ->
|
||||
case ejabberd_hooks:run_fold(check_register_user, LServer, true, [User, Server, Password]) of
|
||||
true ->
|
||||
case lists:foldl(
|
||||
fun(_, ok) ->
|
||||
ok;
|
||||
@@ -307,6 +309,9 @@ try_register(User, Server, Password) ->
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
%% We add our default JWT verifier with hook priority 100.
|
||||
%% So if you need to check or verify your custom JWT before the
|
||||
%% default verifier, It's better to use this hook with priority
|
||||
%% little than 100 and return bool() or {stop, bool()} in your own
|
||||
%% So if you need to check or verify your custom JWT before the
|
||||
%% default verifier, It's better to use this hook with priority
|
||||
%% little than 100 and return bool() or {stop, bool()} in your own
|
||||
%% callback function.
|
||||
ejabberd_hooks:add(check_decoded_jwt, Host, ?MODULE, check_decoded_jwt, 100),
|
||||
case ejabberd_option:jwt_key(Host) of
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("ejabberd_auth.hrl").
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -161,8 +159,6 @@ remove_user(User, Server) ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
-define(BATCH_SIZE, 1000).
|
||||
|
||||
scram_hash_encode(Hash, StoreKey) ->
|
||||
case Hash of
|
||||
sha -> StoreKey;
|
||||
|
||||
+14
-14
@@ -308,9 +308,9 @@ init([#body{attrs = Attrs}, IP, SID]) ->
|
||||
ignore
|
||||
end.
|
||||
|
||||
wait_for_session(_Event, State) ->
|
||||
wait_for_session(Event, State) ->
|
||||
?ERROR_MSG("Unexpected event in 'wait_for_session': ~p",
|
||||
[_Event]),
|
||||
[Event]),
|
||||
{next_state, wait_for_session, State}.
|
||||
|
||||
wait_for_session(#body{attrs = Attrs} = Req, From,
|
||||
@@ -367,16 +367,16 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
|
||||
reply_next_state(State4, Resp#body{els = RespEls}, RID,
|
||||
From)
|
||||
end;
|
||||
wait_for_session(_Event, _From, State) ->
|
||||
wait_for_session(Event, _From, State) ->
|
||||
?ERROR_MSG("Unexpected sync event in 'wait_for_session': ~p",
|
||||
[_Event]),
|
||||
[Event]),
|
||||
{reply, {error, badarg}, wait_for_session, State}.
|
||||
|
||||
active({#body{} = Body, From}, State) ->
|
||||
active1(Body, From, State);
|
||||
active(_Event, State) ->
|
||||
active(Event, State) ->
|
||||
?ERROR_MSG("Unexpected event in 'active': ~p",
|
||||
[_Event]),
|
||||
[Event]),
|
||||
{next_state, active, State}.
|
||||
|
||||
active(#body{attrs = Attrs, size = Size} = Req, From,
|
||||
@@ -408,9 +408,9 @@ active(#body{attrs = Attrs, size = Size} = Req, From,
|
||||
end;
|
||||
true -> active1(Req, From, State1)
|
||||
end;
|
||||
active(_Event, _From, State) ->
|
||||
active(Event, _From, State) ->
|
||||
?ERROR_MSG("Unexpected sync event in 'active': ~p",
|
||||
[_Event]),
|
||||
[Event]),
|
||||
{reply, {error, badarg}, active, State}.
|
||||
|
||||
active1(#body{attrs = Attrs} = Req, From, State) ->
|
||||
@@ -517,9 +517,9 @@ handle_event({activate, C2SPid}, StateName,
|
||||
handle_event({change_shaper, Shaper}, StateName,
|
||||
State) ->
|
||||
{next_state, StateName, State#state{shaper_state = Shaper}};
|
||||
handle_event(_Event, StateName, State) ->
|
||||
handle_event(Event, StateName, State) ->
|
||||
?ERROR_MSG("Unexpected event in '~ts': ~p",
|
||||
[StateName, _Event]),
|
||||
[StateName, Event]),
|
||||
{next_state, StateName, State}.
|
||||
|
||||
handle_sync_event({send_xml,
|
||||
@@ -557,9 +557,9 @@ handle_sync_event(deactivate_socket, _From, StateName,
|
||||
StateData) ->
|
||||
{reply, ok, StateName,
|
||||
StateData#state{c2s_pid = undefined}};
|
||||
handle_sync_event(_Event, _From, StateName, State) ->
|
||||
handle_sync_event(Event, _From, StateName, State) ->
|
||||
?ERROR_MSG("Unexpected sync event in '~ts': ~p",
|
||||
[StateName, _Event]),
|
||||
[StateName, Event]),
|
||||
{reply, {error, badarg}, StateName, State}.
|
||||
|
||||
handle_info({timeout, TRef, wait_timeout}, StateName,
|
||||
@@ -583,9 +583,9 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
|
||||
{stop, normal, State};
|
||||
_ -> {next_state, StateName, State}
|
||||
end;
|
||||
handle_info(_Info, StateName, State) ->
|
||||
handle_info(Info, StateName, State) ->
|
||||
?ERROR_MSG("Unexpected info:~n** Msg: ~p~n** StateName: ~p",
|
||||
[_Info, StateName]),
|
||||
[Info, StateName]),
|
||||
{next_state, StateName, State}.
|
||||
|
||||
terminate(_Reason, _StateName, State) ->
|
||||
|
||||
+29
-12
@@ -35,16 +35,17 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([tls_options/1, tls_required/1, tls_enabled/1,
|
||||
compress_methods/1, bind/2, sasl_mechanisms/2,
|
||||
get_password_fun/2, check_password_fun/2, check_password_digest_fun/2,
|
||||
unauthenticated_stream_features/1, authenticated_stream_features/1,
|
||||
handle_stream_start/2, handle_stream_end/2,
|
||||
handle_unauthenticated_packet/2, handle_authenticated_packet/2,
|
||||
handle_auth_success/4, handle_auth_failure/4, handle_send/3,
|
||||
handle_recv/3, handle_cdata/2, handle_unbinded_packet/2,
|
||||
inline_stream_features/1, handle_sasl2_inline/2,
|
||||
handle_sasl2_inline_post/3, handle_bind2_inline/2,
|
||||
handle_bind2_inline_post/3, sasl_options/1]).
|
||||
allow_unencrypted_sasl2/1, compress_methods/1, bind/2,
|
||||
sasl_mechanisms/2, get_password_fun/2, check_password_fun/2,
|
||||
check_password_digest_fun/2, unauthenticated_stream_features/1,
|
||||
authenticated_stream_features/1, handle_stream_start/2,
|
||||
handle_stream_end/2, handle_unauthenticated_packet/2,
|
||||
handle_authenticated_packet/2, handle_auth_success/4,
|
||||
handle_auth_failure/4, handle_send/3, handle_recv/3, handle_cdata/2,
|
||||
handle_unbinded_packet/2, inline_stream_features/1,
|
||||
handle_sasl2_inline/2, handle_sasl2_inline_post/3,
|
||||
handle_bind2_inline/2, handle_bind2_inline_post/3, sasl_options/1,
|
||||
handle_sasl2_task_next/4, handle_sasl2_task_data/3]).
|
||||
%% Hooks
|
||||
-export([handle_unexpected_cast/2, handle_unexpected_call/3,
|
||||
process_auth_result/3, reject_unauthenticated_packet/2,
|
||||
@@ -392,6 +393,9 @@ tls_enabled(#{tls_enabled := TLSEnabled,
|
||||
tls_verify := TLSVerify}) ->
|
||||
TLSEnabled or TLSRequired or TLSVerify.
|
||||
|
||||
allow_unencrypted_sasl2(#{allow_unencrypted_sasl2 := AllowUnencryptedSasl2}) ->
|
||||
AllowUnencryptedSasl2.
|
||||
|
||||
compress_methods(#{zlib := true}) ->
|
||||
[<<"zlib">>];
|
||||
compress_methods(_) ->
|
||||
@@ -404,7 +408,7 @@ authenticated_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
||||
|
||||
inline_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], []}, [LServer]).
|
||||
ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], [], []}, [LServer]).
|
||||
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) ->
|
||||
Type = ejabberd_auth:store_type(LServer),
|
||||
@@ -580,6 +584,14 @@ handle_bind2_inline_post(Els, Results, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_bind2_inline_post, LServer,
|
||||
State, [Els, Results]).
|
||||
|
||||
handle_sasl2_task_next(Task, Els, InlineEls, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_sasl2_task_next, LServer,
|
||||
{abort, State}, [Task, Els, InlineEls]).
|
||||
|
||||
handle_sasl2_task_data(Els, InlineEls, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_sasl2_task_data, LServer,
|
||||
{abort, State}, [Els, InlineEls]).
|
||||
|
||||
handle_recv(El, Pkt, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_recv, LServer, State, [El, Pkt]).
|
||||
|
||||
@@ -604,12 +616,14 @@ init([State, Opts]) ->
|
||||
TLSEnabled = proplists:get_bool(starttls, Opts),
|
||||
TLSRequired = proplists:get_bool(starttls_required, Opts),
|
||||
TLSVerify = proplists:get_bool(tls_verify, Opts),
|
||||
AllowUnencryptedSasl2 = proplists:get_bool(allow_unencrypted_sasl2, Opts),
|
||||
Zlib = proplists:get_bool(zlib, Opts),
|
||||
Timeout = ejabberd_option:negotiation_timeout(),
|
||||
State1 = State#{tls_options => TLSOpts2,
|
||||
tls_required => TLSRequired,
|
||||
tls_enabled => TLSEnabled,
|
||||
tls_verify => TLSVerify,
|
||||
allow_unencrypted_sasl2 => AllowUnencryptedSasl2,
|
||||
pres_a => ?SETS:new(),
|
||||
zlib => Zlib,
|
||||
lang => ejabberd_option:language(),
|
||||
@@ -1002,7 +1016,7 @@ get_conn_type(State) ->
|
||||
websocket -> websocket
|
||||
end.
|
||||
|
||||
-spec fix_from_to(xmpp_element(), state()) -> stanza().
|
||||
-spec fix_from_to(xmpp_element(), state()) -> stanza() | xmpp_element().
|
||||
fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
|
||||
#jid{luser = U, lserver = S, lresource = R} = JID,
|
||||
case xmpp:get_from(Pkt) of
|
||||
@@ -1047,6 +1061,8 @@ listen_opt_type(starttls) ->
|
||||
econf:bool();
|
||||
listen_opt_type(starttls_required) ->
|
||||
econf:bool();
|
||||
listen_opt_type(allow_unencrypted_sasl2) ->
|
||||
econf:bool();
|
||||
listen_opt_type(tls_verify) ->
|
||||
econf:bool();
|
||||
listen_opt_type(zlib) ->
|
||||
@@ -1069,6 +1085,7 @@ listen_options() ->
|
||||
{tls_compression, false},
|
||||
{starttls, false},
|
||||
{starttls_required, false},
|
||||
{allow_unencrypted_sasl2, false},
|
||||
{tls_verify, false},
|
||||
{zlib, false},
|
||||
{max_stanza_size, infinity},
|
||||
|
||||
@@ -616,8 +616,7 @@ return(Port, TRef, Result) ->
|
||||
|
||||
is_feature_available() ->
|
||||
case get_prog_name() of
|
||||
Prog when is_binary(Prog) -> true;
|
||||
MF when is_list(MF) -> true;
|
||||
PathOrModule when is_binary(PathOrModule) -> true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@
|
||||
-include("logger.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(POLICY_ACCESS, '$policy').
|
||||
|
||||
-type auth() :: {binary(), binary(), binary() | {oauth, binary()}, boolean()} | map().
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
+195
-29
@@ -34,6 +34,10 @@
|
||||
delete/3,
|
||||
delete/4,
|
||||
delete/5,
|
||||
subscribe/4,
|
||||
subscribe/5,
|
||||
unsubscribe/4,
|
||||
unsubscribe/5,
|
||||
run/2,
|
||||
run/3,
|
||||
run_fold/3,
|
||||
@@ -59,6 +63,8 @@
|
||||
-include("ejabberd_stacktrace.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
-type subscriber() :: {Module :: atom(), Function :: atom(), InitArg :: any()}.
|
||||
-type subscriber_event() :: before | 'after' | before_callback | after_callback.
|
||||
-type hook() :: {Seq :: integer(), Module :: atom(), Function :: atom() | fun()}.
|
||||
|
||||
-define(TRACE_HOOK_KEY, '$trace_hook').
|
||||
@@ -74,7 +80,7 @@ start_link() ->
|
||||
add(Hook, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, global, undefined, Function, Seq).
|
||||
|
||||
-spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom() , integer()) -> ok.
|
||||
-spec add(atom(), HostOrModule :: binary() | atom(), fun() | atom(), integer()) -> ok.
|
||||
add(Hook, Host, Function, Seq) when is_function(Function) ->
|
||||
add(Hook, Host, undefined, Function, Seq);
|
||||
|
||||
@@ -87,6 +93,34 @@ add(Hook, Module, Function, Seq) ->
|
||||
add(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(?MODULE, {add, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
-spec subscribe(atom(), atom(), atom(), any()) -> ok.
|
||||
%% @doc Add a subscriber to this hook.
|
||||
%%
|
||||
%% Before running any hook callback, the subscriber will be called in form of
|
||||
%% Module:Function(InitArg, 'before', Host :: binary() | global, Hook, HookArgs)
|
||||
%% Above function should return new state.
|
||||
%%
|
||||
%% Before running each callback, the subscriber will be called in form of
|
||||
%% Module:Function(State, 'before_callback', Host :: binary() | global, Hook, {CallbackMod, CallbackArg, Seq, HookArgs})
|
||||
%% Above function should return new state.
|
||||
%%
|
||||
%% After running each callback, the subscriber will be called in form of
|
||||
%% Module:Function(State, 'after_callback', Host :: binary() | global, Hook, {CallbackMod, CallbackArg, Seq, HookArgs})
|
||||
%% Above function should return new state.
|
||||
%%
|
||||
%% After running any hook callback, the subscriber will be called in form of
|
||||
%% Module:Function(State, 'after', Host :: binary() | global, Hook, HookArgs)
|
||||
%% Return value of this function call will be dropped.
|
||||
%%
|
||||
%% For every ejabberd_hooks:[run|run_fold] for every subscriber above functions will be called and the hook runner
|
||||
%% maintains State in above four calls.
|
||||
subscribe(Hook, Module, Function, InitArg) ->
|
||||
subscribe(Hook, global, Module, Function, InitArg).
|
||||
|
||||
-spec subscribe(atom(), binary() | global, atom(), atom(), any()) -> ok.
|
||||
subscribe(Hook, Host, Module, Function, InitArg) ->
|
||||
gen_server:call(?MODULE, {subscribe, Hook, Host, Module, Function, InitArg}).
|
||||
|
||||
-spec delete(atom(), fun(), integer()) -> ok.
|
||||
%% @doc See del/4.
|
||||
delete(Hook, Function, Seq) when is_function(Function) ->
|
||||
@@ -105,8 +139,20 @@ delete(Hook, Module, Function, Seq) ->
|
||||
delete(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(?MODULE, {delete, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
|
||||
|
||||
-spec unsubscribe(atom(), atom(), atom(), any()) -> ok.
|
||||
%% @doc Removes a subscriber from this hook.
|
||||
unsubscribe(Hook, Module, Function, InitArg) ->
|
||||
unsubscribe(Hook, global, Module, Function, InitArg).
|
||||
|
||||
-spec unsubscribe(atom(), binary() | global, atom(), atom(), any()) -> ok.
|
||||
unsubscribe(Hook, Host, Module, Function, InitArg) ->
|
||||
gen_server:call(?MODULE, {unsubscribe, Hook, Host, Module, Function, InitArg}).
|
||||
|
||||
|
||||
-spec run(atom(), list()) -> ok.
|
||||
%% @doc Run the calls of this hook in order, don't care about function results.
|
||||
%% @doc Run the calls (and subscibers) of this hook in order, don't care about function results.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
@@ -114,17 +160,28 @@ run(Hook, Args) ->
|
||||
-spec run(atom(), binary() | global, list()) -> ok.
|
||||
run(Hook, Host, Args) ->
|
||||
try ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
[{_, Ls, Subs}] ->
|
||||
case erlang:get(?TRACE_HOOK_KEY) of
|
||||
undefined ->
|
||||
undefined when Subs == [] ->
|
||||
run1(Ls, Hook, Args);
|
||||
undefined ->
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, Args, before, []),
|
||||
Subs3 = run1(Ls, Hook, Args, Host, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, Args, 'after', []),
|
||||
ok;
|
||||
TracingHooksOpts ->
|
||||
case do_get_tracing_options(Hook, Host, TracingHooksOpts) of
|
||||
undefined ->
|
||||
run1(Ls, Hook, Args);
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, Args, before, []),
|
||||
Subs3 = run1(Ls, Hook, Args, Host, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, Args, 'after', []),
|
||||
ok;
|
||||
TracingOpts ->
|
||||
foreach_start_hook_tracing(TracingOpts, Hook, Host, Args),
|
||||
run2(Ls, Hook, Args, Host, TracingOpts)
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, Args, before, []),
|
||||
Subs3 = run2(Ls, Hook, Args, Host, TracingOpts, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, Args, 'after', []),
|
||||
ok
|
||||
end
|
||||
end;
|
||||
[] ->
|
||||
@@ -134,7 +191,7 @@ run(Hook, Host, Args) ->
|
||||
end.
|
||||
|
||||
-spec run_fold(atom(), T, list()) -> T.
|
||||
%% @doc Run the calls of this hook in order.
|
||||
%% @doc Run the calls (and subscribers) of this hook in order.
|
||||
%% The arguments passed to the function are: [Val | Args].
|
||||
%% The result of a call is used as Val for the next call.
|
||||
%% If a call returns 'stop', no more calls are performed.
|
||||
@@ -145,17 +202,28 @@ run_fold(Hook, Val, Args) ->
|
||||
-spec run_fold(atom(), binary() | global, T, list()) -> T.
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
try ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
[{_, Ls, Subs}] ->
|
||||
case erlang:get(?TRACE_HOOK_KEY) of
|
||||
undefined ->
|
||||
undefined when Subs == [] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
undefined ->
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, [Val | Args], before, []),
|
||||
{Val2, Subs3} = run_fold1(Ls, Hook, Val, Args, Host, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, [Val2 | Args], 'after', []),
|
||||
Val2;
|
||||
TracingHooksOpts ->
|
||||
case do_get_tracing_options(Hook, Host, TracingHooksOpts) of
|
||||
undefined ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, [Val | Args], before, []),
|
||||
{Val2, Subs3} = run_fold1(Ls, Hook, Val, Args, Host, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, [Val2 | Args], 'after', []),
|
||||
Val2;
|
||||
TracingOpts ->
|
||||
fold_start_hook_tracing(TracingOpts, Hook, Host, [Val | Args]),
|
||||
run_fold2(Ls, Hook, Val, Args, Host, TracingOpts)
|
||||
Subs2 = call_subscriber_list(Subs, Host, Hook, [Val | Args], before, []),
|
||||
{Val2, Subs3} = run_fold2(Ls, Hook, Val, Args, Host, TracingOpts, Subs2),
|
||||
_Subs4 = call_subscriber_list(Subs3, Host, Hook, [Val2 | Args], 'after', []),
|
||||
Val2
|
||||
end
|
||||
end;
|
||||
[] ->
|
||||
@@ -230,6 +298,14 @@ handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
HookFormat = {Seq, Module, Function},
|
||||
Reply = handle_delete(Hook, Host, HookFormat),
|
||||
{reply, Reply, State};
|
||||
handle_call({subscribe, Hook, Host, Module, Function, InitArg}, _From, State) ->
|
||||
SubscriberFormat = {Module, Function, InitArg},
|
||||
Reply = handle_subscribe(Hook, Host, SubscriberFormat),
|
||||
{reply, Reply, State};
|
||||
handle_call({unsubscribe, Hook, Host, Module, Function, InitArg}, _From, State) ->
|
||||
SubscriberFormat = {Module, Function, InitArg},
|
||||
Reply = handle_unsubscribe(Hook, Host, SubscriberFormat),
|
||||
{reply, Reply, State};
|
||||
handle_call(Request, From, State) ->
|
||||
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
@@ -237,27 +313,53 @@ handle_call(Request, From, State) ->
|
||||
-spec handle_add(atom(), atom(), hook()) -> ok.
|
||||
handle_add(Hook, Host, El) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
[{_, Ls, Subs}] ->
|
||||
case lists:member(El, Ls) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
NewLs = lists:merge(Ls, [El]),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs, Subs}),
|
||||
ok
|
||||
end;
|
||||
[] ->
|
||||
NewLs = [El],
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs, []}),
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec handle_delete(atom(), atom(), hook()) -> ok.
|
||||
handle_delete(Hook, Host, El) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
[{_, Ls, Subs}] ->
|
||||
NewLs = lists:delete(El, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs, Subs}),
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec handle_subscribe(atom(), atom(), subscriber()) -> ok.
|
||||
handle_subscribe(Hook, Host, El) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls, Subs}] ->
|
||||
case lists:member(El, Subs) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
ets:insert(hooks, {{Hook, Host}, Ls, Subs ++ [El]}),
|
||||
ok
|
||||
end;
|
||||
[] ->
|
||||
ets:insert(hooks, {{Hook, Host}, [], [El]}),
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec handle_unsubscribe(atom(), atom(), subscriber()) -> ok.
|
||||
handle_unsubscribe(Hook, Host, El) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls, Subs}] ->
|
||||
ets:insert(hooks, {{Hook, Host}, Ls, lists:delete(El, Subs)}),
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -310,6 +412,40 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end.
|
||||
|
||||
-spec run1([hook()], atom(), list(), binary() | global, [subscriber()]) -> [subscriber()].
|
||||
run1([], _Hook, _Args, _Host, SubscriberList) ->
|
||||
SubscriberList;
|
||||
run1([{Seq, Module, Function} | Ls], Hook, Args, Host, SubscriberList) ->
|
||||
SubscriberList2 = call_subscriber_list(SubscriberList, Host, Hook, {Module, Function, Seq, Args}, before_callback, []),
|
||||
Res = safe_apply(Hook, Module, Function, Args),
|
||||
SubscriberList3 = call_subscriber_list(SubscriberList2, Host, Hook, {Module, Function, Seq, Args}, after_callback, []),
|
||||
case Res of
|
||||
'EXIT' ->
|
||||
run1(Ls, Hook, Args, Host, SubscriberList3);
|
||||
stop ->
|
||||
SubscriberList3;
|
||||
_ ->
|
||||
run1(Ls, Hook, Args, Host, SubscriberList3)
|
||||
end.
|
||||
|
||||
-spec run_fold1([hook()], atom(), T, list(), binary() | global, [subscriber()]) -> {T, [subscriber()]}.
|
||||
run_fold1([], _Hook, Val, _Args, _Host, SubscriberList) ->
|
||||
{Val, SubscriberList};
|
||||
run_fold1([{Seq, Module, Function} | Ls], Hook, Val, Args, Host, SubscriberList) ->
|
||||
SubscriberList2 = call_subscriber_list(SubscriberList, Host, Hook, {Module, Function, Seq, [Val | Args]}, before_callback, []),
|
||||
Res = safe_apply(Hook, Module, Function, [Val | Args]),
|
||||
SubscriberList3 = call_subscriber_list(SubscriberList2, Host, Hook, {Module, Function, Seq, [Val | Args]}, after_callback, []),
|
||||
case Res of
|
||||
'EXIT' ->
|
||||
run_fold1(Ls, Hook, Val, Args, Host, SubscriberList3);
|
||||
stop ->
|
||||
{Val, SubscriberList3};
|
||||
{stop, NewVal} ->
|
||||
{NewVal, SubscriberList3};
|
||||
NewVal ->
|
||||
run_fold1(Ls, Hook, NewVal, Args, Host, SubscriberList3)
|
||||
end.
|
||||
|
||||
-spec safe_apply(atom(), atom(), atom() | fun(), list()) -> any().
|
||||
safe_apply(Hook, Module, Function, Args) ->
|
||||
?DEBUG("Running hook ~p: ~p:~p/~B",
|
||||
@@ -332,6 +468,32 @@ safe_apply(Hook, Module, Function, Args) ->
|
||||
'EXIT'
|
||||
end.
|
||||
|
||||
-spec call_subscriber_list([subscriber()], binary() | global, atom(), {atom(), atom(), integer(), list()} | list(), subscriber_event(), [subscriber()]) -> any().
|
||||
call_subscriber_list([], _Host, _Hook, _CallbackOrArgs, _Event, []) ->
|
||||
[];
|
||||
call_subscriber_list([], _Host, _Hook, _CallbackOrArgs, _Event, Result) ->
|
||||
lists:reverse(Result);
|
||||
call_subscriber_list([{Mod, Func, InitArg} | SubscriberList], Host, Hook, CallbackOrArgs, Event, Result) ->
|
||||
SubscriberArgs = [InitArg, Event, Host, Hook, CallbackOrArgs],
|
||||
?DEBUG("Running hook subsciber ~p: ~p:~p/~B with event ~p",
|
||||
[Hook, Mod, Func, length(SubscriberArgs), Event]),
|
||||
try apply(Mod, Func, SubscriberArgs) of
|
||||
State ->
|
||||
call_subscriber_list(SubscriberList, Host, Hook, CallbackOrArgs, Event, [{Mod, Func, State} | Result])
|
||||
catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
|
||||
Stack = ?EX_STACK(St),
|
||||
?ERROR_MSG("Hook subscriber ~p crashed when running ~p:~p/~p:~n" ++
|
||||
string:join(
|
||||
["** ~ts"|
|
||||
["** Arg " ++ integer_to_list(I) ++ " = ~p"
|
||||
|| I <- lists:seq(1, length(SubscriberArgs))]],
|
||||
"~n"),
|
||||
[Hook, Mod, Func, length(SubscriberArgs),
|
||||
misc:format_exception(2, E, R, Stack)|SubscriberArgs]),
|
||||
%% Do not append subscriber for next calls:
|
||||
call_subscriber_list(SubscriberList, Host, Hook, CallbackOrArgs, Event, Result)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal tracing functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -453,41 +615,45 @@ do_get_tracing_options(Hook, Host, MaybeMap) ->
|
||||
end
|
||||
end.
|
||||
|
||||
run2([], Hook, Args, Host, Opts) ->
|
||||
run2([], Hook, Args, Host, Opts, SubscriberList) ->
|
||||
foreach_stop_hook_tracing(Opts, Hook, Host, Args, undefined),
|
||||
ok;
|
||||
run2([{Seq, Module, Function} | Ls], Hook, Args, Host, TracingOpts) ->
|
||||
SubscriberList;
|
||||
run2([{Seq, Module, Function} | Ls], Hook, Args, Host, TracingOpts, SubscriberList) ->
|
||||
foreach_start_callback_tracing(TracingOpts, Hook, Host, Module, Function, Args, Seq),
|
||||
SubscriberList2 = call_subscriber_list(SubscriberList, Host, Hook, {Module, Function, Seq, Args}, before_callback, []),
|
||||
Res = safe_apply(Hook, Module, Function, Args),
|
||||
SubscriberList3 = call_subscriber_list(SubscriberList2, Host, Hook, {Module, Function, Seq, Args}, after_callback, []),
|
||||
foreach_stop_callback_tracing(TracingOpts, Hook, Host, Module, Function, Args, Seq, Res),
|
||||
case Res of
|
||||
'EXIT' ->
|
||||
run2(Ls, Hook, Args, Host, TracingOpts);
|
||||
run2(Ls, Hook, Args, Host, TracingOpts, SubscriberList3);
|
||||
stop ->
|
||||
foreach_stop_hook_tracing(TracingOpts, Hook, Host, Args, {Module, Function, Seq, Ls}),
|
||||
ok;
|
||||
SubscriberList3;
|
||||
_ ->
|
||||
run2(Ls, Hook, Args, Host, TracingOpts)
|
||||
run2(Ls, Hook, Args, Host, TracingOpts, SubscriberList3)
|
||||
end.
|
||||
|
||||
run_fold2([], Hook, Val, Args, Host, Opts) ->
|
||||
run_fold2([], Hook, Val, Args, Host, Opts, SubscriberList) ->
|
||||
fold_stop_hook_tracing(Opts, Hook, Host, [Val | Args], undefined),
|
||||
Val;
|
||||
run_fold2([{Seq, Module, Function} | Ls], Hook, Val, Args, Host, TracingOpts) ->
|
||||
{Val, SubscriberList};
|
||||
run_fold2([{Seq, Module, Function} | Ls], Hook, Val, Args, Host, TracingOpts, SubscriberList) ->
|
||||
fold_start_callback_tracing(TracingOpts, Hook, Host, Module, Function, [Val | Args], Seq),
|
||||
SubscriberList2 = call_subscriber_list(SubscriberList, Host, Hook, {Module, Function, Seq, [Val | Args]}, before_callback, []),
|
||||
Res = safe_apply(Hook, Module, Function, [Val | Args]),
|
||||
SubscriberList3 = call_subscriber_list(SubscriberList2, Host, Hook, {Module, Function, Seq, [Val | Args]}, after_callback, []),
|
||||
fold_stop_callback_tracing(TracingOpts, Hook, Host, Module, Function, [Val | Args], Seq, Res),
|
||||
case Res of
|
||||
'EXIT' ->
|
||||
run_fold2(Ls, Hook, Val, Args, Host, TracingOpts);
|
||||
run_fold2(Ls, Hook, Val, Args, Host, TracingOpts, SubscriberList3);
|
||||
stop ->
|
||||
fold_stop_hook_tracing(TracingOpts, Hook, Host, [Val | Args], {Module, Function, Seq, {old, Val}, Ls}),
|
||||
Val;
|
||||
{Val, SubscriberList3};
|
||||
{stop, NewVal} ->
|
||||
fold_stop_hook_tracing(TracingOpts, Hook, Host, [Val | Args], {Module, Function, Seq, {new, NewVal}, Ls}),
|
||||
NewVal;
|
||||
{NewVal, SubscriberList3};
|
||||
NewVal ->
|
||||
run_fold2(Ls, Hook, NewVal, Args, Host, TracingOpts)
|
||||
run_fold2(Ls, Hook, NewVal, Args, Host, TracingOpts, SubscriberList3)
|
||||
end.
|
||||
|
||||
foreach_start_hook_tracing(TracingOpts, Hook, Host, Args) ->
|
||||
|
||||
+50
-100
@@ -50,8 +50,7 @@
|
||||
input = [] :: list(),
|
||||
active = false :: boolean(),
|
||||
c2s_pid :: pid(),
|
||||
ws :: {#ws{}, pid()},
|
||||
rfc_compliant = undefined :: boolean() | undefined}).
|
||||
ws :: {#ws{}, pid()}}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
@@ -166,57 +165,38 @@ handle_event({new_shaper, Shaper}, StateName, #state{ws = {_, WsPid}} = StateDat
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
handle_sync_event({send_xml, Packet}, _From, StateName,
|
||||
#state{ws = {_, WsPid}, rfc_compliant = R} = StateData) ->
|
||||
Packet2 = case {case R of undefined -> true; V -> V end, Packet} of
|
||||
{true, {xmlstreamstart, _, Attrs}} ->
|
||||
Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} |
|
||||
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
|
||||
{xmlstreamelement, #xmlel{name = <<"open">>, attrs = Attrs2}};
|
||||
{true, {xmlstreamend, _}} ->
|
||||
{xmlstreamelement, #xmlel{name = <<"close">>,
|
||||
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}};
|
||||
{true, {xmlstreamraw, <<"\r\n\r\n">>}} -> % cdata ping
|
||||
skip;
|
||||
{true, {xmlstreamelement, #xmlel{name=Name2} = El2}} ->
|
||||
El3 = case Name2 of
|
||||
<<"stream:", _/binary>> ->
|
||||
fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
|
||||
_ ->
|
||||
case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
|
||||
<<"">> ->
|
||||
fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
|
||||
_ ->
|
||||
El2
|
||||
end
|
||||
end,
|
||||
{xmlstreamelement , El3};
|
||||
_ ->
|
||||
Packet
|
||||
end,
|
||||
case Packet2 of
|
||||
{xmlstreamstart, Name, Attrs3} ->
|
||||
B = fxml:element_to_binary(#xmlel{name = Name, attrs = Attrs3}),
|
||||
route_text(WsPid, <<(binary:part(B, 0, byte_size(B)-2))/binary, ">">>);
|
||||
{xmlstreamend, Name} ->
|
||||
route_text(WsPid, <<"</", Name/binary, ">">>);
|
||||
{xmlstreamelement, El} ->
|
||||
route_text(WsPid, fxml:element_to_binary(El));
|
||||
{xmlstreamraw, Bin} ->
|
||||
route_text(WsPid, Bin);
|
||||
{xmlstreamcdata, Bin2} ->
|
||||
route_text(WsPid, Bin2);
|
||||
skip ->
|
||||
ok
|
||||
end,
|
||||
SN2 = case Packet2 of
|
||||
{xmlstreamelement, #xmlel{name = <<"close">>}} ->
|
||||
stream_end_sent;
|
||||
_ ->
|
||||
StateName
|
||||
end,
|
||||
#state{ws = {_, WsPid}} = StateData) ->
|
||||
SN2 = case Packet of
|
||||
{xmlstreamstart, _, Attrs} ->
|
||||
Attrs2 = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>} |
|
||||
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
|
||||
route_el(WsPid, #xmlel{name = <<"open">>, attrs = Attrs2}),
|
||||
StateName;
|
||||
{xmlstreamend, _} ->
|
||||
route_el(WsPid, #xmlel{name = <<"close">>,
|
||||
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]}),
|
||||
stream_end_sent;
|
||||
{xmlstreamraw, <<"\r\n\r\n">>} ->
|
||||
% cdata ping
|
||||
StateName;
|
||||
{xmlstreamelement, #xmlel{name = Name2} = El2} ->
|
||||
El3 = case Name2 of
|
||||
<<"stream:", _/binary>> ->
|
||||
fxml:replace_tag_attr(<<"xmlns:stream">>, ?NS_STREAM, El2);
|
||||
_ ->
|
||||
case fxml:get_tag_attr_s(<<"xmlns">>, El2) of
|
||||
<<"">> ->
|
||||
fxml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, El2);
|
||||
_ ->
|
||||
El2
|
||||
end
|
||||
end,
|
||||
route_el(WsPid, El3),
|
||||
StateName
|
||||
end,
|
||||
{reply, ok, SN2, StateData};
|
||||
handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}, rfc_compliant = true} = StateData)
|
||||
when StateName /= stream_end_sent ->
|
||||
handle_sync_event(close, _From, StateName, #state{ws = {_, WsPid}} = StateData)
|
||||
when StateName /= stream_end_sent ->
|
||||
Close = #xmlel{name = <<"close">>,
|
||||
attrs = [{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-framing">>}]},
|
||||
route_text(WsPid, fxml:element_to_binary(Close)),
|
||||
@@ -230,7 +210,7 @@ handle_info({received, Packet}, StateName, StateDataI) ->
|
||||
{StateData, Parsed} = parse(StateDataI, Packet),
|
||||
SD = case StateData#state.active of
|
||||
false ->
|
||||
Input = StateData#state.input ++ if is_binary(Parsed) -> [Parsed]; true -> Parsed end,
|
||||
Input = StateData#state.input ++ Parsed,
|
||||
StateData#state{input = Input};
|
||||
true ->
|
||||
StateData#state.c2s_pid ! {tcp, StateData#state.socket, Parsed},
|
||||
@@ -313,53 +293,19 @@ get_human_html_xmlel() ->
|
||||
"client that supports it.">>}]}]}]}.
|
||||
|
||||
|
||||
parse(#state{rfc_compliant = C} = State, Data) ->
|
||||
case C of
|
||||
undefined ->
|
||||
P = fxml_stream:new(self()),
|
||||
P2 = fxml_stream:parse(P, Data),
|
||||
fxml_stream:close(P2),
|
||||
case parsed_items([]) of
|
||||
error ->
|
||||
{State#state{rfc_compliant = true}, <<"parse error">>};
|
||||
[] ->
|
||||
{State#state{rfc_compliant = true}, <<"parse error">>};
|
||||
[{xmlstreamstart, <<"open">>, _} | _] ->
|
||||
parse(State#state{rfc_compliant = true}, Data);
|
||||
_ ->
|
||||
parse(State#state{rfc_compliant = false}, Data)
|
||||
end;
|
||||
true ->
|
||||
El = fxml_stream:parse_element(Data),
|
||||
case El of
|
||||
#xmlel{name = <<"open">>, attrs = Attrs} ->
|
||||
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
|
||||
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
|
||||
{State, [{xmlstreamstart, <<"stream:stream">>, Attrs2}]};
|
||||
#xmlel{name = <<"close">>} ->
|
||||
{State, [{xmlstreamend, <<"stream:stream">>}]};
|
||||
{error, _} ->
|
||||
{State, <<"parse error">>};
|
||||
_ ->
|
||||
{State, [El]}
|
||||
end;
|
||||
false ->
|
||||
{State, Data}
|
||||
end.
|
||||
|
||||
parsed_items(List) ->
|
||||
receive
|
||||
{'$gen_event', El}
|
||||
when element(1, El) == xmlel;
|
||||
element(1, El) == xmlstreamstart;
|
||||
element(1, El) == xmlstreamelement;
|
||||
element(1, El) == xmlstreamcdata;
|
||||
element(1, El) == xmlstreamend ->
|
||||
parsed_items([El | List]);
|
||||
{'$gen_event', {xmlstreamerror, _}} ->
|
||||
error
|
||||
after 0 ->
|
||||
lists:reverse(List)
|
||||
parse(State, Data) ->
|
||||
El = fxml_stream:parse_element(Data),
|
||||
case El of
|
||||
#xmlel{name = <<"open">>, attrs = Attrs} ->
|
||||
Attrs2 = [{<<"xmlns:stream">>, ?NS_STREAM}, {<<"xmlns">>, <<"jabber:client">>} |
|
||||
lists:keydelete(<<"xmlns">>, 1, lists:keydelete(<<"xmlns:stream">>, 1, Attrs))],
|
||||
{State, [{xmlstreamstart, <<"stream:stream">>, Attrs2}]};
|
||||
#xmlel{name = <<"close">>} ->
|
||||
{State, [{xmlstreamend, <<"stream:stream">>}]};
|
||||
{error, _} ->
|
||||
{State, [{xmlstreamerror, {4, <<"not well-formed">>}}]};
|
||||
_ ->
|
||||
{State, [El]}
|
||||
end.
|
||||
|
||||
-spec route_text(pid(), binary()) -> ok.
|
||||
@@ -369,3 +315,7 @@ route_text(Pid, Data) ->
|
||||
{text_reply, Pid} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec route_el(pid(), xmlel() | cdata()) -> ok.
|
||||
route_el(Pid, Data) ->
|
||||
route_text(Pid, fxml:element_to_binary(Data)).
|
||||
|
||||
@@ -60,8 +60,6 @@
|
||||
|
||||
-optional_callbacks([listen_opt_type/1, tcp_init/2, udp_init/2]).
|
||||
|
||||
-define(TCP_SEND_TIMEOUT, 15000).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
|
||||
+11
-2
@@ -283,7 +283,7 @@ start(Level) ->
|
||||
burst_limit_window_time => LogBurstLimitWindowTime,
|
||||
burst_limit_max_count => LogBurstLimitCount},
|
||||
FmtConfig = #{legacy_header => false,
|
||||
time_designator => $ ,
|
||||
time_designator => $\s,
|
||||
max_size => 100*1024,
|
||||
single_line => false},
|
||||
FileFmtConfig = FmtConfig#{template => file_template()},
|
||||
@@ -370,7 +370,16 @@ console_template() ->
|
||||
andalso
|
||||
'Elixir.System':version() >= <<"1.15">> of
|
||||
true ->
|
||||
[date, " ", time, " [", level, "] ", message, "\n"];
|
||||
{ok, DC} = logger:get_handler_config(default),
|
||||
MessageFormat = case maps:get(formatter, DC) of
|
||||
%% https://hexdocs.pm/logger/1.17.2/Logger.Formatter.html#module-formatting
|
||||
{'Elixir.Logger.Formatter', _} ->
|
||||
message;
|
||||
%% https://www.erlang.org/doc/apps/kernel/logger_formatter#t:template/0
|
||||
{logger_formatter, _} ->
|
||||
msg
|
||||
end,
|
||||
[date, " ", time, " [", level, "] ", MessageFormat, "\n"];
|
||||
false ->
|
||||
[time, " [", level, "] " | msg()]
|
||||
end.
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
|
||||
-define(NEED_RESET, [local_content, type]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -619,7 +619,7 @@ strings_to_binary(L) when is_list(L) ->
|
||||
end;
|
||||
strings_to_binary({A, B, C, D}) when
|
||||
is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
|
||||
{A, B, C ,D};
|
||||
{A, B, C, D};
|
||||
strings_to_binary(T) when is_tuple(T) ->
|
||||
list_to_tuple(strings_to_binary1(tuple_to_list(T)));
|
||||
strings_to_binary(X) ->
|
||||
|
||||
@@ -164,6 +164,7 @@
|
||||
-export([sql_username/0, sql_username/1]).
|
||||
-export([trusted_proxies/0]).
|
||||
-export([update_sql_schema/0]).
|
||||
-export([update_sql_schema_timeout/0, update_sql_schema_timeout/1]).
|
||||
-export([use_cache/0, use_cache/1]).
|
||||
-export([validate_stream/0]).
|
||||
-export([version/0]).
|
||||
@@ -329,7 +330,7 @@ cache_size() ->
|
||||
cache_size(Host) ->
|
||||
ejabberd_config:get_option({cache_size, Host}).
|
||||
|
||||
-spec captcha_cmd() -> any().
|
||||
-spec captcha_cmd() -> 'undefined' | binary().
|
||||
captcha_cmd() ->
|
||||
ejabberd_config:get_option({captcha_cmd, global}).
|
||||
|
||||
@@ -1109,6 +1110,13 @@ trusted_proxies() ->
|
||||
update_sql_schema() ->
|
||||
ejabberd_config:get_option({update_sql_schema, global}).
|
||||
|
||||
-spec update_sql_schema_timeout() -> 'infinity' | pos_integer().
|
||||
update_sql_schema_timeout() ->
|
||||
update_sql_schema_timeout(global).
|
||||
-spec update_sql_schema_timeout(global | binary()) -> 'infinity' | pos_integer().
|
||||
update_sql_schema_timeout(Host) ->
|
||||
ejabberd_config:get_option({update_sql_schema_timeout, Host}).
|
||||
|
||||
-spec use_cache() -> boolean().
|
||||
use_cache() ->
|
||||
use_cache(global).
|
||||
|
||||
@@ -117,7 +117,7 @@ opt_type(captcha_cmd) ->
|
||||
fun(V) ->
|
||||
V2 = misc:expand_keyword(<<"@SEMVER@">>, V,
|
||||
ejabberd_option:version()),
|
||||
misc:expand_keyword(<<"@VERSION">>, V2,
|
||||
misc:expand_keyword(<<"@VERSION@">>, V2,
|
||||
misc:semver_to_xxyy(ejabberd_option:version()))
|
||||
end);
|
||||
opt_type(captcha_host) ->
|
||||
@@ -264,6 +264,8 @@ opt_type(new_sql_schema) ->
|
||||
econf:bool();
|
||||
opt_type(update_sql_schema) ->
|
||||
econf:bool();
|
||||
opt_type(update_sql_schema_timeout) ->
|
||||
econf:timeout(second, infinity);
|
||||
opt_type(oauth_access) ->
|
||||
econf:acl();
|
||||
opt_type(oauth_cache_life_time) ->
|
||||
@@ -489,6 +491,7 @@ opt_type(jwt_auth_only_rule) ->
|
||||
{c2s_protocol_options, undefined | binary()} |
|
||||
{s2s_ciphers, undefined | binary()} |
|
||||
{c2s_ciphers, undefined | binary()} |
|
||||
{captcha_cmd, undefined | binary()} |
|
||||
{websocket_origin, [binary()]} |
|
||||
{disable_sasl_mechanisms, [binary()]} |
|
||||
{s2s_zlib, boolean()} |
|
||||
@@ -613,6 +616,7 @@ options() ->
|
||||
{net_ticktime, timer:seconds(60)},
|
||||
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
|
||||
{update_sql_schema, true},
|
||||
{update_sql_schema_timeout, timer:minutes(5)},
|
||||
{oauth_access, none},
|
||||
{oauth_cache_life_time,
|
||||
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
|
||||
|
||||
@@ -30,8 +30,9 @@ doc() ->
|
||||
[{hosts,
|
||||
#{value => ?T("[Domain1, Domain2, ...]"),
|
||||
desc =>
|
||||
?T("The option defines a list containing one or more "
|
||||
"domains that 'ejabberd' will serve. This is a "
|
||||
?T("List of one or more "
|
||||
"_`../configuration/basic.md#host-names|host names`_ "
|
||||
"(or domains) that 'ejabberd' will serve. This is a "
|
||||
"**mandatory** option.")}},
|
||||
{listen,
|
||||
#{value => "[Options, ...]",
|
||||
@@ -42,15 +43,15 @@ doc() ->
|
||||
{modules,
|
||||
#{value => "{Module: Options}",
|
||||
desc =>
|
||||
?T("The option for modules configuration. See "
|
||||
"_`modules.md|Modules`_ section "
|
||||
"for details.")}},
|
||||
?T("Set all the "
|
||||
"_`modules.md|modules`_ configuration options.")}},
|
||||
{loglevel,
|
||||
#{value =>
|
||||
"none | emergency | alert | critical | "
|
||||
"error | warning | notice | info | debug",
|
||||
desc =>
|
||||
?T("Verbosity of log files generated by ejabberd. "
|
||||
?T("Verbosity of ejabberd "
|
||||
"_`../configuration/basic.md#logging|logging`_. "
|
||||
"The default value is 'info'. "
|
||||
"NOTE: previous versions of ejabberd had log levels "
|
||||
"defined in numeric format ('0..5'). The numeric values "
|
||||
@@ -106,7 +107,8 @@ doc() ->
|
||||
{default_db,
|
||||
#{value => "mnesia | sql",
|
||||
desc =>
|
||||
?T("Default persistent storage for ejabberd. "
|
||||
?T("_`database.md#default-database|Default database`_ "
|
||||
"to store persistent data in ejabberd. "
|
||||
"Modules and other components (e.g. authentication) "
|
||||
"may have its own value. The default value is 'mnesia'.")}},
|
||||
{default_ram_db,
|
||||
@@ -135,7 +137,9 @@ doc() ->
|
||||
{acl,
|
||||
#{value => "{ACLName: {ACLType: ACLValue}}",
|
||||
desc =>
|
||||
?T("The option defines access control lists: named sets "
|
||||
?T("This option defines "
|
||||
"_`../configuration/basic.md#acl|access control lists`_: "
|
||||
"named sets "
|
||||
"of rules which are used to match against different targets "
|
||||
"(such as a JID or an IP address). Every set of rules "
|
||||
"has name 'ACLName': it can be any string except 'all' or 'none' "
|
||||
@@ -317,7 +321,9 @@ doc() ->
|
||||
{anonymous_protocol,
|
||||
#{value => "login_anon | sasl_anon | both",
|
||||
desc =>
|
||||
[?T("Define what anonymous protocol will be used: "), "",
|
||||
[?T("Define what "
|
||||
"_`authentication.md#anonymous-login-and-sasl-anonymous|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 "
|
||||
@@ -332,10 +338,8 @@ doc() ->
|
||||
{append_host_config,
|
||||
#{value => "{Host: Options}",
|
||||
desc =>
|
||||
?T("To define specific ejabberd modules in a virtual host, "
|
||||
"you can define the global 'modules' option with the common modules, "
|
||||
"and later add specific modules to certain virtual hosts. "
|
||||
"To accomplish that, 'append_host_config' option can be used.")}},
|
||||
?T("Add a few specific options to a certain "
|
||||
"_`../configuration/basic.md#virtual-hosting|virtual host`_.")}},
|
||||
{auth_cache_life_time,
|
||||
#{value => "timeout()",
|
||||
desc =>
|
||||
@@ -354,7 +358,7 @@ doc() ->
|
||||
{auth_method,
|
||||
#{value => "[mnesia | sql | anonymous | external | jwt | ldap | pam, ...]",
|
||||
desc =>
|
||||
?T("A list of authentication methods to use. "
|
||||
?T("A list of _`authentication.md|authentication`_ methods to use. "
|
||||
"If several methods are defined, authentication is "
|
||||
"considered successful as long as authentication of "
|
||||
"at least one of the methods succeeds. "
|
||||
@@ -533,7 +537,9 @@ doc() ->
|
||||
{define_macro,
|
||||
#{value => "{MacroName: MacroValue}",
|
||||
desc =>
|
||||
?T("Defines a macro. The value can be any valid arbitrary "
|
||||
?T("Defines a "
|
||||
"_`../configuration/file-format.md#macros-in-configuration-file|macro`_. "
|
||||
"The value can be any valid arbitrary "
|
||||
"YAML value. For convenience, it's recommended to define "
|
||||
"a 'MacroName' in capital letters. Duplicated macros are not allowed. "
|
||||
"Macros are processed after additional configuration files have "
|
||||
@@ -572,7 +578,9 @@ doc() ->
|
||||
{domain_balancing,
|
||||
#{value => "{Domain: Options}",
|
||||
desc =>
|
||||
?T("An algorithm to load balance the components that are plugged "
|
||||
?T("An algorithm to "
|
||||
"_`../guide/clustering.md#service-load-balancing|load-balance`_ "
|
||||
"the components that are plugged "
|
||||
"on an ejabberd cluster. It means that you can plug one or several "
|
||||
"instances of the same component on each ejabberd node and that "
|
||||
"the traffic will be automatically distributed. The algorithm "
|
||||
@@ -605,19 +613,23 @@ doc() ->
|
||||
{extauth_pool_name,
|
||||
#{value => ?T("Name"),
|
||||
desc =>
|
||||
?T("Define the pool name appendix, so the full pool name will be "
|
||||
?T("Define the pool name appendix in "
|
||||
"_`authentication.md#external-script|external auth`_, "
|
||||
"so the full pool name will be "
|
||||
"'extauth_pool_Name'. The default value is the hostname.")}},
|
||||
{extauth_pool_size,
|
||||
#{value => ?T("Size"),
|
||||
desc =>
|
||||
?T("The option defines the number of instances of the same "
|
||||
"external program to start for better load balancing. "
|
||||
"_`authentication.md#external-script|external auth`_ "
|
||||
"program to start for better load balancing. "
|
||||
"The default is the number of available CPU cores.")}},
|
||||
{extauth_program,
|
||||
#{value => ?T("Path"),
|
||||
desc =>
|
||||
?T("Indicate in this option the full path to the external "
|
||||
"authentication script. The script must be executable by ejabberd.")}},
|
||||
?T("Indicate in this option the full path to the "
|
||||
"_`authentication.md#external-script|external authentication script`_. "
|
||||
"The script must be executable by ejabberd.")}},
|
||||
{ext_api_headers,
|
||||
#{value => "Headers",
|
||||
desc =>
|
||||
@@ -655,8 +667,10 @@ doc() ->
|
||||
{host_config,
|
||||
#{value => "{Host: Options}",
|
||||
desc =>
|
||||
?T("The option is used to redefine 'Options' for virtual host "
|
||||
"'Host'. In the example below LDAP authentication method "
|
||||
?T("The option is used to redefine 'Options' for "
|
||||
"_`../configuration/basic.md#virtual-hosting|virtual host`_ "
|
||||
"'Host'. "
|
||||
"In the example below LDAP authentication method "
|
||||
"will be used on virtual host 'domain.tld' and SQL method "
|
||||
"will be used on virtual host 'example.org'."),
|
||||
example =>
|
||||
@@ -674,7 +688,9 @@ doc() ->
|
||||
{include_config_file,
|
||||
#{value => "[Filename, ...\\] | {Filename: Options}",
|
||||
desc =>
|
||||
?T("Read additional configuration from 'Filename'. If the "
|
||||
?T("Read and "
|
||||
"_`../configuration/file-format.md#include-additional-files|include additional file`_ "
|
||||
"from 'Filename'. If the "
|
||||
"value is provided in 'pass:[{Filename: Options}]' format, the "
|
||||
"'Options' must be one of the following:")},
|
||||
[{disallow,
|
||||
@@ -701,7 +717,8 @@ doc() ->
|
||||
{jwt_auth_only_rule,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
?T("This ACL rule defines accounts that can use only this auth "
|
||||
?T("This ACL rule defines accounts that can use only the "
|
||||
"_`authentication.md#jwt-authentication|JWT`_ auth "
|
||||
"method, even if others are also defined in the ejabberd "
|
||||
"configuration file. In other words: if there are several auth "
|
||||
"methods enabled for this host (JWT, SQL, ...), users that "
|
||||
@@ -711,36 +728,44 @@ doc() ->
|
||||
#{value => ?T("FieldName"),
|
||||
desc =>
|
||||
?T("By default, the JID is defined in the '\"jid\"' JWT field. "
|
||||
"In this option you can specify other JWT field name "
|
||||
"In this option you can specify other "
|
||||
"_`authentication.md#jwt-authentication|JWT`_ "
|
||||
"field name "
|
||||
"where the JID is defined.")}},
|
||||
{jwt_key,
|
||||
#{value => ?T("FilePath"),
|
||||
desc =>
|
||||
?T("Path to the file that contains the JWK Key. "
|
||||
?T("Path to the file that contains the "
|
||||
"_`authentication.md#jwt-authentication|JWT`_ key. "
|
||||
"The default value is 'undefined'.")}},
|
||||
{language,
|
||||
#{value => ?T("Language"),
|
||||
desc =>
|
||||
?T("The option defines the default language of server strings "
|
||||
?T("Define the "
|
||||
"_`../configuration/basic.md#default-language|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. "
|
||||
"The default value is '\"en\"'.")}},
|
||||
"The default value is '\"en\"'. ")}},
|
||||
{ldap_servers,
|
||||
#{value => "[Host, ...]",
|
||||
desc =>
|
||||
?T("A list of IP addresses or DNS names of your LDAP servers. "
|
||||
?T("A list of IP addresses or DNS names of your LDAP servers (see "
|
||||
"_`../configuration/ldap.md#ldap-connection|LDAP connection`_). "
|
||||
"ejabberd connects immediately to all of them, "
|
||||
"and reconnects infinitely if connection is lost. "
|
||||
"The default value is '[localhost]'.")}},
|
||||
{ldap_backups,
|
||||
#{value => "[Host, ...]",
|
||||
desc =>
|
||||
?T("A list of IP addresses or DNS names of LDAP backup servers. "
|
||||
?T("A list of IP addresses or DNS names of LDAP backup servers (see "
|
||||
"_`../configuration/ldap.md#ldap-connection|LDAP connection`_). "
|
||||
"When no servers listed in _`ldap_servers`_ option are reachable, "
|
||||
"ejabberd will try to connect to these backup servers. "
|
||||
"ejabberd connects 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 "
|
||||
"servers when they become operational again, so the only way "
|
||||
"to restore these connections is to restart ejabberd. This "
|
||||
"limitation might be fixed in future releases.")}},
|
||||
"Please notice that ejabberd only connects to the next server "
|
||||
"when the existing connection is lost; it doesn't detect when a "
|
||||
"previously-attempted server becomes available again.")}},
|
||||
{ldap_encrypt,
|
||||
#{value => "tls | none",
|
||||
desc =>
|
||||
@@ -909,7 +934,9 @@ doc() ->
|
||||
{new_sql_schema,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
{?T("Whether to use 'new' SQL schema. All schemas are located "
|
||||
{?T("Whether to use the "
|
||||
"_`database.md#default-and-new-schemas|new SQL schema`_. "
|
||||
"All schemas are located "
|
||||
"at <https://github.com/processone/ejabberd/tree/~s/sql>. "
|
||||
"There are two schemas available. The default legacy schema "
|
||||
"stores one XMPP domain into one ejabberd database. "
|
||||
@@ -925,10 +952,17 @@ doc() ->
|
||||
#{value => "true | false",
|
||||
note => "updated in 24.06",
|
||||
desc =>
|
||||
?T("Allow ejabberd to update SQL schema. "
|
||||
?T("Allow ejabberd to update SQL schema in "
|
||||
"MySQL, PostgreSQL and SQLite databases. "
|
||||
"This option was added in ejabberd 23.10, "
|
||||
"and enabled by default since 24.06. "
|
||||
"The default value is 'true'.")}},
|
||||
{update_sql_schema_timeout,
|
||||
#{value => "timeout()",
|
||||
note => "added in 24.07",
|
||||
desc =>
|
||||
?T("Time allocated to SQL schema update queries. "
|
||||
"The default value is set to 5 minutes.")}},
|
||||
{oauth_access,
|
||||
#{value => ?T("AccessName"),
|
||||
desc => ?T("By default creating OAuth tokens is not allowed. "
|
||||
@@ -1043,14 +1077,18 @@ doc() ->
|
||||
{pam_service,
|
||||
#{value => ?T("Name"),
|
||||
desc =>
|
||||
?T("This option defines the PAM service name. Refer to the PAM "
|
||||
?T("This option defines the "
|
||||
"_`authentication.md#pam-authentication|PAM`_ "
|
||||
"service name. Refer to the PAM "
|
||||
"documentation of your operation system for more information. "
|
||||
"The default value is 'ejabberd'.")}},
|
||||
{pam_userinfotype,
|
||||
#{value => "username | jid",
|
||||
desc =>
|
||||
?T("This option defines what type of information about the "
|
||||
"user ejabberd provides to the PAM service: only the username, "
|
||||
"user ejabberd provides to the "
|
||||
"_`authentication.md#pam-authentication|PAM`_ "
|
||||
"service: only the username, "
|
||||
"or the user's JID. Default is 'username'.")}},
|
||||
{pgsql_users_number_estimate,
|
||||
#{value => "true | false",
|
||||
@@ -1067,36 +1105,42 @@ doc() ->
|
||||
#{value => "timeout()",
|
||||
desc =>
|
||||
?T("A timeout to wait for the connection to be re-established "
|
||||
"to the Redis server. The default is '1 second'.")}},
|
||||
"to the _`database.md#redis|Redis`_ "
|
||||
"server. The default is '1 second'.")}},
|
||||
{redis_db,
|
||||
#{value => ?T("Number"),
|
||||
desc => ?T("Redis database number. The default is '0'.")}},
|
||||
desc => ?T("_`database.md#redis|Redis`_ "
|
||||
"database number. The default is '0'.")}},
|
||||
{redis_password,
|
||||
#{value => ?T("Password"),
|
||||
desc =>
|
||||
?T("The password to the Redis server. "
|
||||
?T("The password to the _`database.md#redis|Redis`_ server. "
|
||||
"The default is an empty string, i.e. no password.")}},
|
||||
{redis_pool_size,
|
||||
#{value => ?T("Number"),
|
||||
desc =>
|
||||
?T("The number of simultaneous connections to the Redis server. "
|
||||
?T("The number of simultaneous connections to the "
|
||||
"_`database.md#redis|Redis`_ server. "
|
||||
"The default value is '10'.")}},
|
||||
{redis_port,
|
||||
#{value => "1..65535",
|
||||
desc =>
|
||||
?T("The port where the Redis server is accepting connections. "
|
||||
?T("The port where the _`database.md#redis|Redis`_ "
|
||||
" server is accepting connections. "
|
||||
"The default is '6379'.")}},
|
||||
{redis_queue_type,
|
||||
#{value => "ram | file",
|
||||
desc =>
|
||||
?T("The type of request queue for the Redis server. "
|
||||
?T("The type of request queue for the "
|
||||
"_`database.md#redis|Redis`_ server. "
|
||||
"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"),
|
||||
desc =>
|
||||
?T("A hostname or an IP address of the Redis server. "
|
||||
?T("A hostname or an IP address of the "
|
||||
"_`database.md#redis|Redis`_ server."
|
||||
"The default is 'localhost'.")}},
|
||||
{registration_timeout,
|
||||
#{value => "timeout()",
|
||||
@@ -1251,7 +1295,9 @@ doc() ->
|
||||
{shaper,
|
||||
#{value => "{ShaperName: Rate}",
|
||||
desc =>
|
||||
?T("The option defines a set of shapers. Every shaper is assigned "
|
||||
?T("The option defines a set of "
|
||||
"_`../configuration/basic.md#shapers|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 "
|
||||
"itself is defined by its 'Rate', where 'Rate' stands for the "
|
||||
@@ -1268,7 +1314,9 @@ doc() ->
|
||||
{shaper_rules,
|
||||
#{value => "{ShaperRuleName: {Number|ShaperName: ACLRule|ACLName}}",
|
||||
desc =>
|
||||
?T("An entry allowing to declaring shaper to use for matching user/hosts. "
|
||||
?T("This option defines "
|
||||
"_`../configuration/basic.md#shaper-rules|shaper rules`_ "
|
||||
"to use for matching user/hosts. "
|
||||
"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."),
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(PROCNAME, 'ejabberd_redis_client').
|
||||
-define(TR_STACK, redis_transaction_stack).
|
||||
-define(DEFAULT_MAX_QUEUE, 10000).
|
||||
-define(MAX_RETRIES, 1).
|
||||
|
||||
+19
-2
@@ -380,8 +380,9 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
-spec do_route(stanza()) -> ok.
|
||||
do_route(OrigPacket) ->
|
||||
?DEBUG("Route:~n~ts", [xmpp:pp(OrigPacket)]),
|
||||
do_route(OrigPacket1) ->
|
||||
?DEBUG("Route:~n~ts", [xmpp:pp(OrigPacket1)]),
|
||||
OrigPacket = process_privilege_iq(OrigPacket1),
|
||||
case ejabberd_hooks:run_fold(filter_packet, OrigPacket, []) of
|
||||
drop ->
|
||||
ok;
|
||||
@@ -405,6 +406,22 @@ do_route(OrigPacket) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% @format-begin
|
||||
process_privilege_iq(Packet) ->
|
||||
To = xmpp:get_to(Packet),
|
||||
case xmpp:get_meta(Packet, privilege_iq, none) of
|
||||
{OriginalId, OriginalHost, ReplacedJid} when ReplacedJid == To ->
|
||||
Privilege = #privilege{forwarded = #forwarded{sub_els = [Packet]}},
|
||||
#iq{type = xmpp:get_type(Packet),
|
||||
id = OriginalId,
|
||||
to = jid:make(OriginalHost),
|
||||
from = ReplacedJid,
|
||||
sub_els = [Privilege]};
|
||||
_ ->
|
||||
Packet
|
||||
end.
|
||||
%% @format-end
|
||||
|
||||
-spec do_route(stanza(), #route{}) -> any().
|
||||
do_route(Pkt, #route{local_hint = LocalHint,
|
||||
pid = Pid}) when is_pid(Pid) ->
|
||||
|
||||
+21
-1
@@ -43,7 +43,7 @@
|
||||
external_host_overloaded/1, is_temporarly_blocked/1,
|
||||
get_commands_spec/0, zlib_enabled/1, get_idle_timeout/1,
|
||||
tls_required/1, tls_enabled/1, tls_options/3,
|
||||
host_up/1, host_down/1, queue_type/1]).
|
||||
host_up/1, host_down/1, queue_type/1, register_connection/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -130,6 +130,10 @@ get_connections_pids(FromTo) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec register_connection(FromTo :: {binary(), binary()}) -> ok.
|
||||
register_connection(FromTo) ->
|
||||
gen_server:call(ejabberd_s2s, {register_connection, FromTo, self()}).
|
||||
|
||||
-spec dirty_get_connections() -> [{binary(), binary()}].
|
||||
dirty_get_connections() ->
|
||||
mnesia:dirty_all_keys(s2s).
|
||||
@@ -228,6 +232,8 @@ init([]) ->
|
||||
|
||||
handle_call({new_connection, Args}, _From, State) ->
|
||||
{reply, erlang:apply(fun new_connection_int/7, Args), State};
|
||||
handle_call({register_connection, FromTo, Pid}, _From, State) ->
|
||||
{reply, register_connection_int(FromTo, Pid), State};
|
||||
handle_call(Request, From, State) ->
|
||||
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
@@ -478,6 +484,20 @@ new_connection_int(MyServer, Server, From, FromTo,
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec register_connection_int(FromTo :: {binary(), binary()}, Pid :: pid()) -> ok.
|
||||
register_connection_int(FromTo, Pid) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#s2s{fromto = FromTo, pid = Pid})
|
||||
end,
|
||||
TRes = mnesia:transaction(F),
|
||||
case TRes of
|
||||
{atomic, _} ->
|
||||
erlang:monitor(process, Pid),
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec max_s2s_connections_number({binary(), binary()}) -> pos_integer().
|
||||
max_s2s_connections_number({From, To}) ->
|
||||
case ejabberd_shaper:match(From, max_s2s_connections, jid:make(To)) of
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
terminate/2, code_change/3]).
|
||||
%% Hooks
|
||||
-export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
|
||||
handle_unexpected_cast/2, process_downgraded/2]).
|
||||
handle_unexpected_cast/2, process_downgraded/2, handle_unauthenticated_features/2]).
|
||||
%% API
|
||||
-export([start/3, start_link/3, connect/1, close/1, close/2, stop_async/1, send/2,
|
||||
route/2, establish/1, update_state/2, host_up/1, host_down/1]).
|
||||
@@ -216,6 +216,9 @@ dns_retries(#{server_host := ServerHost}) ->
|
||||
dns_timeout(#{server_host := ServerHost}) ->
|
||||
ejabberd_option:s2s_dns_timeout(ServerHost).
|
||||
|
||||
handle_unauthenticated_features(Features, #{server_host := ServerHost} = State) ->
|
||||
ejabberd_hooks:run_fold(s2s_out_unauthenticated_features, ServerHost, State, [Features]).
|
||||
|
||||
handle_auth_success(Mech, #{socket := Socket, ip := IP,
|
||||
remote_server := RServer,
|
||||
server_host := ServerHost,
|
||||
|
||||
@@ -206,6 +206,18 @@ bounce_offline_message(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec bounce_sm_packet({bounce | term(), stanza()}) -> any().
|
||||
bounce_sm_packet({bounce, #message{meta = #{ignore_sm_bounce := true}} = Packet} = Acc) ->
|
||||
?DEBUG("Dropping packet to unavailable resource:~n~ts",
|
||||
[xmpp:pp(Packet)]),
|
||||
Acc;
|
||||
bounce_sm_packet({bounce, #iq{meta = #{ignore_sm_bounce := true}} = Packet} = Acc) ->
|
||||
?DEBUG("Dropping packet to unavailable resource:~n~ts",
|
||||
[xmpp:pp(Packet)]),
|
||||
Acc;
|
||||
bounce_sm_packet({bounce, #presence{meta = #{ignore_sm_bounce := true}} = Packet} = Acc) ->
|
||||
?DEBUG("Dropping packet to unavailable resource:~n~ts",
|
||||
[xmpp:pp(Packet)]),
|
||||
Acc;
|
||||
bounce_sm_packet({bounce, Packet} = Acc) ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Txt = ?T("User session not found"),
|
||||
|
||||
+46
-25
@@ -32,9 +32,12 @@
|
||||
%% External exports
|
||||
-export([start_link/2,
|
||||
sql_query/2,
|
||||
sql_query/3,
|
||||
sql_query_t/1,
|
||||
sql_transaction/2,
|
||||
sql_transaction/4,
|
||||
sql_bloc/2,
|
||||
sql_bloc/3,
|
||||
abort/1,
|
||||
restart/1,
|
||||
use_new_schema/0,
|
||||
@@ -84,7 +87,8 @@
|
||||
reconnect_count = 0 :: non_neg_integer(),
|
||||
host :: binary(),
|
||||
pending_requests :: p1_queue:queue(),
|
||||
overload_reported :: undefined | integer()}).
|
||||
overload_reported :: undefined | integer(),
|
||||
timeout :: pos_integer()}).
|
||||
|
||||
-define(STATE_KEY, ejabberd_sql_state).
|
||||
-define(NESTING_KEY, ejabberd_sql_nesting_level).
|
||||
@@ -120,35 +124,48 @@ start_link(Host, I) ->
|
||||
p1_fsm:start_link({local, Proc}, ?MODULE, [Host],
|
||||
fsm_limit_opts() ++ ?FSMOPTS).
|
||||
|
||||
-spec sql_query(binary(), sql_query(T), pos_integer()) -> sql_query_result(T).
|
||||
sql_query(Host, Query, Timeout) ->
|
||||
sql_call(Host, {sql_query, Query}, Timeout).
|
||||
|
||||
-spec sql_query(binary(), sql_query(T)) -> sql_query_result(T).
|
||||
sql_query(Host, Query) ->
|
||||
sql_call(Host, {sql_query, Query}).
|
||||
sql_query(Host, Query, query_timeout(Host)).
|
||||
|
||||
%% SQL transaction based on a list of queries
|
||||
%% This function automatically
|
||||
-spec sql_transaction(binary(), [sql_query(T)] | fun(() -> T)) ->
|
||||
-spec sql_transaction(binary(), [sql_query(T)] | fun(() -> T), pos_integer(), pos_integer()) ->
|
||||
{atomic, T} |
|
||||
{aborted, any()}.
|
||||
sql_transaction(Host, Queries)
|
||||
sql_transaction(Host, Queries, Timeout, Restarts)
|
||||
when is_list(Queries) ->
|
||||
F = fun () ->
|
||||
lists:foreach(fun (Query) -> sql_query_t(Query) end,
|
||||
Queries)
|
||||
end,
|
||||
sql_transaction(Host, F);
|
||||
sql_transaction(Host, F, Timeout, Restarts);
|
||||
%% SQL transaction, based on a erlang anonymous function (F = fun)
|
||||
sql_transaction(Host, F) when is_function(F) ->
|
||||
case sql_call(Host, {sql_transaction, F}) of
|
||||
sql_transaction(Host, F, Timeout, Restarts) when is_function(F) ->
|
||||
case sql_call(Host, {sql_transaction, F, Restarts}, Timeout) of
|
||||
{atomic, _} = Ret -> Ret;
|
||||
{aborted, _} = Ret -> Ret;
|
||||
Err -> {aborted, Err}
|
||||
end.
|
||||
|
||||
%% SQL bloc, based on a erlang anonymous function (F = fun)
|
||||
sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
|
||||
-spec sql_transaction(binary(), [sql_query(T)] | fun(() -> T)) ->
|
||||
{atomic, T} |
|
||||
{aborted, any()}.
|
||||
sql_transaction(Host, Queries) ->
|
||||
sql_transaction(Host, Queries, query_timeout(Host), ?MAX_TRANSACTION_RESTARTS).
|
||||
|
||||
sql_call(Host, Msg) ->
|
||||
Timeout = query_timeout(Host),
|
||||
%% SQL bloc, based on a erlang anonymous function (F = fun)
|
||||
sql_bloc(Host, F, Timeout) ->
|
||||
sql_call(Host, {sql_bloc, F}, Timeout).
|
||||
|
||||
sql_bloc(Host, F) ->
|
||||
sql_bloc(Host, F, query_timeout(Host)).
|
||||
|
||||
sql_call(Host, Msg, Timeout) ->
|
||||
case get(?STATE_KEY) of
|
||||
undefined ->
|
||||
sync_send_event(Host,
|
||||
@@ -166,8 +183,8 @@ keep_alive(Host, Proc) ->
|
||||
Timeout) of
|
||||
{selected,_,[[<<"1">>]]} ->
|
||||
ok;
|
||||
_Err ->
|
||||
?ERROR_MSG("Keep alive query failed, closing connection: ~p", [_Err]),
|
||||
Err ->
|
||||
?ERROR_MSG("Keep alive query failed, closing connection: ~p", [Err]),
|
||||
sync_send_event(Proc, force_timeout, Timeout)
|
||||
end.
|
||||
|
||||
@@ -355,7 +372,8 @@ init([Host]) ->
|
||||
QueueType = ejabberd_option:sql_queue_type(Host),
|
||||
{ok, connecting,
|
||||
#state{db_type = DBType, host = Host,
|
||||
pending_requests = p1_queue:new(QueueType, max_fsm_queue())}}.
|
||||
pending_requests = p1_queue:new(QueueType, max_fsm_queue()),
|
||||
timeout = query_timeout(Host)}}.
|
||||
|
||||
connecting(connect, #state{host = Host} = State) ->
|
||||
ConnectRes = case db_opts(Host) of
|
||||
@@ -496,10 +514,12 @@ handle_reconnect(Reason, #state{host = Host, reconnect_count = RC} = State) ->
|
||||
_ -> ok
|
||||
end,
|
||||
p1_fsm:send_event_after(StartInterval, connect),
|
||||
{next_state, connecting, State#state{reconnect_count = RC + 1}}.
|
||||
{next_state, connecting, State#state{reconnect_count = RC + 1,
|
||||
timeout = query_timeout(Host)}}.
|
||||
|
||||
run_sql_cmd(Command, From, State, Timestamp) ->
|
||||
case current_time() >= Timestamp of
|
||||
CT = current_time(),
|
||||
case CT >= Timestamp of
|
||||
true ->
|
||||
State1 = report_overload(State),
|
||||
{next_state, session_established, State1};
|
||||
@@ -510,30 +530,31 @@ run_sql_cmd(Command, From, State, Timestamp) ->
|
||||
State#state.pending_requests),
|
||||
handle_reconnect(Reason, State#state{pending_requests = PR})
|
||||
after 0 ->
|
||||
Timeout = min(query_timeout(State#state.host), Timestamp - CT),
|
||||
put(?NESTING_KEY, ?TOP_LEVEL_TXN),
|
||||
put(?STATE_KEY, State),
|
||||
put(?STATE_KEY, State#state{timeout = Timeout}),
|
||||
abort_on_driver_error(outer_op(Command), From, Timestamp)
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Only called by handle_call, only handles top level operations.
|
||||
-spec outer_op(Op::{atom(), binary()}) ->
|
||||
-spec outer_op(Op::{atom(), binary()} | {sql_transaction, binary(), pos_integer()}) ->
|
||||
{error, Reason::binary()} | {aborted, Reason::binary()} | {atomic, Result::any()}.
|
||||
outer_op({sql_query, Query}) ->
|
||||
sql_query_internal(Query);
|
||||
outer_op({sql_transaction, F}) ->
|
||||
outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>);
|
||||
outer_op({sql_transaction, F, Restarts}) ->
|
||||
outer_transaction(F, Restarts, <<"">>);
|
||||
outer_op({sql_bloc, F}) -> execute_bloc(F).
|
||||
|
||||
%% Called via sql_query/transaction/bloc from client code when inside a
|
||||
%% nested operation
|
||||
nested_op({sql_query, Query}) ->
|
||||
sql_query_internal(Query);
|
||||
nested_op({sql_transaction, F}) ->
|
||||
nested_op({sql_transaction, F, Restarts}) ->
|
||||
NestingLevel = get(?NESTING_KEY),
|
||||
if NestingLevel =:= (?TOP_LEVEL_TXN) ->
|
||||
outer_transaction(F, ?MAX_TRANSACTION_RESTARTS, <<"">>);
|
||||
true -> inner_transaction(F)
|
||||
outer_transaction(F, Restarts, <<"">>);
|
||||
true -> inner_transaction(F)
|
||||
end;
|
||||
nested_op({sql_bloc, F}) -> execute_bloc(F).
|
||||
|
||||
@@ -595,7 +616,7 @@ outer_transaction(F, NRestarts, _Reason) ->
|
||||
[?MAX_TRANSACTION_RESTARTS, Reason,
|
||||
StackTrace, get(?STATE_KEY)]),
|
||||
maybe_restart_transaction(F, NRestarts, Reason, true);
|
||||
?EX_RULE(exit, Reason, _) ->
|
||||
?EX_RULE(_, Reason, _) ->
|
||||
maybe_restart_transaction(F, 0, Reason, true)
|
||||
end
|
||||
end.
|
||||
@@ -726,7 +747,7 @@ sql_query_internal(F) when is_function(F) ->
|
||||
sql_query_internal(Query) ->
|
||||
State = get(?STATE_KEY),
|
||||
?DEBUG("SQL: \"~ts\"", [Query]),
|
||||
QueryTimeout = query_timeout(State#state.host),
|
||||
QueryTimeout = State#state.timeout,
|
||||
Res = case State#state.db_type of
|
||||
odbc ->
|
||||
to_odbc(odbc:sql_query(State#state.db_ref, [Query],
|
||||
|
||||
+237
-237
@@ -226,6 +226,13 @@ store_version(Host, Module, Version) ->
|
||||
["!module=%(SModule)s",
|
||||
"version=%(Version)d"]).
|
||||
|
||||
store_version_t(Module, Version) ->
|
||||
SModule = misc:atom_to_binary(Module),
|
||||
?SQL_UPSERT_T(
|
||||
"schema_version",
|
||||
["!module=%(SModule)s",
|
||||
"version=%(Version)d"]).
|
||||
|
||||
table_exists(Host, Table) ->
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
@@ -398,28 +405,25 @@ get_current_version(Host, Module, Schemas) ->
|
||||
Version
|
||||
end.
|
||||
|
||||
sqlite_table_copy(Host, SchemaInfo, Table) ->
|
||||
ejabberd_sql:sql_transaction(Host,
|
||||
fun() ->
|
||||
TableName = Table#sql_table.name,
|
||||
NewTableName = <<"new_", TableName/binary>>,
|
||||
NewTable = Table#sql_table{name = NewTableName},
|
||||
create_table_t(SchemaInfo, NewTable),
|
||||
SQL2 = <<"INSERT INTO ", NewTableName/binary,
|
||||
" SELECT * FROM ", TableName/binary>>,
|
||||
?INFO_MSG("Copying table ~s to ~s:~n~s~n",
|
||||
[TableName, NewTableName, SQL2]),
|
||||
ejabberd_sql:sql_query_t(SQL2),
|
||||
SQL3 = <<"DROP TABLE ", TableName/binary>>,
|
||||
?INFO_MSG("Droping old table ~s:~n~s~n",
|
||||
[TableName, SQL2]),
|
||||
ejabberd_sql:sql_query_t(SQL3),
|
||||
SQL4 = <<"ALTER TABLE ", NewTableName/binary,
|
||||
" RENAME TO ", TableName/binary>>,
|
||||
?INFO_MSG("Renameing table ~s to ~s:~n~s~n",
|
||||
[NewTableName, TableName, SQL4]),
|
||||
ejabberd_sql:sql_query_t(SQL4)
|
||||
end).
|
||||
sqlite_table_copy_t(SchemaInfo, Table) ->
|
||||
TableName = Table#sql_table.name,
|
||||
NewTableName = <<"new_", TableName/binary>>,
|
||||
NewTable = Table#sql_table{name = NewTableName},
|
||||
create_table_t(SchemaInfo, NewTable),
|
||||
SQL2 = <<"INSERT INTO ", NewTableName/binary,
|
||||
" SELECT * FROM ", TableName/binary>>,
|
||||
?INFO_MSG("Copying table ~s to ~s:~n~s~n",
|
||||
[TableName, NewTableName, SQL2]),
|
||||
ejabberd_sql:sql_query_t(SQL2),
|
||||
SQL3 = <<"DROP TABLE ", TableName/binary>>,
|
||||
?INFO_MSG("Droping old table ~s:~n~s~n",
|
||||
[TableName, SQL2]),
|
||||
ejabberd_sql:sql_query_t(SQL3),
|
||||
SQL4 = <<"ALTER TABLE ", NewTableName/binary,
|
||||
" RENAME TO ", TableName/binary>>,
|
||||
?INFO_MSG("Renameing table ~s to ~s:~n~s~n",
|
||||
[NewTableName, TableName, SQL4]),
|
||||
ejabberd_sql:sql_query_t(SQL4).
|
||||
|
||||
format_type(#sql_schema_info{db_type = pgsql}, Column) ->
|
||||
case Column#sql_column.type of
|
||||
@@ -875,18 +879,18 @@ update_schema(Host, Module, RawSchemas) ->
|
||||
end.
|
||||
|
||||
do_update_schema(Host, Module, SchemaInfo, Schema) ->
|
||||
lists:foreach(
|
||||
fun({add_column, TableName, ColumnName}) ->
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Column} =
|
||||
lists:keysearch(
|
||||
ColumnName, #sql_column.name, Table#sql_table.columns),
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, _DBVersion) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({add_column, TableName, ColumnName}) ->
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Column} =
|
||||
lists:keysearch(
|
||||
ColumnName, #sql_column.name, Table#sql_table.columns),
|
||||
Res =
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun(DBType, _DBVersion) ->
|
||||
Def = format_column_def(SchemaInfo, Column),
|
||||
Default = format_default(SchemaInfo, Column),
|
||||
SQLs =
|
||||
@@ -911,209 +915,205 @@ do_update_schema(Host, Module, SchemaInfo, Schema) ->
|
||||
ColumnName,
|
||||
SQLs]),
|
||||
lists:foreach(
|
||||
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
|
||||
SQLs)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_column, TableName, ColumnName}) ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(_DBType, _DBVersion) ->
|
||||
SQL = [<<"ALTER TABLE ">>,
|
||||
TableName,
|
||||
<<" DROP COLUMN ">>,
|
||||
ColumnName,
|
||||
<<";">>],
|
||||
?INFO_MSG("Drop column ~s/~s:~n~s~n",
|
||||
[TableName,
|
||||
ColumnName,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({create_index, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Index} =
|
||||
lists:keysearch(
|
||||
Columns, #sql_index.columns, Table#sql_table.indices),
|
||||
case Index#sql_index.meta of
|
||||
#{ignore := true} -> ok;
|
||||
_ ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun() ->
|
||||
case Index#sql_index.meta of
|
||||
#{primary_key := true} ->
|
||||
SQL1 = format_add_primary_key(
|
||||
SchemaInfo, Table, Index),
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Add primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL);
|
||||
_ ->
|
||||
SQL1 = format_create_index(
|
||||
SchemaInfo, Table, Index),
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Create index ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
({update_primary_key, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Index} =
|
||||
lists:keysearch(
|
||||
Columns, #sql_index.columns, Table#sql_table.indices),
|
||||
Res =
|
||||
case SchemaInfo#sql_schema_info.db_type of
|
||||
sqlite ->
|
||||
sqlite_table_copy(Host, SchemaInfo, Table);
|
||||
pgsql ->
|
||||
TableName = Table#sql_table.name,
|
||||
SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP CONSTRAINT ",
|
||||
TableName/binary,"_pkey, ",
|
||||
"ADD PRIMARY KEY (">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
Index#sql_index.columns),
|
||||
<<");">>],
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Update primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(_DBType, _DBVersion) ->
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end);
|
||||
mysql ->
|
||||
TableName = Table#sql_table.name,
|
||||
SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP PRIMARY KEY, "
|
||||
"ADD PRIMARY KEY (">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
lists:map(
|
||||
fun(Col) ->
|
||||
format_mysql_index_column(Table, Col)
|
||||
end, Index#sql_index.columns)),
|
||||
<<");">>],
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Update primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(_DBType, _DBVersion) ->
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end)
|
||||
end,
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_index, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
case find_index_name(Host, TableName, Columns) of
|
||||
false ->
|
||||
?ERROR_MSG("Can't find an index to drop for ~s/~p",
|
||||
[TableName, Columns]);
|
||||
{ok, IndexName} ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query(
|
||||
Host,
|
||||
fun(DBType, _DBVersion) ->
|
||||
SQL =
|
||||
case DBType of
|
||||
mysql ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName,
|
||||
<<" ON ">>,
|
||||
TableName,
|
||||
<<";">>];
|
||||
_ ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName, <<";">>]
|
||||
end,
|
||||
?INFO_MSG("Drop index ~s/~p:~n~s~n",
|
||||
[TableName,
|
||||
Columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end, Schema#sql_schema.update),
|
||||
store_version(Host, Module, Schema#sql_schema.version).
|
||||
|
||||
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
|
||||
SQLs)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_column, TableName, ColumnName}) ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun(_DBType, _DBVersion) ->
|
||||
SQL = [<<"ALTER TABLE ">>,
|
||||
TableName,
|
||||
<<" DROP COLUMN ">>,
|
||||
ColumnName,
|
||||
<<";">>],
|
||||
?INFO_MSG("Drop column ~s/~s:~n~s~n",
|
||||
[TableName,
|
||||
ColumnName,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({create_index, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Index} =
|
||||
lists:keysearch(
|
||||
Columns, #sql_index.columns, Table#sql_table.indices),
|
||||
case Index#sql_index.meta of
|
||||
#{ignore := true} -> ok;
|
||||
_ ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun() ->
|
||||
case Index#sql_index.meta of
|
||||
#{primary_key := true} ->
|
||||
SQL1 = format_add_primary_key(
|
||||
SchemaInfo, Table, Index),
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Add primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL);
|
||||
_ ->
|
||||
SQL1 = format_create_index(
|
||||
SchemaInfo, Table, Index),
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Create index ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
({update_primary_key, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
{value, Table} =
|
||||
lists:keysearch(
|
||||
TableName, #sql_table.name, Schema#sql_schema.tables),
|
||||
{value, Index} =
|
||||
lists:keysearch(
|
||||
Columns, #sql_index.columns, Table#sql_table.indices),
|
||||
Res =
|
||||
case SchemaInfo#sql_schema_info.db_type of
|
||||
sqlite ->
|
||||
sqlite_table_copy_t(SchemaInfo, Table);
|
||||
pgsql ->
|
||||
TableName = Table#sql_table.name,
|
||||
SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP CONSTRAINT ",
|
||||
TableName/binary, "_pkey, ",
|
||||
"ADD PRIMARY KEY (">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
Index#sql_index.columns),
|
||||
<<");">>],
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Update primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun(_DBType, _DBVersion) ->
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end);
|
||||
mysql ->
|
||||
TableName = Table#sql_table.name,
|
||||
SQL1 = [<<"ALTER TABLE ">>, TableName, <<" DROP PRIMARY KEY, "
|
||||
"ADD PRIMARY KEY (">>,
|
||||
lists:join(
|
||||
<<", ">>,
|
||||
lists:map(
|
||||
fun(Col) ->
|
||||
format_mysql_index_column(Table, Col)
|
||||
end, Index#sql_index.columns)),
|
||||
<<");">>],
|
||||
SQL = iolist_to_binary(SQL1),
|
||||
?INFO_MSG("Update primary key ~s/~p:~n~s~n",
|
||||
[Table#sql_table.name,
|
||||
Index#sql_index.columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun(_DBType, _DBVersion) ->
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end)
|
||||
end,
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
({drop_index, TableName, Columns1}) ->
|
||||
Columns =
|
||||
case ejabberd_sql:use_new_schema() of
|
||||
true ->
|
||||
Columns1;
|
||||
false ->
|
||||
lists:delete(
|
||||
<<"server_host">>, Columns1)
|
||||
end,
|
||||
case find_index_name(Host, TableName, Columns) of
|
||||
false ->
|
||||
?ERROR_MSG("Can't find an index to drop for ~s/~p",
|
||||
[TableName, Columns]);
|
||||
{ok, IndexName} ->
|
||||
Res =
|
||||
ejabberd_sql:sql_query_t(
|
||||
fun(DBType, _DBVersion) ->
|
||||
SQL =
|
||||
case DBType of
|
||||
mysql ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName,
|
||||
<<" ON ">>,
|
||||
TableName,
|
||||
<<";">>];
|
||||
_ ->
|
||||
[<<"DROP INDEX ">>,
|
||||
IndexName, <<";">>]
|
||||
end,
|
||||
?INFO_MSG("Drop index ~s/~p:~n~s~n",
|
||||
[TableName,
|
||||
Columns,
|
||||
SQL]),
|
||||
ejabberd_sql:sql_query_t(SQL)
|
||||
end),
|
||||
case Res of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Failed to update table ~s: ~p",
|
||||
[TableName, Error]),
|
||||
error(Error);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end, Schema#sql_schema.update),
|
||||
store_version_t(Module, Schema#sql_schema.version)
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(Host, F, ejabberd_option:update_sql_schema_timeout(), 1).
|
||||
|
||||
print_schema(SDBType, SDBVersion, SNewSchema) ->
|
||||
{DBType, DBVersion} =
|
||||
|
||||
+3
-13
@@ -106,7 +106,6 @@ prepare_turn_opts(Opts) ->
|
||||
prepare_turn_opts(Opts, _UseTurn = false) ->
|
||||
set_certfile(Opts);
|
||||
prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
NumberOfMyHosts = length(ejabberd_option:hosts()),
|
||||
TurnIP = case proplists:get_value(turn_ipv4_address, Opts) of
|
||||
undefined ->
|
||||
MyIP = misc:get_my_ipv4_address(),
|
||||
@@ -129,18 +128,9 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
AuthType = proplists:get_value(auth_type, Opts, user),
|
||||
Realm = case proplists:get_value(auth_realm, Opts) of
|
||||
undefined when AuthType == user ->
|
||||
if NumberOfMyHosts > 1 ->
|
||||
?INFO_MSG("You have several virtual hosts "
|
||||
"configured, but option 'auth_realm' is "
|
||||
"undefined and 'auth_type' is set to "
|
||||
"'user', so the TURN relay might not be "
|
||||
"working properly. Using ~ts as a "
|
||||
"fallback",
|
||||
[ejabberd_config:get_myname()]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
[{auth_realm, ejabberd_config:get_myname()}];
|
||||
MyName = ejabberd_config:get_myname(),
|
||||
?DEBUG("Using ~ts as TURN realm", [MyName]),
|
||||
[{auth_realm, MyName}];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
|
||||
@@ -82,7 +82,9 @@ config_reloaded() ->
|
||||
%%%===================================================================
|
||||
%%% gen_event callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
init({[], _}) -> % Called by gen_event:swap_handler
|
||||
{ok, #state{}};
|
||||
init([]) -> % Called by gen_event:add_handler
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
{ok, #state{}}.
|
||||
|
||||
|
||||
+21
-13
@@ -38,13 +38,17 @@
|
||||
%% Update all the modified modules
|
||||
update() ->
|
||||
case update_info() of
|
||||
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
|
||||
Eval =
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?DEBUG("Eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{ok, Dir, UpdatedBeams, _Script, LowLevelScript, _Check} ->
|
||||
case eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]) of
|
||||
{ok, _} ->
|
||||
?DEBUG("Updated: ~p~n", [UpdatedBeams]),
|
||||
{ok, UpdatedBeams};
|
||||
Eval ->
|
||||
?DEBUG("Eval: ~p~n", [Eval]),
|
||||
Eval
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
@@ -56,12 +60,16 @@ update(ModulesToUpdate) ->
|
||||
UpdatedBeamsNow =
|
||||
[A || A <- UpdatedBeamsAll, B <- ModulesToUpdate, A == B],
|
||||
{_, LowLevelScript, _} = build_script(Dir, UpdatedBeamsNow),
|
||||
Eval =
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?DEBUG("Eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
case eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]) of
|
||||
{ok, _} ->
|
||||
?DEBUG("Updated: ~p~n", [UpdatedBeamsNow]),
|
||||
{ok, UpdatedBeamsNow};
|
||||
Eval ->
|
||||
?DEBUG("Eval: ~p~n", [Eval]),
|
||||
Eval
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_web.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
|
||||
+114
-53
@@ -587,7 +587,7 @@ process_admin(Host, #request{path = [<<"online-users">> | RPath], lang = Lang} =
|
||||
Res = [make_command(connected_users_vhost,
|
||||
R,
|
||||
[{<<"host">>, Host}],
|
||||
[{table_options, {2, RPath}},
|
||||
[{table_options, {100, RPath}},
|
||||
{result_links, [{sessions, user, Level, <<"">>}]}])],
|
||||
make_xhtml([?XCT(<<"h1">>, ?T("Online Users"))] ++ Res, Host, R, AJID, Level);
|
||||
process_admin(Host,
|
||||
@@ -747,46 +747,99 @@ list_users(Host, Level, PageSize, RPath, R, RegisterEl) ->
|
||||
end.
|
||||
|
||||
list_users(Host, Level, PageSize, RPath, R, Usernames, RegisterEl) ->
|
||||
IsOffline = gen_mod:is_loaded(Host, mod_offline),
|
||||
IsMam = gen_mod:is_loaded(Host, mod_mam),
|
||||
IsRoster = gen_mod:is_loaded(Host, mod_roster),
|
||||
IsLast = gen_mod:is_loaded(Host, mod_last),
|
||||
Columns =
|
||||
[<<"user">>,
|
||||
{<<"offline">>, right},
|
||||
{<<"roster">>, right},
|
||||
{<<"timestamp">>, left},
|
||||
{<<"status">>, left}],
|
||||
list_users_element(IsOffline, column, offline, {}),
|
||||
list_users_element(IsMam, column, mam, {}),
|
||||
list_users_element(IsRoster, column, roster, {}),
|
||||
list_users_element(IsLast, column, timestamp, {}),
|
||||
list_users_element(IsLast, column, status, {})],
|
||||
Rows =
|
||||
[{make_command(echo,
|
||||
R,
|
||||
[{<<"sentence">>,
|
||||
jid:encode(
|
||||
jid:make(Username, Host))}],
|
||||
[{only, raw_and_value}, {result_links, [{sentence, user, Level, <<"">>}]}]),
|
||||
make_command(get_offline_count,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{value, arg_host, Level, <<"user/", Username/binary, "/queue/">>}]}]),
|
||||
make_command(get_roster_count,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{value, arg_host, Level, <<"user/", Username/binary, "/roster/">>}]}]),
|
||||
?C(element(1,
|
||||
make_command_raw_value(get_last,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}]))),
|
||||
?C(element(2,
|
||||
make_command_raw_value(get_last,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}])))}
|
||||
[list_to_tuple(lists:flatten([make_command(echo,
|
||||
R,
|
||||
[{<<"sentence">>,
|
||||
jid:encode(
|
||||
jid:make(Username, Host))}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{sentence, user, Level, <<"">>}]}]),
|
||||
list_users_element(IsOffline,
|
||||
row,
|
||||
offline,
|
||||
{R, Username, Host, Level}),
|
||||
list_users_element(IsMam,
|
||||
row,
|
||||
mam,
|
||||
{R, Username, Host, Level}),
|
||||
list_users_element(IsRoster,
|
||||
row,
|
||||
roster,
|
||||
{R, Username, Host, Level}),
|
||||
list_users_element(IsLast, row, last, {R, Username, Host})]))
|
||||
|| Username <- Usernames],
|
||||
[RegisterEl,
|
||||
make_command(registered_users, R, [], [{only, presentation}]),
|
||||
make_command(get_offline_count, R, [], [{only, presentation}]),
|
||||
make_command(get_roster_count, R, [], [{only, presentation}]),
|
||||
make_command(get_last, R, [], [{only, presentation}]),
|
||||
make_table(PageSize, RPath, Columns, Rows)].
|
||||
Table = make_table(PageSize, RPath, lists:flatten(Columns), Rows),
|
||||
Result =
|
||||
[RegisterEl,
|
||||
make_command(registered_users, R, [], [{only, presentation}]),
|
||||
list_users_element(IsOffline, presentation, offline, R),
|
||||
list_users_element(IsMam, presentation, mam, R),
|
||||
list_users_element(IsRoster, presentation, roster, R),
|
||||
list_users_element(IsLast, presentation, last, R),
|
||||
Table],
|
||||
lists:flatten(Result).
|
||||
|
||||
list_users_element(false, _, _, _) ->
|
||||
[];
|
||||
list_users_element(_, column, offline, _) ->
|
||||
{<<"offline">>, right};
|
||||
list_users_element(_, column, mam, _) ->
|
||||
{<<"mam">>, right};
|
||||
list_users_element(_, column, roster, _) ->
|
||||
{<<"roster">>, right};
|
||||
list_users_element(_, column, timestamp, _) ->
|
||||
{<<"timestamp">>, left};
|
||||
list_users_element(_, column, status, _) ->
|
||||
{<<"status">>, left};
|
||||
list_users_element(_, row, offline, {R, Username, Host, Level}) ->
|
||||
make_command(get_offline_count,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{value, arg_host, Level, <<"user/", Username/binary, "/queue/">>}]}]);
|
||||
list_users_element(_, row, mam, {R, Username, Host, Level}) ->
|
||||
make_command(get_mam_count,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{value, arg_host, Level, <<"user/", Username/binary, "/mam/">>}]}]);
|
||||
list_users_element(_, row, roster, {R, Username, Host, Level}) ->
|
||||
make_command(get_roster_count,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}],
|
||||
[{only, raw_and_value},
|
||||
{result_links,
|
||||
[{value, arg_host, Level, <<"user/", Username/binary, "/roster/">>}]}]);
|
||||
list_users_element(_, row, last, {R, Username, Host}) ->
|
||||
[?C(element(1,
|
||||
make_command_raw_value(get_last, R, [{<<"user">>, Username}, {<<"host">>, Host}]))),
|
||||
?C(element(2,
|
||||
make_command_raw_value(get_last,
|
||||
R,
|
||||
[{<<"user">>, Username}, {<<"host">>, Host}])))];
|
||||
list_users_element(_, presentation, offline, R) ->
|
||||
make_command(get_offline_count, R, [], [{only, presentation}]);
|
||||
list_users_element(_, presentation, mam, R) ->
|
||||
make_command(get_mam_count, R, [], [{only, presentation}]);
|
||||
list_users_element(_, presentation, roster, R) ->
|
||||
make_command(get_roster_count, R, [], [{only, presentation}]);
|
||||
list_users_element(_, presentation, last, R) ->
|
||||
make_command(get_last, R, [], [{only, presentation}]).
|
||||
|
||||
list_users_diapason(Host, R, Usernames, N, RegisterEl) ->
|
||||
URLFunc = fun url_func/1,
|
||||
@@ -952,6 +1005,17 @@ user_info(User, Server, #request{q = Query, lang = Lang} = R) ->
|
||||
Res = user_parse_query(User, Server, Query),
|
||||
UserItems = ejabberd_hooks:run_fold(webadmin_user,
|
||||
LServer, [], [User, Server, R]),
|
||||
Lasts = case gen_mod:is_loaded(Server, mod_last) of
|
||||
true ->
|
||||
[make_command(get_last, R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[]),
|
||||
make_command(set_last, R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[])];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
[?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("User ~ts"),
|
||||
[us_to_list(US)])))]
|
||||
++
|
||||
@@ -968,13 +1032,8 @@ user_info(User, Server, #request{q = Query, lang = Lang} = R) ->
|
||||
[{result_links, [{node, node, 4, <<>>}]}]),
|
||||
make_command(change_password, R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[{style, danger}]),
|
||||
make_command(get_last, R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[]),
|
||||
make_command(set_last, R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[])] ++
|
||||
[{style, danger}])] ++
|
||||
Lasts ++
|
||||
UserItems ++
|
||||
[?P,
|
||||
make_command(unregister, R,
|
||||
@@ -1180,7 +1239,7 @@ pretty_print_xml({xmlcdata, CData}, Prefix) ->
|
||||
($\n) -> true;
|
||||
($\t) -> true;
|
||||
($\v) -> true;
|
||||
($ ) -> true;
|
||||
($\s) -> true;
|
||||
(_) -> false
|
||||
end, binary_to_list(CData)),
|
||||
if IsBlankCData ->
|
||||
@@ -1667,7 +1726,7 @@ make_command_raw_value(Name, Request, BaseArguments) ->
|
||||
raw_value |
|
||||
raw_and_value} |
|
||||
{input_name_append, [binary()]} |
|
||||
{force_execution, boolean()} |
|
||||
{force_execution, boolean() | undefined} |
|
||||
{table_options, {PageSize :: integer(), RemainingPath :: [binary()]}} |
|
||||
{result_named, boolean()} |
|
||||
{result_links,
|
||||
@@ -1678,20 +1737,17 @@ make_command_raw_value(Name, Request, BaseArguments) ->
|
||||
{style, normal | danger}.
|
||||
make_command2(Name, Request, BaseArguments, Options) ->
|
||||
Only = proplists:get_value(only, Options, all),
|
||||
ForceExecution = proplists:get_value(force_execution, Options, false),
|
||||
ForceExecution = proplists:get_value(force_execution, Options, undefined),
|
||||
InputNameAppend = proplists:get_value(input_name_append, Options, []),
|
||||
Resultnamed = proplists:get_value(result_named, Options, false),
|
||||
ResultLinks = proplists:get_value(result_links, Options, []),
|
||||
TO = proplists:get_value(table_options, Options, {999999, []}),
|
||||
Style = proplists:get_value(style, Options, normal),
|
||||
#request{us = {RUser, RServer},
|
||||
ip = RIp,
|
||||
host = RHost} =
|
||||
Request,
|
||||
#request{us = {RUser, RServer}, ip = RIp} = Request,
|
||||
CallerInfo =
|
||||
#{usr => {RUser, RServer, <<"">>},
|
||||
ip => RIp,
|
||||
caller_host => RHost,
|
||||
caller_host => RServer,
|
||||
caller_module => ?MODULE},
|
||||
try {ejabberd_commands:get_command_definition(Name),
|
||||
ejabberd_access_permissions:can_access(Name, CallerInfo)}
|
||||
@@ -1735,6 +1791,8 @@ make_command2(Name,
|
||||
case {ForceExecution, ResultFormatApi} of
|
||||
{true, _} ->
|
||||
auto;
|
||||
{false, _} ->
|
||||
manual;
|
||||
{_, {_, rescode}} ->
|
||||
manual;
|
||||
{_, {_, restuple}} ->
|
||||
@@ -2226,8 +2284,11 @@ make_result(Binary,
|
||||
when (ElementName == ResultName) or (ElementName == unknown_element_name) ->
|
||||
First = proplists:get_value(first, ArgumentsUsed),
|
||||
Second = proplists:get_value(second, ArgumentsUsed),
|
||||
FirstUrlencoded =
|
||||
hd(string:replace(
|
||||
misc:url_encode(First), "%40", "@")),
|
||||
{GroupId, Host} =
|
||||
case jid:decode(First) of
|
||||
case jid:decode(FirstUrlencoded) of
|
||||
#jid{luser = <<"">>, lserver = G} ->
|
||||
{G, Second};
|
||||
#jid{luser = G, lserver = H} ->
|
||||
|
||||
@@ -256,9 +256,9 @@ ws_loop(Codec, Socket, WsHandleLoopPid, SockMod, Shaper) ->
|
||||
"with pid ~p",
|
||||
[self()]),
|
||||
websocket_close(Codec, Socket, WsHandleLoopPid, SockMod, 1001); % going away
|
||||
_Ignored ->
|
||||
Ignored ->
|
||||
?WARNING_MSG("Received unexpected message, ignoring: ~p",
|
||||
[_Ignored]),
|
||||
[Ignored]),
|
||||
ws_loop(Codec, Socket, WsHandleLoopPid,
|
||||
SockMod, Shaper)
|
||||
end.
|
||||
|
||||
+49
-14
@@ -36,7 +36,7 @@
|
||||
install_contrib_modules/2,
|
||||
config_dir/0, get_commands_spec/0]).
|
||||
-export([modules_configs/0, module_ebin_dir/1]).
|
||||
-export([compile_erlang_file/2, compile_elixir_file/2]).
|
||||
-export([compile_erlang_file/2, compile_elixir_files/2]).
|
||||
-export([web_menu_node/3, web_page_node/3, webadmin_node_contrib/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -79,6 +79,11 @@ handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'ETS-TRANSFER', Table, Process, Module}, State) ->
|
||||
?DEBUG("ejabberd now controls ETS table ~p from process ~p for module ~p",
|
||||
[Table, Process, Module]),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?WARNING_MSG("Unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
@@ -603,8 +608,7 @@ compile(LibDir) ->
|
||||
compile_c_files(LibDir),
|
||||
Er = [compile_erlang_file(Bin, File, Options)
|
||||
|| File <- filelib:wildcard(Src++"/**/*.erl")],
|
||||
Ex = [compile_elixir_file(Bin, File)
|
||||
|| File <- filelib:wildcard(Lib ++ "/**/*.ex")],
|
||||
Ex = compile_elixir_files(Bin, filelib:wildcard(Lib ++ "/**/*.ex")),
|
||||
compile_result(lists:flatten([Er, Ex])).
|
||||
|
||||
compile_c_files(LibDir) ->
|
||||
@@ -669,18 +673,34 @@ compile_erlang_file(Dest, File, ErlOptions) ->
|
||||
end.
|
||||
|
||||
-ifdef(ELIXIR_ENABLED).
|
||||
compile_elixir_file(Dest, File) when is_list(Dest) and is_list(File) ->
|
||||
compile_elixir_file(list_to_binary(Dest), list_to_binary(File));
|
||||
compile_elixir_files(_, []) ->
|
||||
ok;
|
||||
compile_elixir_files(Dest, [File | _] = Files) when is_list(Dest) and is_list(File) ->
|
||||
BinFiles = [list_to_binary(F) || F <- Files],
|
||||
compile_elixir_files(list_to_binary(Dest), BinFiles);
|
||||
|
||||
compile_elixir_file(Dest, File) ->
|
||||
try 'Elixir.Kernel.ParallelCompiler':files_to_path([File], Dest, []) of
|
||||
Modules when is_list(Modules) -> {ok, Modules}
|
||||
compile_elixir_files(Dest, Files) ->
|
||||
try 'Elixir.Kernel.ParallelCompiler':compile_to_path(Files, Dest, [{return_diagnostics, true}]) of
|
||||
{ok, Modules, []} when is_list(Modules) ->
|
||||
{ok, Modules};
|
||||
{ok, Modules, Warnings} when is_list(Modules) ->
|
||||
?WARNING_MSG("Warnings compiling module: ~n~p", [Warnings]),
|
||||
{ok, Modules}
|
||||
catch
|
||||
_ -> {error, {compilation_failed, File}}
|
||||
A:B ->
|
||||
?ERROR_MSG("Problem ~p compiling Elixir files: ~p~nFiles: ~p", [A, B, Files]),
|
||||
{error, {compilation_failed, Files}}
|
||||
end.
|
||||
-else.
|
||||
compile_elixir_file(_, File) ->
|
||||
{error, {compilation_failed, File}}.
|
||||
compile_elixir_files(_, []) ->
|
||||
ok;
|
||||
compile_elixir_files(_, Files) ->
|
||||
ErrorString = "Attempted to compile Elixir files, but Elixir support is "
|
||||
"not available in ejabberd. Try compiling ejabberd using "
|
||||
"'./configure --enable-elixir' or './configure --with-rebar=mix'",
|
||||
?ERROR_MSG(ErrorString, []),
|
||||
io:format("Error: " ++ ErrorString ++ "~n", []),
|
||||
{error, {elixir_not_available, Files}}.
|
||||
-endif.
|
||||
|
||||
install(Module, Spec, SrcDir, LibDir, Config) ->
|
||||
@@ -749,8 +769,10 @@ fetch_rebar_deps(SrcDir) ->
|
||||
{ok, CurDir} = file:get_cwd(),
|
||||
file:set_cwd(SrcDir),
|
||||
filelib:ensure_dir(filename:join("deps", ".")),
|
||||
lists:foreach(fun({_App, Cmd}) ->
|
||||
os:cmd("cd deps; "++Cmd++"; cd ..")
|
||||
lists:foreach(fun({App, Cmd}) ->
|
||||
io:format("Fetching dependency ~s: ", [App]),
|
||||
Result = os:cmd("cd deps; "++Cmd++"; cd .."),
|
||||
io:format("~s", [Result])
|
||||
end, Deps),
|
||||
file:set_cwd(CurDir)
|
||||
end.
|
||||
@@ -764,6 +786,19 @@ rebar_deps(Script) ->
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
rebar_dep({App, Version, Git}) when Version /= ".*" ->
|
||||
AppS = atom_to_list(App),
|
||||
Help = os:cmd("mix hex.package"),
|
||||
case string:find(Help, "mix hex.package fetch") /= nomatch of
|
||||
true ->
|
||||
{App, "mix hex.package fetch "++AppS++" "++Version++" --unpack"};
|
||||
false ->
|
||||
io:format("I'll download ~p using git because I can't use Mix "
|
||||
"to fetch from hex.pm:~n~s", [AppS, Help]),
|
||||
rebar_dep({App, ".*", Git})
|
||||
end;
|
||||
|
||||
rebar_dep({App, _, {git, Url}}) ->
|
||||
{App, "git clone "++Url++" "++filename:basename(App)};
|
||||
rebar_dep({App, _, {git, Url, {branch, Ref}}}) ->
|
||||
@@ -1195,7 +1230,7 @@ is_elixir_module(Module) ->
|
||||
filelib:wildcard(Src++"/*.{erl}")} of
|
||||
{[_ | _], []} ->
|
||||
true;
|
||||
{[], [_ | _]} ->
|
||||
{[], _} ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
+10
-1
@@ -42,7 +42,7 @@
|
||||
is_mucsub_message/1, best_match/2, pmap/2, peach/2, format_exception/4,
|
||||
get_my_ipv4_address/0, get_my_ipv6_address/0, parse_ip_mask/1,
|
||||
crypto_hmac/3, crypto_hmac/4, uri_parse/1, uri_parse/2, uri_quote/1,
|
||||
json_encode/1, json_decode/1,
|
||||
json_encode/1, json_decode/1, json_encode_with_kv_lists/1,
|
||||
set_proc_label/1,
|
||||
match_ip_mask/3, format_hosts_list/1, format_cycle/1, delete_dir/1,
|
||||
semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1]).
|
||||
@@ -132,11 +132,20 @@ crypto_hmac(Type, Key, Data, MacL) -> crypto:macN(hmac, Type, Key, Data, MacL).
|
||||
-endif.
|
||||
|
||||
-ifdef(OTP_BELOW_27).
|
||||
json_encode_with_kv_lists(Term) ->
|
||||
jiffy:encode(Term).
|
||||
json_encode(Term) ->
|
||||
jiffy:encode(Term).
|
||||
json_decode(Bin) ->
|
||||
jiffy:decode(Bin, [return_maps]).
|
||||
-else.
|
||||
json_encode_with_kv_lists(Term) ->
|
||||
iolist_to_binary(json:encode(Term,
|
||||
fun([{_, _} | _] = Val, Encoder) ->
|
||||
json:encode_key_value_list(Val, Encoder);
|
||||
(Val, Encoder) ->
|
||||
json:encode_value(Val, Encoder)
|
||||
end)).
|
||||
json_encode(Term) ->
|
||||
iolist_to_binary(json:encode(Term)).
|
||||
json_decode(Bin) ->
|
||||
|
||||
+12
-77
@@ -1425,8 +1425,10 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
|
||||
show = misc:binary_to_atom(Show),
|
||||
priority = Priority,
|
||||
sub_els = []},
|
||||
Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
|
||||
ejabberd_c2s:set_presence(Ref, Pres).
|
||||
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
||||
none -> throw({error, "User session not found"});
|
||||
Ref -> ejabberd_c2s:set_presence(Ref, Pres)
|
||||
end.
|
||||
|
||||
user_sessions_info(User, Host) ->
|
||||
lists:filtermap(fun(Resource) ->
|
||||
@@ -1934,7 +1936,7 @@ srg_get_displayed(Group, Host) ->
|
||||
error ->
|
||||
[]
|
||||
end,
|
||||
proplists:get_value(displayed_groups, Opts).
|
||||
proplists:get_value(displayed_groups, Opts, []).
|
||||
|
||||
srg_add_displayed(Group, Host, NewGroup) ->
|
||||
Opts =
|
||||
@@ -2204,13 +2206,7 @@ web_page_host(Acc, _, _) ->
|
||||
%%% HostUser
|
||||
|
||||
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
|
||||
Acc
|
||||
++ [{<<"auth">>, <<"Authentication">>},
|
||||
{<<"mam">>, <<"MAM">>},
|
||||
{<<"privacy">>, <<"Privacy Lists">>},
|
||||
{<<"private">>, <<"Private XML Storage">>},
|
||||
{<<"session">>, <<"Sessions">>},
|
||||
{<<"vcard">>, <<"vCard">>}].
|
||||
Acc ++ [{<<"auth">>, <<"Authentication">>}, {<<"session">>, <<"Sessions">>}].
|
||||
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"auth">>]} = R) ->
|
||||
Ban = make_command(ban_account,
|
||||
@@ -2240,26 +2236,6 @@ web_page_hostuser(_, Host, User, #request{path = [<<"auth">>]} = R) ->
|
||||
[{<<"user">>, User}, {<<"host">>, Host}],
|
||||
[{style, danger}])],
|
||||
{stop, Res};
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"mam">>]} = R) ->
|
||||
Res = ?H1GL(<<"MAM">>, <<"modules/#mod_mam">>, <<"mod_mam">>)
|
||||
++ [make_command(remove_mam_for_user,
|
||||
R,
|
||||
[{<<"user">>, User}, {<<"host">>, Host}],
|
||||
[{style, danger}]),
|
||||
make_command(remove_mam_for_user_with_peer,
|
||||
R,
|
||||
[{<<"user">>, User}, {<<"host">>, Host}],
|
||||
[{style, danger}])],
|
||||
{stop, Res};
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"privacy">>]} = R) ->
|
||||
Res = ?H1GL(<<"Privacy Lists">>, <<"modules/#mod_privacy">>, <<"mod_privacy">>)
|
||||
++ [make_command(privacy_set, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Res};
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"private">>]} = R) ->
|
||||
Res = ?H1GL(<<"Private XML Storage">>, <<"modules/#mod_private">>, <<"mod_private">>)
|
||||
++ [make_command(private_set, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(private_get, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Res};
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"session">>]} = R) ->
|
||||
Head = [?XC(<<"h1">>, <<"Sessions">>), ?BR],
|
||||
Set = [make_command(resource_num, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
@@ -2278,47 +2254,6 @@ web_page_hostuser(_, Host, User, #request{path = [<<"session">>]} = R) ->
|
||||
make_command(get_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(num_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Head ++ Get ++ Set};
|
||||
web_page_hostuser(_, Host, User, #request{path = [<<"vcard">>]} = R) ->
|
||||
Head = ?H1GL(<<"vCard">>, <<"modules/#mod_vcard">>, <<"mod_vcard">>),
|
||||
Set = [make_command(set_nickname, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(set_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(set_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(set_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
timer:sleep(100), % setting vcard takes a while, let's delay the get commands
|
||||
FieldNames = [<<"VERSION">>, <<"FN">>, <<"NICKNAME">>, <<"BDAY">>],
|
||||
FieldNames2 =
|
||||
[{<<"N">>, <<"FAMILY">>},
|
||||
{<<"N">>, <<"GIVEN">>},
|
||||
{<<"N">>, <<"MIDDLE">>},
|
||||
{<<"ADR">>, <<"CTRY">>},
|
||||
{<<"ADR">>, <<"LOCALITY">>},
|
||||
{<<"EMAIL">>, <<"USERID">>}],
|
||||
Get = [make_command(get_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
?XE(<<"blockquote">>,
|
||||
[make_table([<<"name">>, <<"value">>],
|
||||
[{?C(FieldName),
|
||||
make_command(get_vcard,
|
||||
R,
|
||||
[{<<"user">>, User},
|
||||
{<<"host">>, Host},
|
||||
{<<"name">>, FieldName}],
|
||||
[{only, value}])}
|
||||
|| FieldName <- FieldNames])]),
|
||||
make_command(get_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
?XE(<<"blockquote">>,
|
||||
[make_table([<<"name">>, <<"subname">>, <<"value">>],
|
||||
[{?C(FieldName),
|
||||
?C(FieldSubName),
|
||||
make_command(get_vcard2,
|
||||
R,
|
||||
[{<<"user">>, User},
|
||||
{<<"host">>, Host},
|
||||
{<<"name">>, FieldName},
|
||||
{<<"subname">>, FieldSubName}],
|
||||
[{only, value}])}
|
||||
|| {FieldName, FieldSubName} <- FieldNames2])]),
|
||||
make_command(get_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Head ++ Get ++ Set};
|
||||
web_page_hostuser(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
@@ -2389,7 +2324,7 @@ mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("This module provides additional administrative commands."), "",
|
||||
?T("Details for some commands:"), "",
|
||||
?T("- 'ban-acount':"),
|
||||
?T("- 'ban_account':"),
|
||||
?T("This command kicks all the connected sessions of the account "
|
||||
"from the server. It also changes their password to a randomly "
|
||||
"generated one, so they can't login anymore unless a server "
|
||||
@@ -2402,8 +2337,8 @@ mod_doc() ->
|
||||
"`C:/Program Files/ejabberd` or similar. If you use other "
|
||||
"Operating System, place the file on the same directory where "
|
||||
"the .beam files are installed. See below an example roster file."),
|
||||
?T("- 'srg-create':"),
|
||||
?T("If you want to put a group Name with blankspaces, use the "
|
||||
?T("- 'srg_create':"),
|
||||
?T("If you want to put a group Name with blank spaces, use the "
|
||||
"characters \"\' and \'\" to define when the Name starts and "
|
||||
"ends. See an example below.")],
|
||||
example =>
|
||||
@@ -2427,6 +2362,6 @@ mod_doc() ->
|
||||
"boby@example.org will be kicked, and its password will be set "
|
||||
"to something like "
|
||||
"'BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_rooms'"),
|
||||
["ejabberdctl vhost example.org ban-account boby \"Spammed rooms\""]},
|
||||
{?T("Call to srg-create using double-quotes and single-quotes:"),
|
||||
["ejabberdctl srg-create g1 example.org \"\'Group number 1\'\" this_is_g1 g1"]}]}.
|
||||
["ejabberdctl vhost example.org ban_account boby \"Spammed rooms\""]},
|
||||
{?T("Call to srg_create using double-quotes and single-quotes:"),
|
||||
["ejabberdctl srg_create g1 example.org \"\'Group number 1\'\" this_is_g1 g1"]}]}.
|
||||
|
||||
@@ -494,9 +494,6 @@ announce_commands(From, To,
|
||||
{error, xmpp:err_bad_request(Txt, Lang)}
|
||||
end.
|
||||
|
||||
-define(TVFIELD(Type, Var, Val),
|
||||
#xdata_field{type = Type, var = Var, values = vvaluel(Val)}).
|
||||
|
||||
vvaluel(Val) ->
|
||||
case Val of
|
||||
<<>> -> [];
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
-export([start/2, stop/1, reload/3, mod_doc/0,
|
||||
depends/2, mod_opt_type/1, mod_options/1]).
|
||||
|
||||
-export([filter_packet/1, filter_offline_msg/1, filter_subscription/2]).
|
||||
-export([filter_packet/1, filter_offline_msg/1, filter_subscription/2,
|
||||
get_sm_features/5]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -40,13 +41,17 @@
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
|
||||
-define(NS_BLOCK_STRANGERS, <<"urn:ejabberd:block-strangers">>).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
|
||||
%%%===================================================================
|
||||
%%% Callbacks and hooks
|
||||
%%%===================================================================
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, user_receive_packet, filter_packet, 25},
|
||||
{ok, [{hook, disco_local_features, get_sm_features, 50},
|
||||
{hook, disco_sm_features, get_sm_features, 50},
|
||||
{hook, user_receive_packet, filter_packet, 25},
|
||||
{hook, roster_in_subscription, filter_subscription, 25},
|
||||
{hook, offline_message_hook, filter_offline_msg, 25}]}.
|
||||
|
||||
@@ -56,6 +61,16 @@ stop(_Host) ->
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||
Features = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
end,
|
||||
{result, [?NS_BLOCK_STRANGERS | Features]};
|
||||
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec filter_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()} |
|
||||
{stop, {drop, c2s_state()}}.
|
||||
filter_packet({#message{from = From} = Msg, State} = Acc) ->
|
||||
|
||||
+2
-2
@@ -74,8 +74,8 @@ process([], #request{method = 'GET', data = <<>>}) ->
|
||||
{200, ?HEADER(?CT_XML), get_human_html_xmlel()};
|
||||
process([], #request{method = 'OPTIONS', data = <<>>}) ->
|
||||
{200, ?OPTIONS_HEADER, []};
|
||||
process(_Path, _Request) ->
|
||||
?DEBUG("Bad Request: ~p", [_Request]),
|
||||
process(_Path, Request) ->
|
||||
?DEBUG("Bad Request: ~p", [Request]),
|
||||
{400, ?HEADER(?CT_XML),
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_bosh_sql.erl
|
||||
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 28 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
@@ -145,10 +145,10 @@ c2s_session_resumed(State) ->
|
||||
c2s_session_opened(State) ->
|
||||
maps:remove(carboncopy, State).
|
||||
|
||||
c2s_inline_features({Sasl, Bind} = Acc, Host) ->
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host) ->
|
||||
case gen_mod:is_loaded(Host, ?MODULE) of
|
||||
true ->
|
||||
{Sasl, [#bind2_feature{var = ?NS_CARBONS_2} | Bind]};
|
||||
{Sasl, [#bind2_feature{var = ?NS_CARBONS_2} | Bind], Extra};
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
@@ -139,8 +139,8 @@ handle_call(Request, From, State) ->
|
||||
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
?WARNING_MSG("Unexpected cast = ~p", [_Msg]),
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("Unexpected cast = ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(clean, State) ->
|
||||
@@ -151,8 +151,8 @@ handle_info(clean, State) ->
|
||||
ets:fun2ms(fun({_, _, UnbanTS, _}) -> UnbanTS =< Now end)),
|
||||
erlang:send_after(?CLEAN_INTERVAL, self(), clean),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
?WARNING_MSG("Unexpected info = ~p", [_Info]),
|
||||
handle_info(Info, State) ->
|
||||
?WARNING_MSG("Unexpected info = ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
|
||||
+26
-2
@@ -332,21 +332,40 @@ format_arg({Elements},
|
||||
({Val}) when is_list(Val) ->
|
||||
format_arg({Val}, Tuple)
|
||||
end, Elements);
|
||||
format_arg(Map,
|
||||
{list, {_ElementDefName, {tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]}}})
|
||||
when is_map(Map) andalso
|
||||
(Tuple1S == binary orelse Tuple1S == string) ->
|
||||
maps:fold(
|
||||
fun(K, V, Acc) ->
|
||||
[{format_arg(K, Tuple1S), format_arg(V, Tuple2S)} | Acc]
|
||||
end, [], Map);
|
||||
format_arg(Elements,
|
||||
{list, {_ElementDefName, {list, _} = ElementDefFormat}})
|
||||
when is_list(Elements) ->
|
||||
[{format_arg(Element, ElementDefFormat)}
|
||||
|| Element <- Elements];
|
||||
|
||||
%% Covered by command_test_list and command_test_list_tuple
|
||||
format_arg(Elements,
|
||||
{list, {_ElementDefName, ElementDefFormat}})
|
||||
when is_list(Elements) ->
|
||||
[format_arg(Element, ElementDefFormat)
|
||||
|| Element <- Elements];
|
||||
|
||||
format_arg({[{Name, Value}]},
|
||||
{tuple, [{_Tuple1N, Tuple1S}, {_Tuple2N, Tuple2S}]})
|
||||
when Tuple1S == binary;
|
||||
Tuple1S == string ->
|
||||
{format_arg(Name, Tuple1S), format_arg(Value, Tuple2S)};
|
||||
|
||||
%% Covered by command_test_tuple and command_test_list_tuple
|
||||
format_arg(Elements,
|
||||
{tuple, ElementsDef})
|
||||
when is_map(Elements) ->
|
||||
list_to_tuple([element(2, maps:find(atom_to_binary(Name, latin1), Elements))
|
||||
|| {Name, _Format} <- ElementsDef]);
|
||||
|
||||
format_arg({Elements},
|
||||
{tuple, ElementsDef})
|
||||
when is_list(Elements) ->
|
||||
@@ -363,10 +382,12 @@ format_arg({Elements},
|
||||
end
|
||||
end, ElementsDef),
|
||||
list_to_tuple(F);
|
||||
|
||||
format_arg(Elements, {list, ElementsDef})
|
||||
when is_list(Elements) and is_atom(ElementsDef) ->
|
||||
[format_arg(Element, ElementsDef)
|
||||
|| Element <- Elements];
|
||||
|
||||
format_arg(Arg, integer) when is_integer(Arg) -> Arg;
|
||||
format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg);
|
||||
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
|
||||
@@ -452,6 +473,7 @@ format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
|
||||
format_result(Els, {Name, {list, {_, {tuple, [{name, string}, {value, _}]}} = Fmt}}) ->
|
||||
{misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
|
||||
|
||||
%% Covered by command_test_list and command_test_list_tuple
|
||||
format_result(Els, {Name, {list, Def}}) ->
|
||||
{misc:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
|
||||
|
||||
@@ -465,9 +487,11 @@ format_result(Tuple, {_Name, {tuple, [{name, string}, {value, _} = ValFmt]}}) ->
|
||||
{_, Val2} = format_result(Val, ValFmt),
|
||||
{iolist_to_binary(Name2), Val2};
|
||||
|
||||
%% Covered by command_test_tuple and command_test_list_tuple
|
||||
format_result(Tuple, {Name, {tuple, Def}}) ->
|
||||
Els = lists:zip(tuple_to_list(Tuple), Def),
|
||||
{misc:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
|
||||
Els2 = [format_result(El, ElDef) || {El, ElDef} <- Els],
|
||||
{misc:atom_to_binary(Name), maps:from_list(Els2)};
|
||||
|
||||
format_result(404, {_Name, _}) ->
|
||||
"not_found".
|
||||
@@ -518,7 +542,7 @@ log(Call, Args, IP) ->
|
||||
?INFO_MSG("API call ~ts ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
|
||||
|
||||
hide_sensitive_args(Args=[_H|_T]) ->
|
||||
lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
|
||||
lists:map(fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
|
||||
({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
|
||||
(E) -> E end,
|
||||
Args);
|
||||
|
||||
+155
-5
@@ -38,6 +38,7 @@
|
||||
|
||||
-export([sm_receive_packet/1, user_receive_packet/1, user_send_packet/1,
|
||||
user_send_packet_strip_tag/1, process_iq_v0_2/1, process_iq_v0_3/1,
|
||||
disco_local_features/5,
|
||||
disco_sm_features/5, remove_user/2, remove_room/3, mod_opt_type/1,
|
||||
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
|
||||
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
|
||||
@@ -45,13 +46,21 @@
|
||||
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
|
||||
is_empty_for_user/2, is_empty_for_room/3, check_create_room/4,
|
||||
process_iq/3, store_mam_message/7, make_id/0, wrap_as_mucsub/2, select/7,
|
||||
get_mam_count/2,
|
||||
webadmin_menu_hostuser/4,
|
||||
webadmin_page_hostuser/4,
|
||||
get_mam_messages/2, webadmin_user/4,
|
||||
delete_old_messages_batch/5, delete_old_messages_status/1, delete_old_messages_abort/1,
|
||||
remove_message_from_archive/3]).
|
||||
|
||||
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("mod_mam.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
@@ -139,6 +148,8 @@ start(Host, Opts) ->
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:add(muc_process_iq, Host, ?MODULE,
|
||||
muc_process_iq, 50),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
disco_local_features, 50),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
@@ -149,6 +160,12 @@ start(Host, Opts) ->
|
||||
set_room_option, 50),
|
||||
ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
|
||||
store_mam_message, 100),
|
||||
ejabberd_hooks:add(webadmin_menu_hostuser, Host, ?MODULE,
|
||||
webadmin_menu_hostuser, 50),
|
||||
ejabberd_hooks:add(webadmin_page_hostuser, Host, ?MODULE,
|
||||
webadmin_page_hostuser, 50),
|
||||
ejabberd_hooks:add(webadmin_user, Host, ?MODULE,
|
||||
webadmin_user, 50),
|
||||
case mod_mam_opt:assume_mam_usage(Opts) of
|
||||
true ->
|
||||
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
|
||||
@@ -212,6 +229,8 @@ stop(Host) ->
|
||||
muc_filter_message, 50),
|
||||
ejabberd_hooks:delete(muc_process_iq, Host, ?MODULE,
|
||||
muc_process_iq, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
|
||||
disco_local_features, 50),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
@@ -222,6 +241,12 @@ stop(Host) ->
|
||||
set_room_option, 50),
|
||||
ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
|
||||
store_mam_message, 100),
|
||||
ejabberd_hooks:delete(webadmin_menu_hostuser, Host, ?MODULE,
|
||||
webadmin_menu_hostuser, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_hostuser, Host, ?MODULE,
|
||||
webadmin_page_hostuser, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host, ?MODULE,
|
||||
webadmin_user, 50),
|
||||
case mod_mam_opt:assume_mam_usage(Host) of
|
||||
true ->
|
||||
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
|
||||
@@ -592,6 +617,20 @@ parse_query(#mam_query{xdata = #xdata{}} = Query, Lang) ->
|
||||
parse_query(#mam_query{}, _Lang) ->
|
||||
{ok, []}.
|
||||
|
||||
disco_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc;
|
||||
disco_local_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||
Features = case Acc of
|
||||
{result, Fs} -> Fs;
|
||||
empty -> []
|
||||
end,
|
||||
{result, [?NS_MESSAGE_RETRACT | Features]};
|
||||
disco_local_features(empty, _From, _To, _Node, Lang) ->
|
||||
Txt = ?T("No features available"),
|
||||
{error, xmpp:err_item_not_found(Txt, Lang)};
|
||||
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
disco_sm_features(empty, From, To, Node, Lang) ->
|
||||
disco_sm_features({result, []}, From, To, Node, Lang);
|
||||
disco_sm_features({result, OtherFeatures},
|
||||
@@ -614,6 +653,52 @@ message_is_archived(false, #{lserver := LServer}, Pkt) ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Commands
|
||||
%%%
|
||||
|
||||
%% @format-begin
|
||||
|
||||
get_mam_count(User, Host) ->
|
||||
Jid = jid:make(User, Host),
|
||||
{_, _, Count} = select(Host, Jid, Jid, [], #rsm_set{}, chat, only_count),
|
||||
Count.
|
||||
|
||||
get_mam_messages(User, Host) ->
|
||||
Jid = jid:make(User, Host),
|
||||
{Messages, _, _} = select(Host, Jid, Jid, [], #rsm_set{}, chat, only_messages),
|
||||
format_user_messages(Messages).
|
||||
|
||||
format_user_messages(Messages) ->
|
||||
lists:map(fun({_ID, _IDInt, Fwd}) ->
|
||||
El = hd(Fwd#forwarded.sub_els),
|
||||
FPacket =
|
||||
ejabberd_web_admin:pretty_print_xml(
|
||||
xmpp:encode(El)),
|
||||
SFrom = jid:encode(El#message.from),
|
||||
STo = jid:encode(El#message.to),
|
||||
Time = format_time(Fwd#forwarded.delay#delay.stamp),
|
||||
{Time, SFrom, STo, FPacket}
|
||||
end,
|
||||
Messages).
|
||||
|
||||
format_time(Now) ->
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Now),
|
||||
str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Year, Month, Day, Hour, Minute, Second]).
|
||||
|
||||
webadmin_user(Acc, User, Server, R) ->
|
||||
Acc
|
||||
++ [make_command(get_mam_count,
|
||||
R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[{result_links, [{value, arg_host, 4, <<"user/", User/binary, "/mam/">>}]}])].
|
||||
%% @format-end
|
||||
|
||||
%%%
|
||||
%%% Commands: Purge
|
||||
%%%
|
||||
|
||||
delete_old_messages_batch(Server, Type, Days, BatchSize, Rate) when Type == <<"chat">>;
|
||||
Type == <<"groupchat">>;
|
||||
Type == <<"all">> ->
|
||||
@@ -1487,7 +1572,29 @@ get_jids(Js) ->
|
||||
[jid:tolower(jid:remove_resource(J)) || J <- Js].
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
|
||||
[
|
||||
#ejabberd_commands{name = get_mam_count, tags = [mam],
|
||||
desc = "Get number of MAM messages in a local user archive",
|
||||
module = ?MODULE, function = get_mam_count,
|
||||
note = "added in 24.10",
|
||||
policy = user,
|
||||
args = [],
|
||||
result_example = 5,
|
||||
result_desc = "Number",
|
||||
result = {value, integer}},
|
||||
#ejabberd_commands{name = get_mam_messages,
|
||||
tags = [internal, mam],
|
||||
desc = "Get the mam messages",
|
||||
policy = user,
|
||||
module = mod_mam, function = get_mam_messages,
|
||||
args = [],
|
||||
result = {archive, {list, {messages, {tuple, [{time, string},
|
||||
{from, string},
|
||||
{to, string},
|
||||
{packet, string}
|
||||
]}}}}},
|
||||
|
||||
#ejabberd_commands{name = delete_old_mam_messages, tags = [mam, purge],
|
||||
desc = "Delete MAM messages older than DAYS",
|
||||
longdesc = "Valid message TYPEs: "
|
||||
"`chat`, `groupchat`, `all`.",
|
||||
@@ -1497,7 +1604,7 @@ get_commands_spec() ->
|
||||
args_example = [<<"all">>, 31],
|
||||
args = [{type, binary}, {days, integer}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = delete_old_mam_messages_batch, tags = [purge],
|
||||
#ejabberd_commands{name = delete_old_mam_messages_batch, tags = [mam, purge],
|
||||
desc = "Delete MAM messages older than DAYS",
|
||||
note = "added in 22.05",
|
||||
longdesc = "Valid message TYPEs: "
|
||||
@@ -1513,7 +1620,7 @@ get_commands_spec() ->
|
||||
result = {res, restuple},
|
||||
result_desc = "Result tuple",
|
||||
result_example = {ok, <<"Removal of 5000 messages in progress">>}},
|
||||
#ejabberd_commands{name = delete_old_mam_messages_status, tags = [purge],
|
||||
#ejabberd_commands{name = delete_old_mam_messages_status, tags = [mam, purge],
|
||||
desc = "Status of delete old MAM messages operation",
|
||||
note = "added in 22.05",
|
||||
module = ?MODULE, function = delete_old_messages_status,
|
||||
@@ -1523,7 +1630,7 @@ get_commands_spec() ->
|
||||
result = {status, string},
|
||||
result_desc = "Status test",
|
||||
result_example = "Operation in progress, delete 5000 messages"},
|
||||
#ejabberd_commands{name = abort_delete_old_mam_messages, tags = [purge],
|
||||
#ejabberd_commands{name = abort_delete_old_mam_messages, tags = [mam, purge],
|
||||
desc = "Abort currently running delete old MAM messages operation",
|
||||
note = "added in 22.05",
|
||||
module = ?MODULE, function = delete_old_messages_abort,
|
||||
@@ -1555,6 +1662,49 @@ get_commands_spec() ->
|
||||
result_example = {ok, <<"MAM archive removed">>}}
|
||||
].
|
||||
|
||||
|
||||
%%%
|
||||
%%% WebAdmin
|
||||
%%%
|
||||
|
||||
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
|
||||
Acc ++ [{<<"mam">>, <<"MAM">>},
|
||||
{<<"mam-archive">>, <<"MAM Archive">>}].
|
||||
|
||||
webadmin_page_hostuser(_, Host, U,
|
||||
#request{us = _US, path = [<<"mam">>]} = R) ->
|
||||
Res = ?H1GL(<<"MAM">>, <<"modules/#mod_mam">>, <<"mod_mam">>)
|
||||
++ [make_command(get_mam_count,
|
||||
R,
|
||||
[{<<"user">>, U}, {<<"host">>, Host}],
|
||||
[{result_links,
|
||||
[{value, arg_host, 5, <<"user/", U/binary, "/mam-archive/">>}]}]),
|
||||
make_command(remove_mam_for_user,
|
||||
R,
|
||||
[{<<"user">>, U}, {<<"host">>, Host}],
|
||||
[{style, danger}]),
|
||||
make_command(remove_mam_for_user_with_peer,
|
||||
R,
|
||||
[{<<"user">>, U}, {<<"host">>, Host}],
|
||||
[{style, danger}])],
|
||||
{stop, Res};
|
||||
webadmin_page_hostuser(_, Host, U,
|
||||
#request{us = _US, path = [<<"mam-archive">> | RPath],
|
||||
lang = Lang} = R) ->
|
||||
PageTitle =
|
||||
str:translate_and_format(Lang, ?T("~ts's MAM Archive"), [jid:encode({U, Host, <<"">>})]),
|
||||
Head = ?H1GL(PageTitle, <<"modules/#mod_mam">>, <<"mod_mam">>),
|
||||
Res = make_command(get_mam_messages, R, [{<<"user">>, U},
|
||||
{<<"host">>, Host}],
|
||||
[{table_options, {10, RPath}},
|
||||
{result_links, [{packet, paragraph, 1, <<"">>}]}]),
|
||||
{stop, Head ++ [Res]};
|
||||
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
|
||||
|
||||
%%%
|
||||
%%% Documentation
|
||||
%%%
|
||||
|
||||
mod_opt_type(compress_xml) ->
|
||||
econf:bool();
|
||||
mod_opt_type(assume_mam_usage) ->
|
||||
@@ -1678,7 +1828,7 @@ mod_doc() ->
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("When this option is disabled, for each individual "
|
||||
"subscriber a separa mucsub message is stored. With this "
|
||||
"subscriber a separate mucsub message is stored. With this "
|
||||
"option enabled, when a user fetches archive virtual "
|
||||
"mucsub, messages are generated from muc archives. "
|
||||
"The default value is 'false'.")}}]}.
|
||||
|
||||
@@ -386,8 +386,8 @@ process([<<"federation">>, <<"v2">>, <<"send_join">>, RoomID, EventID],
|
||||
{result, HTTPResult} ->
|
||||
HTTPResult
|
||||
end;
|
||||
process(_Path, _Request) ->
|
||||
?DEBUG("matrix 404: ~p~n~p~n", [_Path, _Request]),
|
||||
process(Path, Request) ->
|
||||
?DEBUG("matrix 404: ~p~n~p~n", [Path, Request]),
|
||||
ejabberd_web:error(not_found).
|
||||
|
||||
preprocess_federation_request(Request) ->
|
||||
@@ -673,7 +673,7 @@ get_pruned_event_id(PrunedEvent) ->
|
||||
|
||||
encode_canonical_json(JSON) ->
|
||||
JSON2 = sort_json(JSON),
|
||||
misc:json_encode(JSON2).
|
||||
misc:json_encode_with_kv_lists(JSON2).
|
||||
|
||||
sort_json(#{} = Map) ->
|
||||
Map2 = maps:map(fun(_K, V) ->
|
||||
@@ -869,17 +869,17 @@ mod_opt_type(key) ->
|
||||
crypto:generate_key(eddsa, ed25519, Key2)
|
||||
end;
|
||||
mod_opt_type(matrix_id_as_jid) ->
|
||||
econf:bool();
|
||||
mod_opt_type(persist) ->
|
||||
econf:bool().
|
||||
|
||||
-spec mod_options(binary()) -> [{key, {binary(), binary()}} |
|
||||
{atom(), any()}].
|
||||
|
||||
mod_options(Host) ->
|
||||
[{matrix_domain, Host},
|
||||
{host, <<"matrix.", Host/binary>>},
|
||||
{key_name, <<"">>},
|
||||
{key, {<<"">>, <<"">>}},
|
||||
{matrix_id_as_jid, false},
|
||||
{persist, false}].
|
||||
{matrix_id_as_jid, false}].
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
@@ -924,10 +924,10 @@ mod_doc() ->
|
||||
{matrix_id_as_jid,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("If set to 'false', all packets failing to be delivered via an XMPP "
|
||||
?T("If set to 'true', all packets failing to be delivered via an XMPP "
|
||||
"server-to-server connection will then be routed to the Matrix gateway "
|
||||
"by translating a Jabber ID 'user@matrixdomain.tld' to a Matrix user "
|
||||
"identifier '@user:matrixdomain.tld'. When set to 'true', messages "
|
||||
"identifier '@user:matrixdomain.tld'. When set to 'false', messages "
|
||||
"must be explicitly sent to the matrix gateway service Jabber ID to be "
|
||||
"routed to a remote Matrix server. In this case, to send a message to "
|
||||
"Matrix user '@user:matrixdomain.tld', the client must send a message "
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
-export([key_name/1]).
|
||||
-export([matrix_domain/1]).
|
||||
-export([matrix_id_as_jid/1]).
|
||||
-export([persist/1]).
|
||||
|
||||
-spec host(gen_mod:opts() | global | binary()) -> binary().
|
||||
host(Opts) when is_map(Opts) ->
|
||||
@@ -16,7 +15,7 @@ host(Opts) when is_map(Opts) ->
|
||||
host(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, host).
|
||||
|
||||
-spec key(gen_mod:opts() | global | binary()) -> any().
|
||||
-spec key(gen_mod:opts() | global | binary()) -> {binary(),binary()}.
|
||||
key(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(key, Opts);
|
||||
key(Host) ->
|
||||
@@ -40,9 +39,3 @@ matrix_id_as_jid(Opts) when is_map(Opts) ->
|
||||
matrix_id_as_jid(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, matrix_id_as_jid).
|
||||
|
||||
-spec persist(gen_mod:opts() | global | binary()) -> boolean().
|
||||
persist(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(persist, Opts);
|
||||
persist(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_matrix_gw, persist).
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
-export([init/1, terminate/3, code_change/4, callback_mode/0]).
|
||||
-export([handle_event/4]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
-export([init/1, terminate/3, code_change/4, callback_mode/0]).
|
||||
-export([handle_event/4]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
|
||||
@@ -142,8 +142,8 @@ mod_opt_type(servers) ->
|
||||
econf:options(
|
||||
#{
|
||||
certfile => econf:pem()
|
||||
}, [{return, map}])
|
||||
)}, [{return, map}]),
|
||||
}, [{return, map}]))},
|
||||
[{return, map}]),
|
||||
[{return, map}]),
|
||||
fun(Servers) ->
|
||||
maps:fold(
|
||||
|
||||
+1
-1
@@ -1582,7 +1582,7 @@ mod_doc() ->
|
||||
desc =>
|
||||
?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, "
|
||||
"overcrowded, presence broadcasts are limited to reduce load, "
|
||||
"traffic and excessive presence \"storm\" received by participants. "
|
||||
"The default value is '1000'.")}},
|
||||
{min_message_interval,
|
||||
|
||||
@@ -620,9 +620,6 @@ web_menu_host(Acc, _Host, Lang) ->
|
||||
%%---------------
|
||||
%% Web Admin Page
|
||||
|
||||
-define(TDTD(L, N),
|
||||
?XE(<<"tr">>, [?XCT(<<"td">>, L), ?XC(<<"td">>, integer_to_binary(N))])).
|
||||
|
||||
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
|
||||
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
|
||||
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
|
||||
@@ -1474,7 +1471,7 @@ get_room_occupants(Pid) ->
|
||||
|
||||
get_room_occupants_number(Room, Host) ->
|
||||
case get_room_pid_validate(Room, Host, <<"name">>, <<"service">>) of
|
||||
{Pid, _, _} when is_pid(Pid )->
|
||||
{Pid, _, _} when is_pid(Pid)->
|
||||
{ok, #{occupants_number := N}} = mod_muc_room:get_info(Pid),
|
||||
N;
|
||||
_ ->
|
||||
@@ -1504,8 +1501,10 @@ send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
|
||||
get_users_to_invite(RoomJid, UsersStrings) ->
|
||||
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
|
||||
RoomJid#jid.lserver),
|
||||
OccupantsJids = [jid:decode(JidString)
|
||||
|| {JidString, _Nick, _} <- OccupantsTuples],
|
||||
OccupantsJids = try [jid:decode(JidString)
|
||||
|| {JidString, _Nick, _} <- OccupantsTuples]
|
||||
catch _:{bad_jid, _} -> throw({error, "Malformed JID of invited user"})
|
||||
end,
|
||||
lists:filtermap(
|
||||
fun(UserString) ->
|
||||
UserJid = jid:decode(UserString),
|
||||
|
||||
+31
-32
@@ -505,12 +505,12 @@ normal_state({route, <<"">>,
|
||||
process_iq_adhoc(From, IQ, StateData);
|
||||
#register{} ->
|
||||
mod_muc:process_iq_register(IQ);
|
||||
#message_moderate{} = Moderate -> % moderate:1
|
||||
process_iq_moderate(From, IQ, Moderate, StateData);
|
||||
#message_moderate{id = Id, reason = Reason} -> % moderate:1
|
||||
process_iq_moderate(From, IQ, Id, Reason, StateData);
|
||||
#fasten_apply_to{id = ModerateId} = ApplyTo ->
|
||||
case xmpp:get_subtag(ApplyTo, #message_moderate{}) of
|
||||
#message_moderate{} = Moderate -> % moderate:0
|
||||
process_iq_moderate(From, IQ, Moderate#message_moderate{id = ModerateId}, StateData);
|
||||
case xmpp:get_subtag(ApplyTo, #message_moderate_21{}) of
|
||||
#message_moderate_21{reason = Reason} -> % moderate:0
|
||||
process_iq_moderate(From, IQ, ModerateId, Reason, StateData);
|
||||
_ ->
|
||||
Txt = ?T("The feature requested is not "
|
||||
"supported by the conference"),
|
||||
@@ -540,7 +540,7 @@ normal_state({route, <<"">>,
|
||||
case NewStateData of
|
||||
stop ->
|
||||
Conf = StateData#state.config,
|
||||
{stop, normal, StateData#state{config = Conf#config{persistent = false}}};
|
||||
{stop, normal, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
|
||||
_ when NewStateData#state.just_created ->
|
||||
close_room_if_temporary_and_empty(NewStateData);
|
||||
_ ->
|
||||
@@ -736,7 +736,7 @@ handle_event({destroy, Reason}, _StateName,
|
||||
[jid:encode(StateData#state.jid), Reason]),
|
||||
add_to_log(room_existence, destroyed, StateData),
|
||||
Conf = StateData#state.config,
|
||||
{stop, shutdown, StateData#state{config = Conf#config{persistent = false}}};
|
||||
{stop, shutdown, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
|
||||
handle_event(destroy, StateName, StateData) ->
|
||||
?INFO_MSG("Destroyed MUC room ~ts",
|
||||
[jid:encode(StateData#state.jid)]),
|
||||
@@ -856,7 +856,7 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName,
|
||||
from = From, sub_els = [#muc_unsubscribe{}]},
|
||||
case process_iq_mucsub(From, IQ, StateData) of
|
||||
{result, _, stop} ->
|
||||
{stop, normal, StateData#state{config = Conf#config{persistent = false}}};
|
||||
{stop, normal, StateData#state{config = Conf#config{persistent = {destroying, Conf#config.persistent}}}};
|
||||
{result, _, NewState} ->
|
||||
{reply, ok, StateName, NewState};
|
||||
{ignore, NewState} ->
|
||||
@@ -1015,8 +1015,10 @@ terminate(Reason, _StateName,
|
||||
_ ->
|
||||
add_to_log(room_existence, stopped, StateData),
|
||||
case (StateData#state.config)#config.persistent of
|
||||
false ->
|
||||
ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host]);
|
||||
false ->
|
||||
ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host, false]);
|
||||
{destroying, Persistent} ->
|
||||
ejabberd_hooks:run(room_destroyed, LServer, [LServer, Room, Host, Persistent]);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
@@ -4576,8 +4578,14 @@ iq_disco_info_extras(Lang, StateData, Static) ->
|
||||
false ->
|
||||
Fs3
|
||||
end,
|
||||
Fs5 = case (StateData#state.config)#config.vcard_xupdate of
|
||||
Hash when is_binary(Hash) ->
|
||||
[{avatarhash, [Hash]} | Fs4];
|
||||
_ ->
|
||||
Fs4
|
||||
end,
|
||||
#xdata{type = result,
|
||||
fields = muc_roominfo:encode(Fs4, Lang)}.
|
||||
fields = muc_roominfo:encode(Fs5, Lang)}.
|
||||
|
||||
-spec process_iq_disco_items(jid(), iq(), state()) ->
|
||||
{error, stanza_error()} | {result, disco_items()}.
|
||||
@@ -5162,13 +5170,12 @@ add_presence_hats(JID, Pres, StateData) ->
|
||||
Pres
|
||||
end.
|
||||
|
||||
-spec process_iq_moderate(jid(), iq(), message_moderate(), state()) ->
|
||||
-spec process_iq_moderate(jid(), iq(), binary(), binary() | undefined, state()) ->
|
||||
{result, undefined, state()} |
|
||||
{error, stanza_error()}.
|
||||
process_iq_moderate(_From, #iq{type = get}, _Moderate, _StateData) ->
|
||||
process_iq_moderate(_From, #iq{type = get}, _Id, _Reason, _StateData) ->
|
||||
{error, xmpp:err_bad_request()};
|
||||
process_iq_moderate(From, #iq{type = set, lang = Lang},
|
||||
#message_moderate{id = Id, reason = Reason, xmlns = Xmlns},
|
||||
process_iq_moderate(From, #iq{type = set, lang = Lang}, Id, Reason,
|
||||
#state{config = Config, room = Room, host = Host,
|
||||
jid = JID, server_host = Server} = StateData) ->
|
||||
FAffiliation = get_affiliation(From, StateData),
|
||||
@@ -5189,25 +5196,17 @@ process_iq_moderate(From, #iq{type = set, lang = Lang},
|
||||
ok
|
||||
end,
|
||||
By = jid:replace_resource(JID, find_nick_by_jid(From, StateData)),
|
||||
SubEl = case Xmlns of
|
||||
?NS_MESSAGE_MODERATE_0 ->
|
||||
SubEls = [#xmlel{name = <<"reason">>,
|
||||
attrs = [],
|
||||
children = [{xmlcdata, Reason}]},
|
||||
#message_retract{id = Id}],
|
||||
ModeratedEl = #message_moderated{by = By,
|
||||
sub_els = SubEls},
|
||||
#fasten_apply_to{id = Id,
|
||||
sub_els = [ModeratedEl]};
|
||||
?NS_MESSAGE_MODERATE_1 ->
|
||||
ModeratedEl = #message_moderated{by = By},
|
||||
#message_retract{id = Id,
|
||||
reason = Reason,
|
||||
moderated = ModeratedEl}
|
||||
end,
|
||||
Mod21 = #message_moderated_21{by = By,
|
||||
reason = Reason,
|
||||
sub_els = [#message_retract_30{}]},
|
||||
SubEl = [#fasten_apply_to{id = Id,
|
||||
sub_els = [Mod21]},
|
||||
#message_retract{id = Id,
|
||||
reason = Reason,
|
||||
moderated = #message_moderated{by = By}}],
|
||||
Packet0 = #message{type = groupchat,
|
||||
from = From,
|
||||
sub_els = [SubEl]},
|
||||
sub_els = SubEl},
|
||||
{FromNick, _Role} = get_participant_data(From, StateData),
|
||||
Packet = ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
|
||||
@@ -46,7 +46,7 @@ start(Host, _Opts) ->
|
||||
gen_server:start({local, gen_mod:get_module_proc(Host, ?MODULE)}, ?MODULE, [Host], []).
|
||||
|
||||
stop(Host) ->
|
||||
gen_server:stop({local, gen_mod:get_module_proc(Host, ?MODULE)}).
|
||||
gen_server:stop(gen_mod:get_module_proc(Host, ?MODULE)).
|
||||
|
||||
init([Host]) ->
|
||||
ejabberd_mnesia:create(?MODULE, muc_rtbl,
|
||||
|
||||
+5
-3
@@ -1002,7 +1002,9 @@ get_queue_length(LUser, LServer) ->
|
||||
count_offline_messages(LUser, LServer).
|
||||
|
||||
webadmin_user(Acc, User, Server, R) ->
|
||||
Acc ++ [make_command(get_offline_count, R, [{<<"user">>, User}, {<<"host">>, Server}], [])].
|
||||
Acc ++ [make_command(get_offline_count, R, [{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[{result_links, [{value, arg_host, 4, <<"user/", User/binary, "/queue/">>}]}]
|
||||
)].
|
||||
|
||||
%%%
|
||||
%%%
|
||||
@@ -1225,7 +1227,7 @@ mod_doc() ->
|
||||
{bounce_groupchat,
|
||||
#{value => "true | false",
|
||||
desc =>
|
||||
?T("This option is use the disable an optimisation that "
|
||||
?T("This option is use the disable an optimization that "
|
||||
"avoids bouncing error messages when groupchat messages "
|
||||
"could not be stored as offline. It will reduce chat "
|
||||
"room load, without any drawback in standard use cases. "
|
||||
@@ -1235,7 +1237,7 @@ mod_doc() ->
|
||||
"but the bounce is much more likely to happen in the context "
|
||||
"of MucSub, so it is even more important to have it on "
|
||||
"large MucSub services. The default value is 'false', meaning "
|
||||
"the optimisation is enabled.")}},
|
||||
"the optimization is enabled.")}},
|
||||
{db_type,
|
||||
#{value => "mnesia | sql",
|
||||
desc =>
|
||||
|
||||
@@ -217,7 +217,7 @@ need_transform(_) ->
|
||||
|
||||
transform({offline_msg, {U, S}, Timestamp, Expire, From, To, _, Packet}) ->
|
||||
#offline_msg{us = {U, S}, timestamp = Timestamp, expire = Expire,
|
||||
from = From ,to = To, packet = Packet};
|
||||
from = From, to = To, packet = Packet};
|
||||
transform(#offline_msg{us = {U, S}, from = From, to = To,
|
||||
packet = El} = R) ->
|
||||
R#offline_msg{us = {iolist_to_binary(U), iolist_to_binary(S)},
|
||||
|
||||
@@ -127,8 +127,8 @@ remove_old_messages(Days, LServer) ->
|
||||
of
|
||||
{updated, N} ->
|
||||
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
|
||||
_Error ->
|
||||
?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error])
|
||||
Error ->
|
||||
?ERROR_MSG("Cannot delete message in offline spool: ~p", [Error])
|
||||
end,
|
||||
{atomic, ok}.
|
||||
|
||||
|
||||
@@ -40,8 +40,14 @@
|
||||
import_start/2, import_stop/2, import/5, import_info/0,
|
||||
mod_opt_type/1, mod_options/1, depends/2]).
|
||||
|
||||
-export([webadmin_menu_hostuser/4, webadmin_page_hostuser/4]).
|
||||
|
||||
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
@@ -78,6 +84,8 @@ start(Host, Opts) ->
|
||||
{hook, user_send_packet, user_send_packet, 50},
|
||||
{hook, privacy_check_packet, check_packet, 50},
|
||||
{hook, remove_user, remove_user, 50},
|
||||
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
|
||||
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
|
||||
{iq_handler, ejabberd_sm, ?NS_PRIVACY, process_iq}]}.
|
||||
|
||||
stop(_Host) ->
|
||||
@@ -840,6 +848,24 @@ export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
||||
%%%
|
||||
%%% WebAdmin
|
||||
%%%
|
||||
|
||||
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
|
||||
Acc ++ [{<<"privacy">>, <<"Privacy Lists">>}].
|
||||
|
||||
webadmin_page_hostuser(_, Host, User,
|
||||
#request{us = _US, path = [<<"privacy">>]} = R) ->
|
||||
Res = ?H1GL(<<"Privacy Lists">>, <<"modules/#mod_privacy">>, <<"mod_privacy">>)
|
||||
++ [make_command(privacy_set, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Res};
|
||||
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
|
||||
|
||||
%%%
|
||||
%%% Documentation
|
||||
%%%
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
|
||||
@@ -41,10 +41,16 @@
|
||||
|
||||
-export([get_commands_spec/0, bookmarks_to_pep/2]).
|
||||
|
||||
-export([webadmin_menu_hostuser/4, webadmin_page_hostuser/4]).
|
||||
|
||||
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("mod_private.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("ejabberd_web_admin.hrl").
|
||||
-include("translate.hrl").
|
||||
-include("pubsub.hrl").
|
||||
|
||||
@@ -71,6 +77,8 @@ start(Host, Opts) ->
|
||||
{hook, pubsub_publish_item, pubsub_publish_item, 50},
|
||||
{hook, pubsub_delete_item, pubsub_delete_item, 50},
|
||||
{hook, pubsub_tree_call, pubsub_tree_call, 50},
|
||||
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
|
||||
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
|
||||
{iq_handler, ejabberd_sm, ?NS_PRIVATE, process_sm_iq}]}.
|
||||
|
||||
stop(Host) ->
|
||||
@@ -544,6 +552,21 @@ bookmarks_to_pep(User, Server) ->
|
||||
{error, <<"Cannot retrieve bookmarks from private XML storage">>}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% WebAdmin
|
||||
%%%===================================================================
|
||||
|
||||
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
|
||||
Acc ++ [{<<"private">>, <<"Private XML Storage">>}].
|
||||
|
||||
webadmin_page_hostuser(_, Host, User,
|
||||
#request{path = [<<"private">>]} = R) ->
|
||||
Res = ?H1GL(<<"Private XML Storage">>, <<"modules/#mod_private">>, <<"mod_private">>)
|
||||
++ [make_command(private_set, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
|
||||
make_command(private_get, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
|
||||
{stop, Res};
|
||||
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Cache
|
||||
%%%===================================================================
|
||||
|
||||
+188
-9
@@ -25,7 +25,7 @@
|
||||
|
||||
-author('amuhar3@gmail.com').
|
||||
|
||||
-protocol({xep, 356, '0.2.1', '16.09', "", ""}).
|
||||
-protocol({xep, 356, '0.4.1', '24.10', "", ""}).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(gen_mod).
|
||||
@@ -37,6 +37,7 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([component_connected/1, component_disconnected/2,
|
||||
component_send_packet/1,
|
||||
roster_access/2, process_message/1,
|
||||
process_presence_out/1, process_presence_in/1]).
|
||||
|
||||
@@ -45,14 +46,17 @@
|
||||
-include("translate.hrl").
|
||||
|
||||
-type roster_permission() :: both | get | set.
|
||||
-type iq_permission() :: both | get | set.
|
||||
-type presence_permission() :: managed_entity | roster.
|
||||
-type message_permission() :: outgoing.
|
||||
-type roster_permissions() :: [{roster_permission(), acl:acl()}].
|
||||
-type iq_permissions() :: [{iq_permission(), acl:acl()}].
|
||||
-type presence_permissions() :: [{presence_permission(), acl:acl()}].
|
||||
-type message_permissions() :: [{message_permission(), acl:acl()}].
|
||||
-type access() :: [{roster, roster_permissions()} |
|
||||
{presence, presence_permissions()} |
|
||||
{message, message_permissions()}].
|
||||
-type access() :: [{roster, roster_permission()} |
|
||||
{iq, [privilege_namespace()]} |
|
||||
{presence, presence_permission()} |
|
||||
{message, message_permission()}].
|
||||
-type permissions() :: #{binary() => access()}.
|
||||
-record(state, {server_host = <<"">> :: binary()}).
|
||||
|
||||
@@ -71,6 +75,10 @@ reload(_Host, _NewOpts, _OldOpts) ->
|
||||
mod_opt_type(roster) ->
|
||||
econf:options(
|
||||
#{both => econf:acl(), get => econf:acl(), set => econf:acl()});
|
||||
mod_opt_type(iq) ->
|
||||
econf:map(
|
||||
econf:binary(),
|
||||
econf:options(#{both => econf:acl(), get => econf:acl(), set => econf:acl()}));
|
||||
mod_opt_type(message) ->
|
||||
econf:options(
|
||||
#{outgoing => econf:acl()});
|
||||
@@ -80,6 +88,7 @@ mod_opt_type(presence) ->
|
||||
|
||||
mod_options(_) ->
|
||||
[{roster, [{both, none}, {get, none}, {set, none}]},
|
||||
{iq, []},
|
||||
{presence, [{managed_entity, none}, {roster, none}]},
|
||||
{message, [{outgoing,none}]}].
|
||||
|
||||
@@ -108,6 +117,7 @@ mod_doc() ->
|
||||
"carefully, only if you trust a component."), "",
|
||||
?T("NOTE: This module is complementary to _`mod_delegation`_, "
|
||||
"but can also be used separately.")],
|
||||
note => "improved in 24.10",
|
||||
opts =>
|
||||
[{roster,
|
||||
#{value => ?T("Options"),
|
||||
@@ -130,6 +140,27 @@ mod_doc() ->
|
||||
desc =>
|
||||
?T("Sets write access to a user's roster. "
|
||||
"The default value is 'none'.")}}]},
|
||||
{iq,
|
||||
#{value => "{Namespace: Options}",
|
||||
desc =>
|
||||
?T("This option defines namespaces and their IQ permissions. "
|
||||
"By default no permissions are given. "
|
||||
"The 'Options' are:")},
|
||||
[{both,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
?T("Allows sending IQ stanzas of type 'get' and 'set'. "
|
||||
"The default value is 'none'.")}},
|
||||
{get,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
?T("Allows sending IQ stanzas of type 'get'. "
|
||||
"The default value is 'none'.")}},
|
||||
{set,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
?T("Allows sending IQ stanzas of type 'set'. "
|
||||
"The default value is 'none'.")}}]},
|
||||
{message,
|
||||
#{value => ?T("Options"),
|
||||
desc =>
|
||||
@@ -164,6 +195,9 @@ mod_doc() ->
|
||||
example =>
|
||||
["modules:",
|
||||
" mod_privilege:",
|
||||
" iq:",
|
||||
" http://jabber.org/protocol/pubsub:",
|
||||
" get: all",
|
||||
" roster:",
|
||||
" get: all",
|
||||
" presence:",
|
||||
@@ -190,6 +224,10 @@ component_disconnected(Host, _Reason) ->
|
||||
gen_server:cast(Proc, {component_disconnected, Host})
|
||||
end, ejabberd_option:hosts()).
|
||||
|
||||
%%
|
||||
%% Message processing
|
||||
%%
|
||||
|
||||
-spec process_message(stanza()) -> stop | ok.
|
||||
process_message(#message{from = #jid{luser = <<"">>, lresource = <<"">>} = From,
|
||||
to = #jid{lresource = <<"">>} = To,
|
||||
@@ -212,9 +250,73 @@ process_message(#message{from = #jid{luser = <<"">>, lresource = <<"">>} = From,
|
||||
%% Component is disconnected
|
||||
ok
|
||||
end;
|
||||
|
||||
process_message(_Stanza) ->
|
||||
ok.
|
||||
|
||||
%%
|
||||
%% IQ processing
|
||||
%%
|
||||
|
||||
%% @format-begin
|
||||
|
||||
component_send_packet({#iq{from = From,
|
||||
to = #jid{lresource = <<"">>} = To,
|
||||
id = Id,
|
||||
type = Type} =
|
||||
IQ,
|
||||
State})
|
||||
when Type /= error ->
|
||||
Host = From#jid.lserver,
|
||||
ServerHost = To#jid.lserver,
|
||||
Permissions = get_permissions(ServerHost),
|
||||
Result =
|
||||
case {maps:find(Host, Permissions), get_iq_encapsulated_details(IQ)} of
|
||||
{{ok, Access}, {ok, EncapType, EncapNs, EncapFrom, EncIq}}
|
||||
when (EncapType == Type) and ((EncapFrom == undefined) or (EncapFrom == To)) ->
|
||||
NsPermissions = proplists:get_value(iq, Access, none),
|
||||
Permission =
|
||||
case lists:keyfind(EncapNs, 2, NsPermissions) of
|
||||
#privilege_namespace{type = AllowedType} ->
|
||||
AllowedType;
|
||||
_ ->
|
||||
none
|
||||
end,
|
||||
case Permission == both
|
||||
orelse Permission == get andalso Type == get
|
||||
orelse Permission == set andalso Type == set
|
||||
of
|
||||
true ->
|
||||
forward_iq(Host, To, Id, EncIq);
|
||||
false ->
|
||||
?INFO_MSG("IQ not forwarded: Permission not granted to ns=~s with type=~p",
|
||||
[EncapNs, Type]),
|
||||
drop
|
||||
end;
|
||||
{error, _} ->
|
||||
%% Component is disconnected
|
||||
?INFO_MSG("IQ not forwarded: Component seems disconnected", []),
|
||||
drop;
|
||||
{_, {ok, E, _, _, _}} when E /= Type ->
|
||||
?INFO_MSG("IQ not forwarded: The encapsulated IQ stanza type=~p "
|
||||
"does not match the top-level IQ stanza type=~p",
|
||||
[E, Type]),
|
||||
drop;
|
||||
{_, {ok, _, _, EF, _}} when (EF /= undefined) and (EF /= To) ->
|
||||
?INFO_MSG("IQ not forwarded: The FROM attribute in the encapsulated "
|
||||
"IQ stanza and the TO in top-level IQ stanza do not match",
|
||||
[]),
|
||||
drop
|
||||
end,
|
||||
{Result, State};
|
||||
component_send_packet(Acc) ->
|
||||
Acc.
|
||||
%% @format-end
|
||||
|
||||
%%
|
||||
%% Roster processing
|
||||
%%
|
||||
|
||||
-spec roster_access({true, iq()} | false, iq()) -> {true, iq()} | false.
|
||||
roster_access({true, _IQ} = Acc, _) ->
|
||||
Acc;
|
||||
@@ -309,6 +411,8 @@ init([Host|_]) ->
|
||||
process_presence_out, 50),
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
||||
process_presence_in, 50),
|
||||
ejabberd_hooks:add(component_send_packet, ?MODULE,
|
||||
component_send_packet, 50),
|
||||
{ok, #state{server_host = Host}}.
|
||||
|
||||
handle_call(Request, From, State) ->
|
||||
@@ -320,22 +424,26 @@ handle_cast({component_connected, Host}, State) ->
|
||||
From = jid:make(ServerHost),
|
||||
To = jid:make(Host),
|
||||
RosterPerm = get_roster_permission(ServerHost, Host),
|
||||
IqNamespaces = get_iq_namespaces(ServerHost, Host),
|
||||
PresencePerm = get_presence_permission(ServerHost, Host),
|
||||
MessagePerm = get_message_permission(ServerHost, Host),
|
||||
if RosterPerm /= none; PresencePerm /= none; MessagePerm /= none ->
|
||||
if RosterPerm /= none; IqNamespaces /= []; PresencePerm /= none; MessagePerm /= none ->
|
||||
Priv = #privilege{perms = [#privilege_perm{access = message,
|
||||
type = MessagePerm},
|
||||
#privilege_perm{access = roster,
|
||||
type = RosterPerm},
|
||||
#privilege_perm{access = iq,
|
||||
namespaces = IqNamespaces},
|
||||
#privilege_perm{access = presence,
|
||||
type = PresencePerm}]},
|
||||
?INFO_MSG("Granting permissions to external "
|
||||
"component '~ts': roster = ~ts, presence = ~ts, "
|
||||
"message = ~ts",
|
||||
[Host, RosterPerm, PresencePerm, MessagePerm]),
|
||||
"message = ~ts,~n iq = ~p",
|
||||
[Host, RosterPerm, PresencePerm, MessagePerm, IqNamespaces]),
|
||||
Msg = #message{from = From, to = To, sub_els = [Priv]},
|
||||
ejabberd_router:route(Msg),
|
||||
Permissions = maps:put(Host, [{roster, RosterPerm},
|
||||
{iq, IqNamespaces},
|
||||
{presence, PresencePerm},
|
||||
{message, MessagePerm}],
|
||||
get_permissions(ServerHost)),
|
||||
@@ -366,6 +474,8 @@ terminate(_Reason, State) ->
|
||||
Host = State#state.server_host,
|
||||
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
|
||||
false ->
|
||||
ejabberd_hooks:delete(component_send_packet, ?MODULE,
|
||||
component_send_packet, 50),
|
||||
ejabberd_hooks:delete(component_connected, ?MODULE,
|
||||
component_connected, 50),
|
||||
ejabberd_hooks:delete(component_disconnected, ?MODULE,
|
||||
@@ -391,10 +501,15 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%%===================================================================
|
||||
-spec get_permissions(binary()) -> permissions().
|
||||
get_permissions(ServerHost) ->
|
||||
try ets:lookup_element(?MODULE, ServerHost, 2)
|
||||
catch _:badarg -> #{}
|
||||
case ets:lookup(?MODULE, ServerHost) of
|
||||
[] -> #{};
|
||||
[{_, Permissions}] -> Permissions
|
||||
end.
|
||||
|
||||
%%
|
||||
%% Message
|
||||
%%
|
||||
|
||||
-spec forward_message(message()) -> ok.
|
||||
forward_message(#message{to = To} = Msg) ->
|
||||
ServerHost = To#jid.lserver,
|
||||
@@ -435,6 +550,45 @@ forward_message(#message{to = To} = Msg) ->
|
||||
ejabberd_router:route_error(Msg, Err)
|
||||
end.
|
||||
|
||||
%%
|
||||
%% IQ
|
||||
%%
|
||||
|
||||
%% @format-begin
|
||||
|
||||
-spec get_iq_encapsulated_details(iq()) ->
|
||||
{ok, set | get, binary(), jid(), iq()} |
|
||||
{error, Why :: atom(), any(), iq()}.
|
||||
get_iq_encapsulated_details(#iq{sub_els = [IqSub]} = Msg) ->
|
||||
Lang = xmpp:get_lang(Msg),
|
||||
try xmpp:try_subtag(Msg, #privileged_iq{}) of
|
||||
#privileged_iq{iq = #iq{type = EncapsulatedType, from = From} = EncIq} ->
|
||||
[IqSubSub] = xmpp:get_els(IqSub),
|
||||
[Element] = xmpp:get_els(IqSubSub),
|
||||
Ns = xmpp:get_ns(Element),
|
||||
{ok, EncapsulatedType, Ns, From, EncIq};
|
||||
_ ->
|
||||
Txt = ?T("No <privileged_iq/> element found"),
|
||||
Err = xmpp:err_bad_request(Txt, Lang),
|
||||
{error, no_privileged_iq, Err}
|
||||
catch
|
||||
_:{xmpp_codec, Why} ->
|
||||
Txt = xmpp:io_format_error(Why),
|
||||
Err = xmpp:err_bad_request(Txt, Lang),
|
||||
{error, codec_error, Err}
|
||||
end.
|
||||
|
||||
-spec forward_iq(binary(), jid(), binary(), iq()) -> iq().
|
||||
forward_iq(Host, ToplevelTo, Id, Iq) ->
|
||||
FromJID = ToplevelTo,
|
||||
NewIq0 = Iq#iq{from = FromJID},
|
||||
xmpp:put_meta(NewIq0, privilege_iq, {Id, Host, FromJID}).
|
||||
%% @format-end
|
||||
|
||||
%%
|
||||
%% Permissions
|
||||
%%
|
||||
|
||||
-spec get_roster_permission(binary(), binary()) -> roster_permission() | none.
|
||||
get_roster_permission(ServerHost, Host) ->
|
||||
Perms = mod_privilege_opt:roster(ServerHost),
|
||||
@@ -451,6 +605,26 @@ get_roster_permission(ServerHost, Host) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_iq_namespaces(binary(), binary()) -> [privilege_namespace()].
|
||||
get_iq_namespaces(ServerHost, Host) ->
|
||||
NsPerms = mod_privilege_opt:iq(ServerHost),
|
||||
[#privilege_namespace{ns = Ns, type = get_iq_permission(ServerHost, Host, Perms)} || {Ns, Perms} <- NsPerms].
|
||||
|
||||
-spec get_iq_permission(binary(), binary(), [iq_permission()]) -> iq_permission() | none.
|
||||
get_iq_permission(ServerHost, Host, Perms) ->
|
||||
case match_rule(ServerHost, Host, Perms, both) of
|
||||
allow ->
|
||||
both;
|
||||
deny ->
|
||||
Get = match_rule(ServerHost, Host, Perms, get),
|
||||
Set = match_rule(ServerHost, Host, Perms, set),
|
||||
if Get == allow, Set == allow -> both;
|
||||
Get == allow -> get;
|
||||
Set == allow -> set;
|
||||
true -> none
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_message_permission(binary(), binary()) -> message_permission() | none.
|
||||
get_message_permission(ServerHost, Host) ->
|
||||
Perms = mod_privilege_opt:message(ServerHost),
|
||||
@@ -472,7 +646,12 @@ get_presence_permission(ServerHost, Host) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-ifdef(OTP_BELOW_26).
|
||||
-dialyzer({no_contracts, match_rule/4}).
|
||||
-endif.
|
||||
|
||||
-spec match_rule(binary(), binary(), roster_permissions(), roster_permission()) -> allow | deny;
|
||||
(binary(), binary(), iq_permissions(), iq_permission()) -> allow | deny;
|
||||
(binary(), binary(), presence_permissions(), presence_permission()) -> allow | deny;
|
||||
(binary(), binary(), message_permissions(), message_permission()) -> allow | deny.
|
||||
match_rule(ServerHost, Host, Perms, Type) ->
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
|
||||
-module(mod_privilege_opt).
|
||||
|
||||
-export([iq/1]).
|
||||
-export([message/1]).
|
||||
-export([presence/1]).
|
||||
-export([roster/1]).
|
||||
|
||||
-spec iq(gen_mod:opts() | global | binary()) -> [{binary(),[{'both',acl:acl()} | {'get',acl:acl()} | {'set',acl:acl()}]}].
|
||||
iq(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(iq, Opts);
|
||||
iq(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_privilege, iq).
|
||||
|
||||
-spec message(gen_mod:opts() | global | binary()) -> [{'outgoing','none' | acl:acl()}].
|
||||
message(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(message, Opts);
|
||||
|
||||
+13
-19
@@ -265,10 +265,7 @@ init([ServerHost|_]) ->
|
||||
ejabberd_router:register_route(
|
||||
Host, ServerHost, {apply, ?MODULE, route}),
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
DefaultModule = plugin(Host, hd(Plugins)),
|
||||
DefaultNodeCfg = merge_config(
|
||||
[mod_pubsub_opt:default_node_config(Opts),
|
||||
DefaultModule:options()]),
|
||||
DefaultNodeCfg = mod_pubsub_opt:default_node_config(Opts),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
T = gen_mod:get_module_proc(H, config),
|
||||
@@ -3029,7 +3026,8 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
|
||||
end,
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_to(StanzaToSend, jid:make(To)))
|
||||
xmpp:set_to(xmpp:put_meta(StanzaToSend, ignore_sm_bounce, true),
|
||||
jid:make(To)))
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID).
|
||||
|
||||
@@ -3051,8 +3049,7 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
|
||||
extended_headers([Publisher])),
|
||||
Pred = fun(To) -> delivery_permitted(Owner, To, NodeOptions) end,
|
||||
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
|
||||
{pep_message, <<((Node))/binary, "+notify">>, Stanza, Pred}),
|
||||
ejabberd_router:route(xmpp:set_to(Stanza, jid:make(LUser, LServer)));
|
||||
{pep_message, <<((Node))/binary, "+notify">>, Stanza, Pred});
|
||||
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
|
||||
|
||||
@@ -3369,22 +3366,19 @@ get_option(Options, Var, Def) ->
|
||||
|
||||
-spec node_options(host(), binary()) -> [{atom(), any()}].
|
||||
node_options(Host, Type) ->
|
||||
DefaultOpts = node_plugin_options(Host, Type),
|
||||
case lists:member(Type, config(Host, plugins)) of
|
||||
true ->
|
||||
config(Host, default_node_config, DefaultOpts);
|
||||
false -> DefaultOpts
|
||||
end.
|
||||
ConfigOpts = config(Host, default_node_config),
|
||||
PluginOpts = node_plugin_options(Host, Type),
|
||||
merge_config([ConfigOpts, PluginOpts]).
|
||||
|
||||
-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
|
||||
node_plugin_options(Host, Type) ->
|
||||
Module = plugin(Host, Type),
|
||||
case catch Module:options() of
|
||||
{'EXIT', {undef, _}} ->
|
||||
case {lists:member(Type, config(Host, plugins)), catch Module:options()} of
|
||||
{true, Opts} when is_list(Opts) ->
|
||||
Opts;
|
||||
{_, _} ->
|
||||
DefaultModule = plugin(Host, ?STDNODE),
|
||||
DefaultModule:options();
|
||||
Result ->
|
||||
Result
|
||||
DefaultModule:options()
|
||||
end.
|
||||
|
||||
-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
|
||||
@@ -4200,7 +4194,7 @@ delete_old_items(N) ->
|
||||
fun(#pubsub_node{id = Nidx, type = Type}) ->
|
||||
case node_action(Host, Type,
|
||||
remove_extra_items,
|
||||
[Nidx , N]) of
|
||||
[Nidx, N]) of
|
||||
{result, _} ->
|
||||
ok;
|
||||
{error, _} ->
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ mod_doc() ->
|
||||
"\"app servers\" operated by third-party vendors of "
|
||||
"mobile apps. Those app servers will usually trigger "
|
||||
"notification delivery to the user's mobile device using "
|
||||
"platform-dependant backend services such as FCM or APNS."),
|
||||
"platform-dependent backend services such as FCM or APNS."),
|
||||
opts =>
|
||||
[{notify_on,
|
||||
#{value => "messages | all",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push_sql.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Purpose :
|
||||
%%% Created : 26 Oct 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
+26
-8
@@ -412,6 +412,12 @@ send_welcome_message(JID) ->
|
||||
case mod_register_opt:welcome_message(Host) of
|
||||
{<<"">>, <<"">>} -> ok;
|
||||
{Subj, Body} ->
|
||||
ejabberd_router:route(
|
||||
#message{from = jid:make(Host),
|
||||
to = JID,
|
||||
type = chat,
|
||||
subject = xmpp:mk_text(Subj),
|
||||
body = xmpp:mk_text(<<Subj/binary, "\n\n", Body/binary>>)}),
|
||||
ejabberd_router:route(
|
||||
#message{from = jid:make(Host),
|
||||
to = JID,
|
||||
@@ -567,24 +573,24 @@ may_remove_resource(From) -> From.
|
||||
get_ip_access(Host) ->
|
||||
mod_register_opt:ip_access(Host).
|
||||
|
||||
check_ip_access({User, Server, Resource}, IPAccess) ->
|
||||
check_ip_access(Server, {User, Server, Resource}, IPAccess) ->
|
||||
case ejabberd_sm:get_user_ip(User, Server, Resource) of
|
||||
{IPAddress, _PortNumber} ->
|
||||
check_ip_access(IPAddress, IPAccess);
|
||||
check_ip_access(Server, IPAddress, IPAccess);
|
||||
_ ->
|
||||
deny
|
||||
end;
|
||||
check_ip_access(undefined, _IPAccess) ->
|
||||
check_ip_access(_Server, undefined, _IPAccess) ->
|
||||
deny;
|
||||
check_ip_access(IPAddress, IPAccess) ->
|
||||
acl:match_rule(global, IPAccess, IPAddress).
|
||||
check_ip_access(Server, IPAddress, IPAccess) ->
|
||||
acl:match_rule(Server, IPAccess, IPAddress).
|
||||
|
||||
check_access(User, Server, Source) ->
|
||||
JID = jid:make(User, Server),
|
||||
Access = mod_register_opt:access(Server),
|
||||
IPAccess = get_ip_access(Server),
|
||||
case acl:match_rule(Server, Access, JID) of
|
||||
allow -> check_ip_access(Source, IPAccess);
|
||||
allow -> check_ip_access(Server, Source, IPAccess);
|
||||
deny -> deny
|
||||
end.
|
||||
|
||||
@@ -648,7 +654,10 @@ mod_doc() ->
|
||||
?T("Specify rules to restrict what usernames can be registered. "
|
||||
"If a rule returns 'deny' on the requested username, "
|
||||
"registration of that user name is denied. There are no "
|
||||
"restrictions by default.")}},
|
||||
"restrictions by default. "
|
||||
"If 'AccessName' is 'none', then registering new accounts using "
|
||||
"In-Band Registration is disabled and the corresponding "
|
||||
"stream feature is not announced to clients.")}},
|
||||
{access_from,
|
||||
#{value => ?T("AccessName"),
|
||||
desc =>
|
||||
@@ -702,4 +711,13 @@ mod_doc() ->
|
||||
#{value => "{subject: Subject, body: Body}",
|
||||
desc =>
|
||||
?T("Set a welcome message that is sent to each newly registered account. "
|
||||
"The message will have subject 'Subject' and text 'Body'.")}}]}.
|
||||
"The message will have subject 'Subject' and text 'Body'."),
|
||||
example =>
|
||||
["modules:",
|
||||
" mod_register:",
|
||||
" welcome_message:",
|
||||
" subject: \"Welcome!\"",
|
||||
" body: |-",
|
||||
" Hi!",
|
||||
" Welcome to this XMPP server"]}}
|
||||
]}.
|
||||
|
||||
+5
-1
@@ -1096,7 +1096,11 @@ make_webadmin_roster_table(Host, Username, R, RPath) ->
|
||||
|
||||
webadmin_user(Acc, User, Server, R) ->
|
||||
Acc
|
||||
++ [make_command(get_roster_count, R, [{<<"user">>, User}, {<<"host">>, Server}], [])].
|
||||
++ [make_command(get_roster_count,
|
||||
R,
|
||||
[{<<"user">>, User}, {<<"host">>, Server}],
|
||||
[{result_links,
|
||||
[{value, arg_host, 4, <<"user/", User/binary, "/roster/">>}]}])].
|
||||
%%% @format-end
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@@ -340,16 +340,16 @@ update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||
|
||||
raw_to_record(LServer,
|
||||
[User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType]) ->
|
||||
SServer, SSubscribe, SType]) ->
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType});
|
||||
SServer, SSubscribe, SType});
|
||||
raw_to_record(LServer,
|
||||
{User, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType}) ->
|
||||
SServer, SSubscribe, SType}) ->
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType});
|
||||
SServer, SSubscribe, SType});
|
||||
raw_to_record(LServer,
|
||||
{User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
|
||||
_SServer, _SSubscribe, _SType}) ->
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Created : 20 Oct 2024 by Pawel Chmielowski <pawel@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2024 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_s2s_bidi).
|
||||
-behaviour(gen_mod).
|
||||
-protocol({xep, 288, '1.0.1', '24.10', "complete", ""}).
|
||||
|
||||
%% gen_mod API
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_options/1]).
|
||||
-export([mod_doc/0]).
|
||||
%% Hooks
|
||||
-export([s2s_in_packet/2, s2s_out_packet/2,
|
||||
s2s_in_features/2, s2s_in_auth_result/3, s2s_out_unauthenticated_features/2, s2s_in_handle_info/2]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, s2s_in_pre_auth_features, s2s_in_features, 50},
|
||||
{hook, s2s_in_post_auth_features, s2s_in_features, 50},
|
||||
{hook, s2s_in_unauthenticated_packet, s2s_in_packet, 50},
|
||||
{hook, s2s_in_authenticated_packet, s2s_in_packet, 50},
|
||||
{hook, s2s_in_handle_info, s2s_in_handle_info, 50},
|
||||
{hook, s2s_in_auth_result, s2s_in_auth_result, 50},
|
||||
{hook, s2s_out_unauthenticated_features, s2s_out_unauthenticated_features, 50},
|
||||
{hook, s2s_out_packet, s2s_out_packet, 50}]}.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_options(_Host) ->
|
||||
[].
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("The module adds support for "
|
||||
"https://xmpp.org/extensions/xep-0288.html"
|
||||
"[XEP-0288: Bidirectional Server-to-Server Connections] that allows using "
|
||||
"single s2s connection to communicate in both directions.")],
|
||||
note => "added in 24.10",
|
||||
opts => [],
|
||||
example =>
|
||||
["modules:",
|
||||
" mod_s2s_bidi: {}"]}.
|
||||
|
||||
s2s_in_features(Acc, _) ->
|
||||
[#s2s_bidi{}|Acc].
|
||||
|
||||
s2s_in_packet(State, #s2s_bidi{}) ->
|
||||
{stop, State#{bidi_enabled => true}};
|
||||
s2s_in_packet(State, _) ->
|
||||
State.
|
||||
|
||||
s2s_out_unauthenticated_features(#{db_verify := _} = State, _) ->
|
||||
State;
|
||||
s2s_out_unauthenticated_features(State, #stream_features{} = Pkt) ->
|
||||
try xmpp:try_subtag(Pkt, #s2s_bidi{}) of
|
||||
#s2s_bidi{} ->
|
||||
ejabberd_s2s_out:send(State#{bidi_enabled => true}, #s2s_bidi{})
|
||||
catch _:{xmpp_codec, _Why} ->
|
||||
State
|
||||
end.
|
||||
|
||||
s2s_out_packet(#{bidi_enabled := true, ip := {IP, _}} = State, Pkt0)
|
||||
when ?is_stanza(Pkt0) ->
|
||||
To = xmpp:get_to(Pkt0),
|
||||
case check_from_to(State, xmpp:get_from(Pkt0), To) of
|
||||
ok ->
|
||||
Pkt = xmpp:put_meta(Pkt0, ip, IP),
|
||||
LServer = ejabberd_router:host_of_route(To#jid.lserver),
|
||||
State1 = ejabberd_hooks:run_fold(s2s_in_authenticated_packet,
|
||||
LServer, State, [Pkt]),
|
||||
{Pkt1, State2} = ejabberd_hooks:run_fold(s2s_receive_packet, LServer,
|
||||
{Pkt, State1}, []),
|
||||
case Pkt1 of
|
||||
drop -> ok;
|
||||
_ -> ejabberd_router:route(Pkt1)
|
||||
end,
|
||||
{stop, State2};
|
||||
{error, Err} ->
|
||||
{stop, ejabberd_s2s_out:send(State, Err)}
|
||||
end;
|
||||
s2s_out_packet(#{db_verify := _} = State, #stream_features{}) ->
|
||||
State;
|
||||
s2s_out_packet(State, #stream_features{} = Pkt) ->
|
||||
try xmpp:try_subtag(Pkt, #s2s_bidi{}) of
|
||||
#s2s_bidi{} ->
|
||||
ejabberd_s2s_out:send(State#{bidi_enabled => true}, #s2s_bidi{})
|
||||
catch _:{xmpp_codec, _Why} ->
|
||||
State
|
||||
end;
|
||||
s2s_out_packet(State, _Pkt) ->
|
||||
State.
|
||||
|
||||
s2s_in_handle_info(State, {route, Pkt}) when ?is_stanza(Pkt) ->
|
||||
ejabberd_s2s_in:send(State, Pkt);
|
||||
s2s_in_handle_info(State, _Info) ->
|
||||
State.
|
||||
|
||||
check_from_to(#{remote_server := RServer}, #jid{lserver = FromServer},
|
||||
#jid{lserver = ToServer}) ->
|
||||
if
|
||||
RServer /= FromServer -> {error, xmpp:serr_invalid_from()};
|
||||
true ->
|
||||
case ejabberd_router:is_my_route(ToServer) of
|
||||
false -> {error, xmpp:serr_host_unknown()};
|
||||
_ -> ok
|
||||
end
|
||||
end.
|
||||
|
||||
s2s_in_auth_result(#{server := LServer, bidi_enabled := true} = State, true, RServer) ->
|
||||
ejabberd_s2s:register_connection({LServer, RServer}),
|
||||
State;
|
||||
s2s_in_auth_result(State, _, _) ->
|
||||
State.
|
||||
@@ -0,0 +1,121 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Created : 20 Oct 2024 by Pawel Chmielowski <pawel@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2024 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_scram_upgrade).
|
||||
-behaviour(gen_mod).
|
||||
-protocol({xep, 480, '0.1', '24.10', "complete", ""}).
|
||||
|
||||
%% gen_mod API
|
||||
-export([start/2, stop/1, reload/3, depends/2, mod_options/1, mod_opt_type/1]).
|
||||
-export([mod_doc/0]).
|
||||
%% Hooks
|
||||
-export([c2s_inline_features/2, c2s_handle_sasl2_inline/1,
|
||||
c2s_handle_sasl2_task_next/4, c2s_handle_sasl2_task_data/3]).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include_lib("xmpp/include/scram.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start(_Host, _Opts) ->
|
||||
{ok, [{hook, c2s_inline_features, c2s_inline_features, 50},
|
||||
{hook, c2s_handle_sasl2_inline, c2s_handle_sasl2_inline, 10},
|
||||
{hook, c2s_handle_sasl2_task_next, c2s_handle_sasl2_task_next, 10},
|
||||
{hook, c2s_handle_sasl2_task_data, c2s_handle_sasl2_task_data, 10}]}.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
reload(_Host, _NewOpts, _OldOpts) ->
|
||||
ok.
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(offered_upgrades) ->
|
||||
econf:list(econf:enum([sha256, sha512])).
|
||||
|
||||
mod_options(_Host) ->
|
||||
[{offered_upgrades, [sha256, sha512]}].
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("The module adds support for "
|
||||
"https://xmpp.org/extensions/xep-0480.html"
|
||||
"[XEP-0480: SASL Upgrade Tasks] that allows users to upgrade "
|
||||
"passwords to more secure representation.")],
|
||||
note => "added in 24.10",
|
||||
opts => [{offered_upgrades,
|
||||
#{value => "list(sha256, sha512)",
|
||||
desc => ?T("List with upgrade types that should be offered")}}],
|
||||
example =>
|
||||
["modules:",
|
||||
" mod_scram_upgrade:",
|
||||
" offered_upgrades:",
|
||||
" - sha256",
|
||||
" - sha512"]}.
|
||||
|
||||
c2s_inline_features({Sasl, Bind, Extra}, Host) ->
|
||||
Methods = lists:map(
|
||||
fun(sha256) -> #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-256">>};
|
||||
(sha512) -> #sasl_upgrade{cdata = <<"UPGR-SCRAM-SHA-512">>}
|
||||
end, mod_scram_upgrade_opt:offered_upgrades(Host)),
|
||||
{Sasl, Bind, Methods ++ Extra}.
|
||||
|
||||
c2s_handle_sasl2_inline({State, Els, _Results} = Acc) ->
|
||||
case lists:keyfind(sasl_upgrade, 1, Els) of
|
||||
false ->
|
||||
Acc;
|
||||
#sasl_upgrade{cdata = Type} ->
|
||||
{stop, {State, {continue, [Type]}, []}}
|
||||
end.
|
||||
|
||||
c2s_handle_sasl2_task_next({_, State}, Task, _Els, _InlineEls) ->
|
||||
Algo = case Task of
|
||||
<<"UPGR-SCRAM-SHA-256">> -> sha256;
|
||||
<<"UPGR-SCRAM-SHA-512">> -> sha512
|
||||
end,
|
||||
Salt = p1_rand:bytes(16),
|
||||
{task_data, [#scram_upgrade_salt{cdata = Salt, iterations = 4096}],
|
||||
State#{scram_upgrade => {Algo, Salt, 4096}}}.
|
||||
|
||||
c2s_handle_sasl2_task_data({_, #{user := User, server := Server,
|
||||
scram_upgrade := {Algo, Salt, Iter}} = State},
|
||||
Els, InlineEls) ->
|
||||
case xmpp:get_subtag(#sasl2_task_data{sub_els = Els}, #scram_upgrade_hash{}) of
|
||||
#scram_upgrade_hash{data = SaltedPassword} ->
|
||||
StoredKey = scram:stored_key(Algo, scram:client_key(Algo, SaltedPassword)),
|
||||
ServerKey = scram:server_key(Algo, SaltedPassword),
|
||||
ejabberd_auth:set_password(User, Server,
|
||||
#scram{hash = Algo, iterationcount = Iter, salt = Salt,
|
||||
serverkey = ServerKey, storedkey = StoredKey}),
|
||||
State2 = maps:remove(scram_upgrade, State),
|
||||
InlineEls = lists:keydelete(sasl_upgrade, 1, InlineEls),
|
||||
case ejabberd_c2s:handle_sasl2_inline(InlineEls, State2) of
|
||||
{State3, NewEls, Results} ->
|
||||
{success, NewEls, Results, State3}
|
||||
end;
|
||||
_ ->
|
||||
{abort, State}
|
||||
end.
|
||||
@@ -0,0 +1,13 @@
|
||||
%% Generated automatically
|
||||
%% DO NOT EDIT: run `make options` instead
|
||||
|
||||
-module(mod_scram_upgrade_opt).
|
||||
|
||||
-export([offered_upgrades/1]).
|
||||
|
||||
-spec offered_upgrades(gen_mod:opts() | global | binary()) -> ['sha256' | 'sha512'].
|
||||
offered_upgrades(Opts) when is_map(Opts) ->
|
||||
gen_mod:get_opt(offered_upgrades, Opts);
|
||||
offered_upgrades(Host) ->
|
||||
gen_mod:get_module_opt(Host, mod_scram_upgrade, offered_upgrades).
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
-define(GROUP_CACHE, shared_roster_ldap_group_cache).
|
||||
-define(DISPLAYED_CACHE, shared_roster_ldap_displayed_cache).
|
||||
-define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds
|
||||
-define(INVALID_SETTING_MSG, "~ts is not properly set! ~ts will not function.").
|
||||
|
||||
-record(state,
|
||||
{host = <<"">> :: binary(),
|
||||
|
||||
@@ -122,11 +122,12 @@ c2s_stream_features(Acc, Host) ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
c2s_inline_features({Sasl, Bind} = Acc, Host) ->
|
||||
c2s_inline_features({Sasl, Bind, Extra} = Acc, Host) ->
|
||||
case gen_mod:is_loaded(Host, ?MODULE) of
|
||||
true ->
|
||||
{[#feature_sm{xmlns = ?NS_STREAM_MGMT_3} | Sasl],
|
||||
[#bind2_feature{var = ?NS_STREAM_MGMT_3} | Bind]};
|
||||
[#bind2_feature{var = ?NS_STREAM_MGMT_3} | Bind],
|
||||
Extra};
|
||||
false ->
|
||||
Acc
|
||||
end.
|
||||
@@ -950,7 +951,7 @@ mod_doc() ->
|
||||
"https://xmpp.org/extensions/xep-0198.html"
|
||||
"[XEP-0198: Stream Management]. This protocol allows "
|
||||
"active management of an XML stream between two XMPP "
|
||||
"entities, including features for stanza acknowledgements "
|
||||
"entities, including features for stanza acknowledgments "
|
||||
"and stream resumption."),
|
||||
opts =>
|
||||
[{max_ack_queue,
|
||||
@@ -992,7 +993,7 @@ mod_doc() ->
|
||||
{ack_timeout,
|
||||
#{value => "timeout()",
|
||||
desc =>
|
||||
?T("A time to wait for stanza acknowledgements. "
|
||||
?T("A time to wait for stanza acknowledgments. "
|
||||
"Setting it to 'infinity' effectively disables the timeout. "
|
||||
"The default value is '1' minute.")}},
|
||||
{resend_on_timeout,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user