Compare commits

...

288 Commits

Author SHA1 Message Date
Badlop d97366fe74 Installers: Fix yaml syntax
Container / build (amd64, <nil>) (push) Failing after 43s
Installers / binaries (amd64, <nil>) (push) Failing after 46s
CI / compile (27) (push) Has been cancelled
Container / build (arm64, -arm) (push) Has been cancelled
Installers / binaries (arm64, -arm) (push) Has been cancelled
Runtime / rebars (25, rebar) (push) Has been cancelled
Runtime / rebars (25, rebar3) (push) Has been cancelled
Runtime / rebars (26, rebar) (push) Has been cancelled
Runtime / rebars (26, rebar3) (push) Has been cancelled
Runtime / rebars (27, rebar3) (push) Has been cancelled
Runtime / rebars (28, rebar3) (push) Has been cancelled
Runtime / rebar3-elixir (1.14) (push) Has been cancelled
Runtime / rebar3-elixir (1.18) (push) Has been cancelled
Runtime / rebar3-elixir (1.19) (push) Has been cancelled
Runtime / mix (1.14) (push) Has been cancelled
Runtime / mix (1.18) (push) Has been cancelled
Runtime / mix (1.19) (push) Has been cancelled
CI / static (27) (push) Has been cancelled
CI / dynamic (27) (push) Has been cancelled
CI / ct (agnostic, 27, multi) (push) Has been cancelled
CI / ct (extauth, 27, multi) (push) Has been cancelled
CI / ct (ldap, 27, multi) (push) Has been cancelled
CI / ct (mnesia, 27, multi) (push) Has been cancelled
CI / ct (mysql, 27, multi) (push) Has been cancelled
CI / ct (mysql, 27, single) (push) Has been cancelled
CI / ct (pgsql, 27, multi) (push) Has been cancelled
CI / ct (pgsql, 27, single) (push) Has been cancelled
CI / ct (redis, 27, multi) (push) Has been cancelled
CI / ct (sqlite, 27, multi) (push) Has been cancelled
CI / ct (sqlite, 27, single) (push) Has been cancelled
CI / cover (push) Has been cancelled
CI / schema (27) (push) Has been cancelled
Container / merge (push) Has been cancelled
Installers / Release (push) Has been cancelled
2025-10-28 13:56:01 +01:00
Badlop 0258195682 Update mix.lock 2025-10-28 13:52:35 +01:00
Badlop 7805241296 Installers: Fix paths to the downloaded installers 2025-10-28 13:46:31 +01:00
Badlop 33ea1823de Set version to 25.10 2025-10-28 13:39:19 +01:00
Badlop 038444773b Container: Try to push resulting image tags properly 2025-10-28 13:39:14 +01:00
Badlop 85b614e77b CHANGELOG.md: Update to 25.10 2025-10-28 12:02:01 +01:00
Badlop 02aa256c59 Copy recent changes to the container template files 2025-10-28 12:01:57 +01:00
Badlop 21c6cc8fd1 Update man page to 25.10 2025-10-28 10:29:17 +01:00
Badlop 5728db746d Update version notes to 25.10 2025-10-28 10:29:16 +01:00
Badlop 5083217fce Update doap file 2025-10-28 10:29:15 +01:00
dependabot[bot] 527c000441 Bump ex_doc from 0.38.3 to 0.39.1
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.38.3 to 0.39.1.
- [Release notes](https://github.com/elixir-lang/ex_doc/releases)
- [Changelog](https://github.com/elixir-lang/ex_doc/blob/v0.39.1/CHANGELOG.md)
- [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.38.3...v0.39.1)

---
updated-dependencies:
- dependency-name: ex_doc
  dependency-version: 0.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 10:29:13 +01:00
Badlop 48af6467ea mod_announce: Fix API commands descriptions 2025-10-28 10:29:11 +01:00
Badlop b051bcd983 Weekly: No need to test XMPP interop also on the weekly workflow 2025-10-27 12:45:38 +01:00
dependabot[bot] 11a6d3659e Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 12:40:23 +01:00
Badlop 412489d31b Update Spanish and Catalan translations 2025-10-27 12:37:12 +01:00
Badlop b75e08edf7 Update Russian translation (thanks to Yurt Page) 2025-10-27 12:02:29 +01:00
Badlop ff83eb960a Fix text and capitalization of some translatable strings 2025-10-27 11:50:53 +01:00
Badlop 67208fad9d mod_announce: Add API commands, reusing existing ad-hoc functions 2025-10-23 20:51:03 +02:00
Badlop e02864bf6c mod_announce: Add comments for Vim folding to help module reading 2025-10-23 20:51:01 +02:00
Badlop 45abd38d09 mod_muc_admin: Use in WebAdmin the new API commands that get nick registers 2025-10-23 20:51:00 +02:00
Badlop e7cf1d0b87 mod_muc_admin: New API commands muc_get_registered_nick and nicks (#4468) 2025-10-23 20:50:58 +02:00
Badlop b7aa7dc64c mod_muc: New functions to get registered nick/nicks 2025-10-23 20:50:57 +02:00
Badlop d10d7aa8cc WebAdmin: Respect newline and whitespace characters in results 2025-10-23 20:50:55 +02:00
Badlop 6b3333c3ff Runtime: Test with new Elixir 1.19 2025-10-23 20:50:53 +02:00
Badlop 3ba6a8e039 Fix typo introduced in recent commit 5575d4e78 2025-10-23 20:50:50 +02:00
Pawel Chmielowski c27a3a02fe Tag dependences 2025-10-23 13:48:49 +02:00
Pawel Chmielowski ca05bbd652 Don't catch exit:{aborted, _} inside mnesia transactions
This is used by mnesia internally to retry deadlocked transaction,
so we should allow it to propagate.

This could be triggered by calling create_node concurrently from
multiple processes and did lead to {cyclic, ...} errors.
2025-10-22 16:12:58 +02:00
Pawel Chmielowski 357988b662 Check if room is hibernated before calling mod_muc process
This moves fetching hibernation data from mod_muc process
to caller process, and makes us call mod_muc only
if we have something to unhibernate.
2025-10-21 18:37:22 +02:00
Paweł Chmielowski c40df92263 Merge pull request #4471 from pouriya/pass-HTTP-headers-from-WS-to-C2S-connection
ref: pass HTTP headers from WS to C2S
2025-10-21 12:09:42 +02:00
Pouriya cbe30c1fcb ref: pass HTTP headers from WS to C2S connection 2025-10-21 13:13:08 +03:30
Badlop f77b55c073 mod_configure: New ad-hoc commands that were missing from XEP-0133 2025-10-20 13:04:36 +02:00
Badlop f9ab175a79 ejabberd_admin: New API command restart_kindly, improve stop_kindly 2025-10-20 13:04:28 +02:00
Badlop fb0097a6e0 mod_admin_extra: New API commands: list_banned and count_banned 2025-10-20 13:04:25 +02:00
Badlop d247003038 mod_admin_extra: Improve API command status_list: support for status to be a list
This is used by mod_configure for some ad-hoc commands
2025-10-20 13:04:24 +02:00
Badlop 0f2d64c10d mod_adhoc_api: Add support for async command calling 2025-10-20 13:04:22 +02:00
Badlop d49ccbf031 mod_adhoc_api: If argument is a list of jids, type is jid-multi 2025-10-20 13:04:20 +02:00
Badlop 6d5065ae3a mod_adhoc_api: If field has several values, type is text-multi 2025-10-20 13:04:19 +02:00
Badlop 803e6d743a Fix small typo 2025-10-20 13:04:17 +02:00
Badlop 2bf4a5970a CI: Fix updating ubuntu packages 2025-10-20 10:53:47 +02:00
Holger Weiss 9e985e3e79 mod_push: Apply cosmetic changes 2025-10-18 00:47:09 +02:00
Holger Weiss 72661acb43 Merge remote-tracking branch 'processone/pr/4383'
* processone/pr/4383:
  feat: fire new `push_send_notification` hook
2025-10-18 00:43:01 +02:00
badlop eace76b16e Merge pull request #4460 from badlop/arm_runner
Use ARM native runner and parallelization in workflows to reduce perceived run time
2025-10-17 09:30:12 +02:00
badlop ca03a97ca2 Merge pull request #4469 from guusdk/xmpp-interop-testing-v1.7.2
CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
2025-10-16 15:07:17 +02:00
Pawel Chmielowski 95d54f9568 Fix formating 2025-10-14 15:13:34 +02:00
Pawel Chmielowski 62185c8d30 Remove dialyzer warnings 2025-10-14 15:01:34 +02:00
Pawel Chmielowski ef94ee66fd Use mod_private:del_data in unban_account command 2025-10-14 14:10:23 +02:00
Pawel Chmielowski f666cd4643 Fix dialyzer warning 2025-10-14 13:53:15 +02:00
Pawel Chmielowski 12989ba5eb Add del_data/3, get_users_with_data/2, count_users_with_data/2 in mod_private 2025-10-14 11:38:21 +02:00
Guus der Kinderen 63f3b4f7af CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
Updates this GitHub Action that's used to execute XMPP-based interop tests from v1.7.1 to v1.7.2.

This bugfix release introduces some changes that improve the stability of the tests. Highlights include:

- No longer includes tests that verify client, instead of server behavior
- Add prerequisite checks for various tests, to prevent them from failing if the server under test doesn't support the feature
- Remove prerequisite checks for other tests where they needlessly prevent a test from executing
2025-10-13 22:04:06 +02:00
Pawel Chmielowski 7a4ea0eba2 Add config transformer from use_new_schema -> sql_multihost_schema 2025-10-13 11:36:52 +02:00
Badlop ad4876821b Installers: Generate ARM installers in native runner 2025-10-09 13:54:14 +02:00
Badlop cf2b2d5e85 Container: Build ARM in native runner instead of QEMU, merge and clean 2025-10-09 13:54:13 +02:00
Badlop a0c5df0c5b Weekly: New workflow that condenses CI, test all erlang without caching 2025-10-09 13:54:11 +02:00
Badlop ae36405466 CI and Runtime: Reorganize steps to run in parallel, and ARM runner 2025-10-09 13:54:10 +02:00
Badlop e7eb116d93 Add local composite actions to manage ejabberd and databases 2025-10-09 13:54:08 +02:00
Badlop 10a82afd46 Tests: Run agnostic-database tests only once, not for every backend 2025-10-09 13:54:06 +02:00
Badlop bba9563525 Tests: The odbc backend is not actually used in Commont Tests 2025-10-09 13:54:04 +02:00
Badlop 558cabd860 Tests: Fix typo when commenting CT_BACKENDS environment variable 2025-10-09 13:54:02 +02:00
Badlop 2118c3028f mod_admin_update_sql: Use same index name than when creating database 2025-10-09 13:54:00 +02:00
badlop 4a7238afea Merge pull request #4459 from badlop/maybe
Enable maybe expression for Erlang 25 and 26
2025-10-09 12:47:30 +02:00
badlop 127c909f0f Merge pull request #4456 from badlop/multihost
Rename New SQL schema to Multihost, and Default to Singlehost
2025-10-09 12:15:10 +02:00
Badlop 1a7c1c5c06 mod_mam: Mention ejabberd version of the new option 2025-10-09 11:17:36 +02:00
Badlop dd5d2aeee0 make-binaries: Bump Expat 2.7.3, OpenSSL 3.5.4, unixODBC 2.3.14 2025-10-09 11:17:34 +02:00
Badlop 1d99c5cb60 Revert "make-binaries: Bump crosstool to 1.28.0"
This reverts commit 4e909fc50d.
2025-10-09 11:17:32 +02:00
badlop b9e4bbc070 Merge pull request #4463 from guusdk/xmpp-interop-testing-disable-XEP-0421
CI: Disable XEP-0421 testing that consistently fails
2025-10-08 19:07:17 +02:00
badlop 41fab88ee5 Merge pull request #4462 from guusdk/xmpp-interop-testing-v1.7.1
CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
2025-10-08 19:06:52 +02:00
Pawel Chmielowski 0ca877f925 make misc:json_encode always call json with our filter 2025-10-07 14:42:13 +02:00
Pawel Chmielowski b480f125ee Add commands argument type binary_or_list
This allow commands accepting list of binary, that will also
accept single binary that will be converted to list
2025-10-07 13:30:05 +02:00
Pawel Chmielowski 7ee5f81923 Format sub elements for tuples from maps 2025-10-07 13:21:36 +02:00
Guus der Kinderen 3d43b98d42 CI: Disable XEP-0421 testing that consistently fails
The XMPP Interop Framework tests for XEP-0421 consistently report test failures. These tests should be disabled, until appropriate fixes are applied to ejabberd.
2025-10-02 15:41:52 +02:00
Guus der Kinderen 1636c202a7 CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
Updates this GitHub Action that's used to execute XMPP-based interop tests from v1.6.1 to v1.7.1.

This does not bring a significant amount of new tests (although some early tests for XEP-0060: 'Publish/Subscribe' was added), but does improve the stability of the existing tests (meaning: less false positives and less 'flacky' tests).

Additional configuration options have been added:

- there now are three different ways to provision test accounts on the server-under-test. That should make it easier for some to embed our tests to their pipeline
- the failOnImpossibleTest option fails the test run if any configured tests were impossible to execute, ensuring all intended tests actually ran.
2025-10-02 15:05:14 +02:00
Jérôme Sautret 060992bafa Improve roster API commands documentation 2025-09-30 16:38:23 +02:00
Pawel Chmielowski c0c69394b9 Make mod_muc_sql properly handle new hats data (#4380) 2025-09-30 10:00:11 +02:00
Badlop 2b7285e0b2 Update implementation of XEP-0317 Hats to version 0.3.1 (#4380) 2025-09-30 10:00:05 +02:00
Badlop c3a24ffdf8 Revert "mod_muc_room.hrl: Work around old Dialyzer bug"
This reverts commit c4f6c9dfe7.
2025-09-30 09:58:14 +02:00
Pawel Chmielowski 48fb446f8c Properly pass send_timeout option to listener sockets 2025-09-26 10:44:38 +02:00
Pawel Chmielowski 0283a501fa Add archive_muc_as_mucsub option in mod_mam
This option can be used to enable archiving of
incoming groupchat messages as mucsub events
if user is subscribed to a room.
2025-09-25 10:06:26 +02:00
Badlop fb572bf901 ejabberdctl: When ping returns pang, return also status code 1 (#4327) 2025-09-24 13:27:04 +02:00
Badlop 7eb09295a3 mod_block_strangers: Clarify access and catpcha documentation (#4221) 2025-09-24 13:27:02 +02:00
Badlop e0eae52eae mod_muc_room: Don't require password if user is owner of room 2025-09-24 13:27:00 +02:00
Badlop dd270f99fc container.md: Update versions used in ecs container image 2025-09-24 13:26:59 +02:00
Badlop 1472caab50 make-binaries: Bump OpenSSL 3.5.3 and Expat 2.7.2 2025-09-24 13:26:57 +02:00
Badlop ef2e62a01c Bump Erlang/OTP version to 27.3.4.3 in installers and container 2025-09-24 13:26:55 +02:00
Badlop 4e909fc50d make-binaries: Bump crosstool to 1.28.0 2025-09-24 13:26:53 +02:00
Pawel Chmielowski 19b7106124 Remove EX_RULE and EX_STACK macros
As we no longer targer R19, we no longer need to carry those.
2025-09-23 11:25:17 +02:00
Badlop ccd9fa6561 Enable feature maybe_expr also in the runtime for Erlang/OTP 25
As explained in https://erlangforums.com/t/how-to-enable-maybe-expr/2154
- Erlang/OTP 27 and newer has already the feature enabled by default
- Erlang/OTP 26 requires enabling the feature in the compiler
- Erlang/OTP 25 requires enabling also in the runtime system
2025-09-22 16:04:42 +02:00
Badlop 1ddd72ffe7 Enable feature maybe_expr in the compiler for Erlang/OTP 26
Documentation:
https://www.erlang.org/doc/system/expressions.html#maybe
https://www.erlang.org/eeps/eep-0049
https://www.erlang.org/eeps/eep-0060
2025-09-22 16:04:42 +02:00
Badlop b759acdcea Runtime: Remove Erlang 24 which won't work anymore with maybe_expr 2025-09-22 16:04:42 +02:00
Badlop 4eee6d7cec CI: Don't care to include commit details in the CT logs HTML page 2025-09-18 12:02:04 +02:00
Badlop 5575d4e78e Rename New SQL schema to Multihost, and Default to Singlehost
Right now all names are supported, the previous (obsolete)
and the renamed (preferred). The changes relevant to the usage are:

When preparing configuration, the arguments:
  ./configure --enable-new-sql-schema
  ./configure --enable-multihost-sql-schema

When configuring ejabberd, the toplevel options:
  new_sql_schema: true
  sql_schema_multihost: true

When developing source code, the functions:
  ejabberd_sql:use_new_schema()
  ejabberd_sql:use_multihost_schema()
2025-09-17 11:12:38 +02:00
Badlop 4dea2f1eb6 Fix some Elvis reports 2025-09-03 12:20:42 +02:00
Badlop 1d5b9bba15 Get yconf that fixes problem introduced in recent commit (#4444) 2025-08-29 17:18:06 +02:00
Badlop 2951281115 mod_http_upload: Encode URL before parsing, as done before bba1a1e3c (#4450) 2025-08-29 17:17:39 +02:00
Badlop fd8aba6d41 ext_mod: Print module status message after installation 2025-08-29 17:17:37 +02:00
Badlop 066e0a8101 No need to test intermediate XML, and delete after conversion 2025-08-29 17:17:35 +02:00
Badlop bf262a6051 Fix markdown link to a command 2025-08-29 17:17:33 +02:00
badlop 7840924b17 Merge pull request #4443 from guusdk/xmpp-interop-testing-v1.6.1
CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
2025-08-26 17:21:46 +02:00
Guus der Kinderen 60bdab4e52 CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
Updates this GitHub Action that's used to execute XMPP-based interop tests from v1.6.0 to v1.6.1.

This is a bugfix release that should increase the stability / predictability of test execution.

A notable change is that the file structure in which XMPP stanzas are generated (which is provided as debug output) has changed. They are still stored in the directory denoted by the logDir argument, but the file structure in that directory has changed somewhat.
2025-08-25 10:43:31 +02:00
Alexey Shchepin 3a36a722c5 Fix a bug in mod_matrix_gw_room:check_event_power_level/3
CI / Tests (25) (push) Failing after 1s
CI / Tests (26) (push) Failing after 0s
CI / Tests (27) (push) Failing after 0s
CI / Tests (28) (push) Failing after 1s
Container / Container (push) Failing after 50s
Installers / Binaries (push) Failing after 49s
Runtime / Rebars (24, rebar3) (push) Failing after 6s
Runtime / Rebars (25, rebar) (push) Failing after 5s
Runtime / Rebars (25, rebar3) (push) Failing after 4s
Runtime / Rebars (26, rebar) (push) Failing after 4s
Runtime / Rebars (26, rebar3) (push) Failing after 4s
Runtime / Rebars (27, rebar3) (push) Failing after 5s
Runtime / Rebars (28, rebar3) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.14) (push) Failing after 4s
Runtime / Rebar3+Elixir (1.15) (push) Failing after 4s
Runtime / Rebar3+Elixir (1.16) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.17) (push) Failing after 4s
Runtime / Rebar3+Elixir (1.18) (push) Failing after 5s
Runtime / Mix (1.14) (push) Failing after 5s
Runtime / Mix (1.15) (push) Failing after 5s
Runtime / Mix (1.16) (push) Failing after 4s
Runtime / Mix (1.17) (push) Failing after 5s
Runtime / Mix (1.18) (push) Failing after 5s
Installers / Release (push) Has been skipped
2025-08-22 14:46:59 +03:00
Badlop 00c75c3dc9 Set version to 25.08 2025-08-22 11:15:33 +02:00
Badlop cae7850a70 CHANGELOG.md: Update to 25.08 2025-08-22 10:56:06 +02:00
Badlop ce668bef14 Container: Apply some improvements from ejabberd source code
Applied:
- ejabberd.yml.example: Use HOST_URL_ENCODE to handle case when vhost is non-latin1
- ejabberdctl: Improve explanation how to stop ejabberd in live mode
- ejabberdctl: New "mnesia_change" command, a frontend to mnesia_change_nodename
2025-08-22 10:56:03 +02:00
Badlop 3887b6d930 Update man page to 25.08 2025-08-21 17:12:55 +02:00
Badlop b7bd0e196d Update rebar.lock too 2025-08-21 17:12:09 +02:00
Badlop 6d63842ad3 Fix typo in hu.msg string 2025-08-21 16:24:58 +02:00
Badlop 2f3b9015e9 Update module and options version notes 2025-08-21 16:24:55 +02:00
Badlop 6ae48eb991 Result of running "make options" 2025-08-21 16:24:50 +02:00
dependabot[bot] c508795ad4 build(deps): bump golang in /.github/container
Bumps golang from 1.24-alpine to 1.25-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 16:24:48 +02:00
Badlop 4a053807e0 build(deps-dev): bump dialyxir from 1.4.5 to 1.4.6
Bumps [dialyxir](https://github.com/jeremyjh/dialyxir) from 1.4.5 to 1.4.6.
- [Release notes](https://github.com/jeremyjh/dialyxir/releases)
- [Changelog](https://github.com/jeremyjh/dialyxir/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeremyjh/dialyxir/compare/1.4.5...1.4.6)

---
updated-dependencies:
- dependency-name: dialyxir
  dependency-version: 1.4.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 16:24:46 +02:00
dependabot[bot] dd5bbda2dc build(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 16:24:44 +02:00
Badlop 98469678a0 ejabberd_listener: Add secret in temporary unix domain socket path (#4422) 2025-08-21 16:24:42 +02:00
Badlop b8550e087e mod_conversejs: Ensure plugins URL is separated with / (#4413) 2025-08-21 16:24:39 +02:00
Holger Weiss 644d468b4f Update registration test
Adjust test case for commit 654d4b81b1.
2025-08-21 09:51:24 +02:00
Pawel Chmielowski 74c810eeaa Tag dependencies 2025-08-20 13:46:55 +02:00
Holger Weiss a46325166a mod_register: Don't duplicate welcome subject
Don't include the configured welcome message subject with the body.  If
that's desired, the admin can simply configure it that way.  But if it's
undesired, there would be no way to avoid the subject duplication.
2025-08-19 20:09:17 +02:00
Holger Weiss 654d4b81b1 mod_register: Don't duplicate welcome message
Originally, the welcome message was sent as type 'normal'.  Apparently,
some clients don't display 'normal' messages as expected (see #4246).
To address that issue, commit 9a0ff13cc2
duplicated the welcome message as type 'chat'.  However, we shouldn't
send both formats.  The 'normal' message is either ignored by the
client, in which case it serves no purpose, or displayed, in which case
the user would see the message twice.
2025-08-19 20:03:01 +02:00
Holger Weiss ff3d33dde4 Bump xmpp version
Allow for adding HTTP File Upload purposes support to ejabberd.
2025-08-18 16:23:33 +02:00
Badlop 3183e2f733 Fix dialyzer warnings in recent commit 2025-08-15 16:33:05 +02:00
Badlop e1dc686ae7 mod_conversejs: Ensure assets_path ends in / as required by Converse (#4414) 2025-08-15 15:20:35 +02:00
Badlop 38b203feb1 ejabberd_listener: Use init_fail for errors as recommended by init_ack
That is recommended since OTP 26, see
 https://www.erlang.org/doc/apps/stdlib/proc_lib.html#init_ack/2
 Warning
 Do not use this function to return an error indicating that the process
 start failed. When doing so the start function can return before the
 failing process has exited, which may block VM resources required for a
 new start attempt to succeed. Use init_fail/2,3 for that purpose.
2025-08-15 15:20:33 +02:00
Alexey Shchepin 8b61cf0742 Don't send empty direct Matrix messages (thanks to snoopcatt) (#4420) 2025-08-15 04:52:07 +03:00
Alexey Shchepin a02c75aa08 Add support for null values in is_canonical_json (thanks to snoopcatt) (#4421) 2025-08-15 04:52:07 +03:00
Alexey Shchepin 51af393baa Add leave_timeout mod_matrix_gw option (#4386) 2025-08-15 04:52:07 +03:00
Badlop 41318e45a5 mod_conversejs: Add option conversejs_plugins (#4413) 2025-08-14 19:05:12 +02:00
badlop a94f227103 Merge pull request #4425 from guusdk/xmpp-interop-testing-v1.6.0
CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
2025-08-14 11:37:51 +02:00
Badlop 517776acd4 COMPILE.md: Mention dependencies and add link to Docs (#4431) 2025-08-13 18:17:26 +02:00
dependabot[bot] 212a5ded6e build(deps): bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 18:17:05 +02:00
Badlop fd9c929e37 Bump OpenSSL version to 3.5.2 2025-08-13 16:50:18 +02:00
Badlop ce828163af Bump Erlang/OTP version to 27.3.4.2 2025-08-13 16:50:14 +02:00
Badlop 97e1b419a0 mod_providers: New module to serve easily XMPP Providers files 2025-08-13 16:49:42 +02:00
Badlop d70ac7f7c5 ejabberd_logger: Print log lines colorized in console when using rebar3 2025-08-13 16:49:42 +02:00
Badlop 7065cb69f1 ejabberdctl: New "mnesia_change" command, a frontend to mnesia_change_nodename 2025-08-13 16:49:42 +02:00
Badlop 7815463ba0 ejabberd.yml.example: Use HOST_URL_ENCODE to handle case when vhost is non-latin1 2025-08-13 16:49:42 +02:00
Badlop 48e6631751 mod_muc_room: Fix warning about unused variable 2025-08-13 16:49:37 +02:00
Badlop 903e6b70b4 mod_matrix_gw: Document what room versions are supported since when 2025-08-13 16:48:01 +02:00
Alexey Shchepin 5edba59b24 Fix dialyzer errors 2025-08-11 20:24:59 +03:00
Alexey Shchepin 31cb4b06e4 Matrix gateway updates
- Partially rewritten state resolution
- Support for Hydra rooms
- Use double colon for separating a matrix server from a room ID in JID
  with Hydra rooms
- Partially rewritten mod_matrix_gw_s2s
- Add notary_servers option
2025-08-11 19:44:50 +03:00
Pawel Chmielowski 10f6723f00 Prevent loops in xml_compress:decode with corrupted data 2025-08-07 13:49:10 +02:00
Pawel Chmielowski f594620c68 Only offer upgrades to methods that aren't already stored 2025-08-05 11:06:17 +02:00
Pawel Chmielowski dacfad61d8 Fix format of passwords updates triggered by mod_scram_upgrade 2025-08-05 11:02:33 +02:00
badlop 7c1da7e0cf Merge pull request #4412 from marc0s/issue-4410
fix: unsubscribe users from members-only rooms when expelled
2025-08-01 12:38:37 +02:00
marc0s e709f99b47 fix: unsubscribe users from members-only rooms when expelled
Fixes #4410
2025-07-31 08:05:32 +02:00
Guus der Kinderen f150419891 CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action
Updates this GitHub Action that's used to execute XMPP-based interop tests from v1.5.0 to v1.6.0.

In this update, 524 new tests were added (more than doubling the amount of tests that previously existed).
2025-07-30 22:44:54 +02:00
Badlop 053fd26994 econf: If a host in configuration is encoded IDNA, decode it (#3519)
For example:

hosts:
  - localhost
  - locälhost3
  - xn--loclhost4-x2a

all them are converted to utf8:

ejabberd_option:hosts().
[<<"localhost">>,
 <<"locälhost3"/utf8>>,
 <<"locälhost4"/utf8>>]
2025-07-26 00:43:25 +02:00
Badlop bba1a1e3ca mod_http_upload: Encode URLs into IDNA when showing to XMPP client (#3519) 2025-07-25 19:54:28 +02:00
Badlop e5da1efea4 misc: Move uri_parse/1 to yconf and merge with yconf:parse_uri/1 2025-07-25 19:54:26 +02:00
Badlop fbfd41c16e misc: uri_decode/1 moved here from ejabberd_http and prosody2ejabberd 2025-07-25 19:54:25 +02:00
Badlop 4391921727 ejabberd_config: New predefined keyword HOST_URL_ENCODE 2025-07-25 19:54:23 +02:00
Badlop 4cd3c657e2 ejabberd_listener: Try to create provisional socket in final directory (#4422)
and if that path is too long, then try HOME directory,
if that's too long too, throw error explaining the problem.

By the way, cutting the base64 string to 107 is a bad idea,
as it encodes the final path, which would get lost and crash.
2025-07-25 11:15:37 +02:00
Badlop 7647b77225 Runtime: Raise the minimum Erlang tested to Erlang/OTP 24
The Erlang containers from versions 20-23 use Debian Buster,
and require the debian repositories to install some development libraries.
The Debian Buster repositories are no longer available,
which means that we can no longer perform any test with Erlang 20-23.
2025-07-25 11:15:34 +02:00
Pawel Chmielowski fe8710fe00 Rename auth_password_types_hidden_in_scram1 option to auth_password_types_hidden_in_sasl1
Also add migration code from old name
2025-07-25 09:39:21 +02:00
Pawel Chmielowski 1a9b147baf Report db failures in mod_muc_mnesia:restore_room 2025-07-23 20:27:19 +02:00
Pawel Chmielowski 6214e0385d Report db failures from mod_muc:restore_room 2025-07-23 19:59:32 +02:00
Badlop f7002c31f0 Fix some typos in previous commit (#4422) 2025-07-22 13:30:54 +02:00
Badlop 99b75396ad ejabberd_listener: Log error when cannot set definitive unix socket (#4422) 2025-07-22 10:48:17 +02:00
Badlop b1c3baa7bd Bump p1_acme to fix 'AttributePKCS-10' and OTP 28 (processone/p1_acme#4) 2025-07-22 10:47:38 +02:00
Badlop 355eb5dfde Improve documentation of toplevel options default_db and default_ram_db 2025-07-22 10:47:36 +02:00
Badlop d269e32c3a ejabberd_config: Improve warning message about unsupported ram_db type 2025-07-22 10:47:33 +02:00
Badlop 214b76f763 ejabberd_doc: Document commands tags for modules 2025-07-22 10:47:30 +02:00
Holger Weiss 73a8fbdfb5 make-binaries: Re-add executable bit 2025-07-21 13:42:07 +02:00
Holger Weiss 9b6f0aeb3c make-binaries: Disable Linux-PAM's logind support
Make sure Linux-PAM doesn't attempt to include logind support.  This
avoids a build failure in case the host system has systemd's
development headers installed.
2025-07-21 13:38:11 +02:00
Badlop 45a6aed57f mod_admin_extra: Run sm_kick_user event when kicking account (#4415)
This is important when running the ban_account command and mod_auth_fast
is enabled, as the client may store auth tokens that bypass
the banning stored in private storage and enforced by ejabberd_auth.
2025-07-16 18:09:14 +02:00
Badlop 9d17a160b6 mod_admin_extra: No need to change password in ban_account (#4415)
When ban details are stored in private storage,
ejabberd_auth reads them and prevents user login,
so there's no need to modify the account password.
2025-07-16 13:33:40 +02:00
Badlop 850d097660 ejabberd_auth: Handle case running check_password when account is banned 2025-07-16 13:33:36 +02:00
Badlop 8ce8f67c06 misc: Add workaround for Json library not able to handle empty list 2025-07-16 13:33:33 +02:00
Pawel Chmielowski a17c2c166d Fix issue with filtering duplicates in auth_mnesia:get_users()
Previous version was only correct when data to process
was sorted, which was not always the case.

This add common implementation of lists:uniq in misc that works
also on <R25, and switches get_users to use it.
2025-07-16 12:43:25 +02:00
Badlop a64aa9e280 Set version to 25.07
CI / Tests (25) (push) Failing after 0s
CI / Tests (26) (push) Failing after 0s
CI / Tests (27) (push) Failing after 1s
CI / Tests (28) (push) Failing after 0s
Container / Container (push) Failing after 46s
Installers / Binaries (push) Failing after 1m7s
Runtime / Rebars (20, rebar) (push) Failing after 5s
Runtime / Rebars (20, rebar3) (push) Failing after 5s
Runtime / Rebars (25, rebar) (push) Failing after 4s
Runtime / Rebars (25, rebar3) (push) Failing after 5s
Runtime / Rebars (26, rebar) (push) Failing after 5s
Runtime / Rebars (26, rebar3) (push) Failing after 5s
Runtime / Rebars (27, rebar3) (push) Failing after 4s
Runtime / Rebars (28, rebar3) (push) Failing after 13s
Runtime / Rebar3+Elixir (1.14) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.15) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.16) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.17) (push) Failing after 5s
Runtime / Rebar3+Elixir (1.18) (push) Failing after 4s
Runtime / Mix (1.14) (push) Failing after 4s
Runtime / Mix (1.15) (push) Failing after 4s
Runtime / Mix (1.16) (push) Failing after 5s
Runtime / Mix (1.17) (push) Failing after 5s
Runtime / Mix (1.18) (push) Failing after 5s
Installers / Release (push) Has been skipped
2025-07-11 13:15:26 +02:00
Badlop 3834a47a39 CHANGELOG.md: Update to 25.07 2025-07-11 13:15:26 +02:00
Badlop 9f6ff515ff Fix documentation for commands implemented in modules with several behaviours 2025-07-11 13:10:08 +02:00
Pawel Chmielowski 64a210842e Use tagged dependencies 2025-07-11 12:55:25 +02:00
Badlop c0de52c967 Update man page to 25.07 2025-07-11 12:10:58 +02:00
Badlop 67c3df05b3 ext_mod: Add temporary workaround for zip including absolute path 2025-07-11 12:10:57 +02:00
Badlop 4a66616756 mod_antispam: Mention usage of CONFIG_PATH predefined keyword 2025-07-11 12:10:54 +02:00
Pawel Chmielowski e4d424bf56 Add auth_password_types_hidden_in_scram1 option
This option allows disabling some auth mechanisms
to be offered in SASL1 features. This makes adding
new password types easier, by ensuring that new
password use will be offered only to clients that
have new type stored (SASL2 clients that send us
user info before features need to be sent), but
not to clients where we don't know if they have
new passwords.
2025-07-10 16:52:13 +02:00
Badlop c0fc6091b1 Annotate ejabberd version of new modules, options, commands 2025-07-10 10:58:17 +02:00
Badlop ef35d19ff1 Update Ukrainian translation (thanks to Максим Горпиніч) 2025-07-10 10:58:15 +02:00
Badlop cfa787c4b6 Update Tamil translation (thanks to TamilNeram) 2025-07-10 10:58:14 +02:00
Badlop 611ebce0d2 Update Greek translation (thanks to GiannosOB) 2025-07-10 10:58:13 +02:00
Badlop 1e0b8cb547 Bump Erlang/OTP 27.3.4.1 for container image too 2025-07-10 10:58:10 +02:00
Pawel Chmielowski 99d323b1dd Update fast_tls with updated deps 2025-07-09 17:13:17 +02:00
Pawel Chmielowski 72bc9b6c7f Allow s2s connections to accept client certificates that have only server purpose
Due to Google Chrome certification requirements we can expect
that in near future there will be no certificate authority
that will issue certifcates that have both server and client auth
purposes.

This change makes s2s listeners ignore cert purposes, and should
allow servers that have those new certificate to use it, to
authenticate new s2s connections.

This fixes issue #4392
2025-07-09 14:16:02 +02:00
Pawel Chmielowski 4694a482f4 Update doap with info about xep-0486 2025-07-08 20:13:56 +02:00
Badlop 443f39bfdb Result of running "make format doap options" 2025-07-08 12:43:43 +02:00
Badlop b118dd8fc6 Update reference to XEP-0485 support 2025-07-08 12:43:43 +02:00
Badlop bf39da7b8b mod_pubsub_serverinfo: Rephrase documentation and improve markdown 2025-07-08 12:42:52 +02:00
Badlop ad3eee059e mod_antispam: Annotate ejabberd version of the new commands 2025-07-08 12:42:50 +02:00
Badlop e94ccabcf0 mod_antispam: Move commands to a new "spam" API tag 2025-07-08 12:42:48 +02:00
Badlop 427a29c74e Bump Erlang/OTP 27.3.4.1, Elixir 1.18.4, libexpat 2.7.1, OpenSSL 3.5.1
Notice:
- installers use OTP 27.3.4.1, the latest available right now
- containers use OTP 27.3.4, because container for 27.3.4.1 was not published,
  see https://hub.docker.com/_/erlang
2025-07-08 12:42:44 +02:00
badlop 1d79edbae0 Merge pull request #4408 from sstrigler/mod_pubsub_serverinfo
mod_pubsub_serverinfo
2025-07-08 10:55:14 +02:00
badlop 9e4a6d09df Merge pull request #4373 from sstrigler/mod_antispam
mod_antispam: port from ejabberd-contrib/mod_spam_filter
2025-07-08 10:19:34 +02:00
Stefan Strigler 7b08289799 Merge pull request #6 from badlop/antispam_services
Replace options rtbl_host and rtbl_domains_node with rtbl_services
2025-07-08 08:23:22 +02:00
Paweł Chmielowski bc937546ec Merge pull request #4409 from Britaliope/patch-1
missing comma in postgres schema
2025-07-07 19:03:10 +02:00
Stefan Strigler 3b972fe4a3 update p1/xmpp to latest 2025-07-07 17:58:56 +02:00
Badlop 5e93725044 Replace options rtbl_host and rtbl_domains_node with rtbl_services 2025-07-07 17:42:05 +02:00
Stefan Strigler 500af47b79 mod_pubsub_serverinfo: codec spec moved to p1/xmpp 2025-07-07 17:40:34 +02:00
Stefan Strigler d862e04186 Merge pull request #5 from badlop/antispam_options
Fix crashes, improve options, complete documentation
2025-07-07 15:29:06 +02:00
Badlop 5f293cb1e0 Document more options 2025-07-07 14:28:14 +02:00
Bruno MATEU c93ea2c22f missing comma in postgres schema 2025-07-07 08:28:23 +02:00
Stefan Strigler c567005241 mod_pubsub_serverinfo: get pubsub host from server state 2025-07-05 14:54:11 +02:00
Stefan Strigler a6823d157c mod_pubsub_serverinfo: add documentation 2025-07-05 14:38:55 +02:00
Stefan Strigler bf54cc59e1 mod_pubsub_serverinfo: apply make format 2025-07-05 14:23:03 +02:00
Stefan Strigler 740b0c7dd7 mod_pubsub_serverinfo: initial import as found on ejabberd-contrib 2025-07-05 14:19:20 +02:00
Badlop c3f5083f15 Use the new gen_mod:prep_stop/1 feature
This fixes the problem when stopping the module with multiple vhosts:
unsubscribing from a local pubsub requires mod_pubsub in that vhost running,
but ejabberd stops mod_pubsub from a vhost before stopping mod_antispam
in other vhost.
2025-06-30 18:21:19 +02:00
Badlop b65c11daf6 New predefined keyword: CONFIG_PATH 2025-06-30 18:21:19 +02:00
Badlop 263e1f59f7 Fix problem calling get_log_path when ejabberd is stopping
When ejabberd is being stopped
and some module calls ejabberd_logger:get_log_path(),
application:load/1 crashes with error:

** Reason for termination ==
** {terminating,
       [{application_controller,call,2,
            [{file,"application_controller.erl"},{line,511}]},
        {application,load1,2,[{file,"application.erl"},{line,274}]},
        {ejabberd_config,env_binary_to_list,2,
            [{file,"/home/git/ejabberd/src/ejabberd_config.erl"},
             {line,343}]},
        {ejabberd_logger,get_log_path,0,
            [{file,"/home/git/ejabberd/src/ejabberd_logger.erl"},
             {line,55}]},
2025-06-30 18:21:19 +02:00
Badlop 3d89c9199c gen_mod: Add support to prepare module stopping before actually stopping any module
Follows the reasoning of application:prep_stop, but applied to gen_mod:
  https://www.erlang.org/docs/28/apps/kernel/application.html#c:prep_stop/1
2025-06-30 18:21:19 +02:00
Stefan Strigler 4a51bf90ab Merge pull request #3 from badlop/antispam_files
Move spam file management to a submodule
2025-06-25 15:45:15 +02:00
Badlop a77c7e36b0 Move spam files parsing to a submodule 2025-06-23 09:44:33 +02:00
Badlop 88ae3fddf3 mod_antispam: Sort and document files options 2025-06-23 09:44:33 +02:00
Badlop bddcf0624e mod_antispam: Move some definitions to a header file 2025-06-23 09:44:33 +02:00
Badlop 6b47d3eb0d mod_auth_fast: Clear tokens on kick, change pass and unregister (#4397)(#4398)(#4399) 2025-06-20 16:55:59 +02:00
Badlop a0c97b33e0 CONTAINER.md: Move ejabberd-contrib content from Docs website 2025-06-20 12:45:35 +02:00
Badlop 5def9cef9f ext_mod: When upgrading module, clean also the compiled directories 2025-06-20 12:45:31 +02:00
Badlop c20d745028 New option hosts_alias and function resolve_host_alias/1 (4400) 2025-06-19 12:40:16 +02:00
Badlop e099435cd6 ejabberd_http: Remove unused default_host option and state element
The option 'default_host' for ejabberd_http was added years ago
in commit 7d623d5. However it was problematic and the usage was
removed in commit 83c291c. It's time to remove its last forgotten bits.
2025-06-19 12:40:15 +02:00
Badlop d6a00f5151 mod_conversejs: Add link in WebAdmin to local Converse if configured 2025-06-19 12:40:14 +02:00
Badlop 653413e912 Run new webadmin hooks to add items to system menu 2025-06-19 12:40:12 +02:00
Badlop 288eecc23d Use misc:encode_pid/1 in ejabberd_sm_sql 2025-06-19 12:40:10 +02:00
Stefan Strigler b55b6f3d26 Merge pull request #2 from badlop/antispam_filter
Move filtering actual implementation to a submodule
2025-06-18 14:15:39 +02:00
Badlop d00561b58c Move filtering implementation to a submodule 2025-06-18 11:52:28 +02:00
Badlop 432810db89 Fix minor typos 2025-06-18 11:52:28 +02:00
Badlop f3b1b5d419 Result of running "make format" 2025-06-18 11:52:28 +02:00
Stefan Strigler d9a7b67f0e mod_antispam: increase timeout when waiting for dump file 2025-06-17 17:21:58 +02:00
Badlop 85f05192c8 Move spam_dump_file implementation to a submodule 2025-06-17 13:20:43 +02:00
Badlop 149b715b4f New predefined keyword: LOG_PATH 2025-06-17 13:20:43 +02:00
Stefan Strigler bae345b92b mod_antispam: test dump file 2025-06-17 13:18:13 +02:00
Stefan Strigler 10ec128b94 mod_antispam: test whitelisted domain 2025-06-17 13:18:13 +02:00
Stefan Strigler 7a6e409879 mod_antispam: use message/3 in test 2025-06-17 13:18:13 +02:00
Stefan Strigler ea19e4bc7f mod_antispam: remove unnecessary check in test
this was left over from debugging issues with fixtures
2025-06-17 13:18:13 +02:00
Stefan Strigler 6122a525d2 mod_antispam: fix config types 2025-06-17 13:18:13 +02:00
Badlop b607d95a93 Refactorize each individual test case in individual functions 2025-06-17 13:18:13 +02:00
Stefan Strigler ee46333def add make target test-<group>
Eg. invoke common test for specific test group only like

$ CT_BACKEND=mnesia,redis make test-antispam_single
2025-06-17 13:18:13 +02:00
Stefan Strigler 34b40aec66 mod_antispam: add format instructions 2025-06-17 13:18:13 +02:00
Stefan Strigler 639147be41 fix pubsub retract items being a list of ids 2025-06-17 13:18:11 +02:00
Stefan Strigler c48aa38c39 mod_antispam: add test suite 2025-06-17 13:10:27 +02:00
Stefan Strigler 70bec7b714 tests: update readme and compose to work with current sw versions 2025-06-17 13:10:27 +02:00
Stefan Strigler a7c15eaccf mod_antispam: initial import from ejabberd-contrib/mod_spam_filter 2025-06-17 13:10:27 +02:00
Badlop c78e99dd54 Use auxiliary function to get HOME, use Mnesia directory when not set (#4402) 2025-06-17 13:02:01 +02:00
dependabot[bot] 3196779308 build(deps-dev): bump ex_doc from 0.37.3 to 0.38.2
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.37.3 to 0.38.2.
- [Release notes](https://github.com/elixir-lang/ex_doc/releases)
- [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.37.3...v0.38.2)

---
updated-dependencies:
- dependency-name: ex_doc
  dependency-version: 0.38.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 13:02:01 +02:00
Badlop 064b005ec5 Define some example glossary terms 2025-06-17 13:02:01 +02:00
Badlop 2d2b98e525 Update Elvis to 4.1.1, fix some warnings and enable their tests 2025-06-16 16:59:43 +02:00
Badlop 71f623ddbf Fix previous commits 2025-06-16 13:14:07 +02:00
Badlop f03b5f4c44 Support list of IDs in pubsub-items-retract (processone/xmpp#100) 2025-06-16 13:09:52 +02:00
Badlop aff8b47b6c Add dialyzer spec to try to dix warnings 2025-06-16 12:48:34 +02:00
Badlop 6c1452435d Bump xmpp to get: Add Unified Push support (via Conversations/up) (processone/xmpp#101) 2025-06-16 12:24:06 +02:00
Badlop 38f365ffeb Move ecPrivkeyVer1 workaround for Jose from ejabberd to p1_acme
Revert "Add workaround for Jose 1.11.10 not supporting OTP 28 ecPrivkeyVer1 (#4393)"
This reverts commit 363351b18c.
2025-06-16 12:24:01 +02:00
Badlop ed846c4a88 ext_mod: Recommend to write README.md instead txt (processone/ejabberd-contrib#363) 2025-06-09 18:02:40 +02:00
Badlop 8855a304cc ext_mod: Support library path installed from Debian (processone/ejabberd-contrib#363) 2025-06-09 17:53:25 +02:00
Paweł Chmielowski 95a083a6f4 Replace csplit with perl in rebar3-format.sh
Bsd csplit doesn't offer some options that we require, so let's use perl
that should work everywhere
2025-06-06 16:52:28 +02:00
Jérôme Sautret 0bb99bb371 Fix supported version of XEP-0485 2025-06-06 11:57:27 +02:00
Badlop 38cc3ccb1e Avoid using the "else" conditional compilation macro directive
Even if "-else." is a valid directive:
  https://www.erlang.org/docs/28/system/macros.html#conditional-compilation
there is a bug in rebar3_format that annoyingly rewrites it as "- else ."
  https://github.com/AdRoll/rebar3_format/issues/337
as a quick workaround, rewrite directives to not use "-else."
2025-06-05 17:02:41 +02:00
Jérôme Sautret c51b044b3f Fix macro used in string options when defined in env var
Configuration macro defined as EJABBERD_MACRO_* environment variable
couldn't be used inside string options, like this:

captcha_cmd: "tools/@SCRIPT@"
2025-06-05 14:20:11 +02:00
Badlop 363351b18c Add workaround for Jose 1.11.10 not supporting OTP 28 ecPrivkeyVer1 (#4393) 2025-06-04 11:00:00 +02:00
Paweł Chmielowski 167bbc768a Remove unused function 2025-06-02 18:39:51 +02:00
Paweł Chmielowski 591e15f0f6 Fix mnesia to sql exporter after changes to auth tables
Conversion functions used by ej2sql module was not updated after change
that did allow storing multiple passwords per user, which made us skip
passwords that were updated to new format, this fixes this problem.

This fixes issue #4391
2025-06-02 18:19:03 +02:00
Badlop b4a917db09 Runtime: Fix step name 2025-05-30 17:17:32 +02:00
Badlop 7755fcc846 Bump fast_xml and xmpp for improved Erlang/OTP 28 support 2025-05-30 17:17:32 +02:00
Badlop 250af8f06a Fix "make options" in Erlang/OTP 28 (thanks to Alexey Shchepin)(#4352) 2025-05-30 17:17:32 +02:00
Alexey Shchepin 9569e407b5 Don't send empty messages in Matrix rooms (#4385) 2025-05-30 17:27:30 +03:00
Alexey Shchepin 573d5525ec Fix key validation in mod_matrix_gw_s2s:check_signature 2025-05-30 17:27:30 +03:00
Badlop f1de7b008b Use xmpp and p1_acme patched with Erlang/OTP 28 support 2025-05-28 17:36:39 +02:00
Badlop c10e6ded78 Runtime: Use --with-min-erlang to bypass Erlang 25 soft requirement 2025-05-28 17:36:39 +02:00
Badlop 9bc991cb7d CI and Runtime: Add Erlang/OTP 28 to the versions matrix 2025-05-28 17:36:39 +02:00
Badlop ffa7c32d80 Rebar/Rebar3: Update binaries to work with Erlang/OTP 25-28 (#4354)
They are compiled from their git repositories, main branches,
using erlang:25-slim docker image.

To compile ejabberd using rebar/rebar3 and Erlang 20.0 up to 23.3,
you can download the old binaries from ejabberd 21.12, available at:
  https://github.com/processone/ejabberd/raw/21.12/rebar
  https://github.com/processone/ejabberd/raw/21.12/rebar3

To compile ejabberd using rebar/rebar3 and Erlang 24.0 up to 24.3,
you can download the old binaries from ejabberd 24.12, available at:
  https://github.com/processone/ejabberd/raw/24.12/rebar
  https://github.com/processone/ejabberd/raw/24.12/rebar3
2025-05-28 17:36:07 +02:00
Alexey Shchepin 038491d2ec Support older Matrix rooms versions starting from version 4 2025-05-28 13:30:00 +03:00
Paweł Chmielowski 9d1d57cd82 Fix typo in last commit 2025-05-21 15:53:13 +02:00
Paweł Chmielowski c38b2bfc21 Add options that allow to configure proxy used by rest module 2025-05-21 15:37:49 +02:00
Paweł Chmielowski 18e7805ef5 Present mam full text search in xep-431 compatible way
Also offer those fields only on mysql, where full text search is available
2025-05-16 13:33:24 +02:00
Badlop 010eab6e30 When encoding JSON, handle term that is key-value list (#4379) 2025-05-13 12:37:26 +02:00
Badlop 30c8088d73 Fix crash in "rebar3 cover" with Erlang/OTP 28 (#4353) 2025-05-13 12:37:26 +02:00
Badlop 354009033a CI: Don't run "make options" with Erlang/OTP 28 yet because it crashes (#4352) 2025-05-13 12:37:26 +02:00
Badlop bf3f904fe9 Runtime: Don't test rebar2 + OTP 28 because "make rel" fails
When running "make rel":

./rebar  generate
==> rel (generate)
ERROR: generate failed while processing /__w/ejabberd/ejabberd/rel:
{'EXIT',{{badmatch,{error,"Application et is used in release \"ejabberd\" and cannot be excluded"}},
         [{rebar_reltool,generate,2,
                         [{file,"src/rebar_reltool.erl"},{line,53}]},
          {rebar_core,run_modules,4,[{file,"src/rebar_core.erl"},{line,493}]},
          {rebar_core,execute,6,[{file,"src/rebar_core.erl"},{line,418}]},
          {rebar_core,maybe_execute,8,
                      [{file,"src/rebar_core.erl"},{line,302}]},
          {rebar_core,process_dir1,7,[{file,"src/rebar_core.erl"},{line,261}]},
          {rebar_core,process_each,5,[{file,"src/rebar_core.erl"},{line,351}]},
          {rebar_core,process_dir1,7,[{file,"src/rebar_core.erl"},{line,253}]},
          {rebar_core,process_commands,2,
                      [{file,"src/rebar_core.erl"},{line,93}]}]}}
make: *** [Makefile:570: prod] Error 1
2025-05-13 12:37:14 +02:00
Paweł Chmielowski d65cafae64 Update code for switching to new schema type to users table changes 2025-05-12 18:47:18 +02:00
Marcos de Vera Piquero cda1d4ce7f feat: fire new push_send_notification hook
Do not route XEP-357 IQ notification directly but run it through
registered hooks that will ultimately decide whether or not send it
and, if needed, customize the notification contents.

Hooks can return:

  - a modified IQ packet, as per their business logic
	- the atom `drop`, to effectively cancel the push notification
2025-05-09 14:14:51 +02:00
Paweł Chmielowski 128103b7b2 Typo 2025-04-30 14:44:19 +02:00
Paweł Chmielowski cbb88638d2 Add mssql specific implementation of delete_old_mam_messages 2025-04-30 14:31:05 +02:00
Paweł Chmielowski f046aeeaa2 Fix dialyzer warning in last commit 2025-04-29 12:20:13 +02:00
Paweł Chmielowski bd5f9537c5 Normalize username when determining if user want to change pass in mod_register
Should fix issue #4377
2025-04-29 11:43:17 +02:00
Paweł Chmielowski 838bbd70ef Strip query data when returning errors in mod_register 2025-04-29 10:33:17 +02:00
Paweł Chmielowski e7997244af Allow to specify minimal erlang version using --with-min-erlang in configure 2025-04-28 13:58:40 +02:00
Paweł Chmielowski 3874e71971 Better lists:uniq substitute in ejabberd_config
Original version didn't keep original order of modules, which could break
ability to override of options by external modules.
2025-04-28 12:59:17 +02:00
Paweł Chmielowski 67cc0c5286 Handle objects that don't need conversion in mod_mam_mnesia:transform()
This should fix issue #4374
2025-04-22 12:31:08 +02:00
Badlop 826123db56 Bump Erlang/OTP version to 27.3.3 2025-04-22 12:27:10 +02:00
Badlop 05b0037462 Raise the minimum Elixir tested version to 1.14.0 (#4281)
Cannot test with Elixir 1.13.4 because its container image includes
Erlang/OTP 24.3 that ejabberd does not support anymore.
2025-04-22 12:27:10 +02:00
Badlop 54796f888e Raise Erlang/OTP minimum requirement to 25.0 (#4281) 2025-04-22 12:27:10 +02:00
Badlop 82ec0a4837 Remove unused MyNick variables 2025-04-22 12:27:09 +02:00
Badlop 7167df7979 mysql.sql: Fix typo in commit 7862c6a when creating users table 2025-04-22 12:25:04 +02:00
Paweł Chmielowski 45e7d8426d Make delete_old_mam_messages_batch work with sqlite 2025-04-17 14:21:25 +02:00
168 changed files with 10293 additions and 2541 deletions
+155
View File
@@ -0,0 +1,155 @@
name: 'Manage Database'
inputs:
for:
default: ""
description: 'One or more databases to manage:
mysql, pgsql, redis, mssql'
do:
default: ""
description: 'One or more tasks to do:
install, start, user, create, drop, dump'
dump-suffix:
default: ""
description: 'Suffix to append to the dump file name'
mssql-schema:
default: ""
description: 'SQL schema for the MSSQL database:
singlehost or multihost'
runs:
using: "composite"
steps:
############################################################# Install #####
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'install')
shell: sh
run: |
sudo sed -i 's/yes/no/g' /etc/initramfs-tools/update-initramfs.conf
sudo rm -f /var/lib/man-db/auto-update || echo ok
sudo apt-get -q update
sudo apt-get -q -y install postgresql
- if: contains(inputs.for, 'redis') && contains(inputs.do, 'install')
shell: sh
run: |
sudo sed -i 's/yes/no/g' /etc/initramfs-tools/update-initramfs.conf
sudo rm -f /var/lib/man-db/auto-update || echo ok
sudo apt-get -q update
sudo apt-get -q -y install redis-server
- if: contains(inputs.for, 'mssql') && contains(inputs.do, 'install')
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: tdsodbc
- if: contains(inputs.for, 'mssql') && contains(inputs.do, 'install')
shell: sh
run: |
docker run -d -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=ejabberd_Test1" \
-v $(pwd)/test/docker/db/mssql/initdb/initdb_mssql.sql:/initdb_mssql.sql:ro \
-v $(pwd)/sql/mssql.sql:/mssql.sql:ro \
-v $(pwd)/sql/mssql.new.sql:/mssql.new.sql:ro \
-p 1433:1433 --name ejabberd-mssql \
"mcr.microsoft.com/mssql/server:2019-latest"
sleep 10
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd \
-C -U SA -P ejabberd_Test1 -S localhost -i /initdb_mssql.sql
- if: contains(inputs.for, 'mssql') && contains(inputs.do, 'install')
&& inputs.mssql-schema == 'singlehost'
shell: sh
run: |
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd \
-C -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test \
-i /mssql.sql
- if: contains(inputs.for, 'mssql') && contains(inputs.do, 'install')
&& inputs.mssql-schema == 'multihost'
shell: sh
run: |
docker exec ejabberd-mssql /opt/mssql-tools18/bin/sqlcmd \
-C -U SA -P ejabberd_Test1 -S localhost -d ejabberd_test \
-i /mssql.new.sql
############################################################### Start #####
- if: contains(inputs.for, 'mysql') && contains(inputs.do, 'start')
shell: sh
run: |
sudo systemctl start mysql.service
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'start')
shell: sh
run: |
sudo systemctl start postgresql.service
pg_isready
################################################################ User #####
- if: contains(inputs.for, 'mysql') && contains(inputs.do, 'user')
shell: sh
run: |
mysql -u root -proot -e "CREATE USER 'ejabberd_test'@'localhost'
IDENTIFIED BY 'ejabberd_test';"
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'user')
shell: sh
run: |
sudo -u postgres psql -c "CREATE USER ejabberd_test
WITH PASSWORD 'ejabberd_test';"
################################################################ Dump #####
- if: contains(inputs.for, 'mysql') && contains(inputs.do, 'dump')
shell: sh
run: |
sudo mysqldump -u root -proot ejabberd_test \
> mysql-${{ inputs.dump-suffix }}.sql
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'dump')
shell: sh
run: |
sudo -u postgres pg_dump ejabberd_test \
> pgsql-${{ inputs.dump-suffix }}.sql
################################################################ Drop #####
- if: contains(inputs.for, 'mysql') && contains(inputs.do, 'drop')
shell: sh
run: |
mysql -u root -proot -e "DROP DATABASE ejabberd_test;"
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'drop')
shell: sh
run: |
sudo -u postgres psql -c "DROP DATABASE ejabberd_test;"
############################################################## Create #####
- if: contains(inputs.for, 'mysql') && contains(inputs.do, 'create')
shell: sh
run: |
mysql -u root -proot -e "CREATE DATABASE ejabberd_test;"
mysql -u root -proot -e "GRANT ALL ON ejabberd_test.*
TO 'ejabberd_test'@'localhost';"
- if: contains(inputs.for, 'pgsql') && contains(inputs.do, 'create')
shell: sh
run: |
sudo -u postgres psql -c "CREATE DATABASE ejabberd_test;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES
ON DATABASE ejabberd_test TO ejabberd_test;"
sudo -u postgres psql -c "GRANT ALL ON SCHEMA public TO ejabberd_test;"
sudo -u postgres psql -c "ALTER DATABASE ejabberd_test
OWNER TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
TABLES IN SCHEMA public
TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
SEQUENCES IN SCHEMA public
TO ejabberd_test;"
+178
View File
@@ -0,0 +1,178 @@
name: 'Manage ejabberd'
inputs:
for:
default: ""
description: 'Release method, one of:
prod, dev, install, run, deb'
do:
default: ""
description: 'One or more tasks to perform:
deploy, no_acme, no_tls,
start, register, update_sql, stop,
logs, check'
username:
default: "user1"
description: 'Username part of the account JID'
host:
default: "localhost"
description: 'Host part of the account JID'
tool:
default: "rebar3"
description: 'Build tool to use:
rebar or rebar3 (only relevant to make rebar2)'
rel_name_vsn:
default: ""
description: 'Base name of installer files'
runs:
using: "composite"
steps:
- name: Path Definitions
id: path
shell: bash
run: |
case ${{ inputs.for }} in
'prod')
BASE="_build/prod/rel/ejabberd"
echo "conf=$BASE/conf" >> $GITHUB_OUTPUT
echo "logs=$BASE/logs" >> $GITHUB_OUTPUT
echo "ectl=$BASE/bin/ejabberdctl" >> $GITHUB_OUTPUT
;;
'dev')
BASE="_build/dev/rel/ejabberd"
echo "conf=$BASE/conf" >> $GITHUB_OUTPUT
echo "logs=$BASE/logs" >> $GITHUB_OUTPUT
echo "ectl=$BASE/bin/ejabberdctl" >> $GITHUB_OUTPUT
;;
'install')
BASE="/tmp/ejabberd"
echo "conf=$BASE/etc/ejabberd" >> $GITHUB_OUTPUT
echo "logs=$BASE/var/log/ejabberd" >> $GITHUB_OUTPUT
echo "ectl=$BASE/sbin/ejabberdctl" >> $GITHUB_OUTPUT
;;
'deb')
BASE="/opt/ejabberd"
echo "base=$BASE" >> $GITHUB_OUTPUT
echo "conf=$BASE/conf" >> $GITHUB_OUTPUT
echo "logs=$BASE/logs" >> $GITHUB_OUTPUT
echo "ectl=sudo /opt/${{ inputs.rel_name_vsn }}/bin/ejabberdctl" >> $GITHUB_OUTPUT
;;
'run')
BASE="$HOME/opt/ejabberd"
echo "base=$BASE" >> $GITHUB_OUTPUT
echo "conf=$BASE/conf" >> $GITHUB_OUTPUT
echo "logs=$BASE/logs" >> $GITHUB_OUTPUT
echo "ectl=$HOME/opt/${{ inputs.rel_name_vsn }}/bin/ejabberdctl" >> $GITHUB_OUTPUT
;;
esac
############################################################## Deploy #####
- if: contains(inputs.do, 'deploy') &&
inputs.for == 'prod' && inputs.tool == 'rebar'
shell: sh
run: |
mkdir -p _build/prod && ln -s `pwd`/rel/ _build/prod/rel
- if: contains(inputs.do, 'deploy') &&
inputs.for == 'dev' && inputs.tool == 'rebar'
shell: sh
run: |
mkdir -p _build/dev && ln -s `pwd`/rel/ _build/dev/rel
- if: contains(inputs.do, 'deploy') &&
(inputs.for == 'prod' ||
inputs.for == 'dev' ||
inputs.for == 'install')
shell: sh
run: |
make ${{ inputs.for }}
- if: contains(inputs.do, 'deploy') &&
inputs.for == 'deb'
shell: sh
run: |
sudo dpkg -i $(ls -1 *.deb)
- if: contains(inputs.do, 'deploy') &&
inputs.for == 'run'
shell: sh
run: |
./$(ls -1 *.run)
################################################################ ACME #####
- if: contains(inputs.do, 'no_acme')
shell: sh
run: |
sed -i 's/loglevel/acme:\n auto: false\nloglevel/g' \
${{ steps.path.outputs.conf }}/ejabberd.yml
################################################################# TLS #####
- if: contains(inputs.do, 'no_tls') &&
inputs.for == 'dev'
shell: sh
run: |
sed -i 's/starttls_required: true/starttls_required: false/g' \
${{ steps.path.outputs.conf }}/ejabberd.yml
############################################################### Start #####
- if: contains(inputs.do, 'start')
shell: sh
run: |
${{ steps.path.outputs.ectl }} start
${{ steps.path.outputs.ectl }} started
############################################################ Register #####
- if: contains(inputs.do, 'register')
shell: sh
run: |
${{ steps.path.outputs.ectl }} \
register ${{ inputs.username }} ${{ inputs.host }} s0mePass
${{ steps.path.outputs.ectl }} \
registered_users ${{ inputs.host }} >> registered.log
grep -q '${{ inputs.username }}' registered.log
########################################################### UpdateSQL #####
- if: contains(inputs.do, 'update_sql')
shell: sh
run: |
${{ steps.path.outputs.ectl }} \
update_sql
################################################################ Stop #####
- if: contains(inputs.do, 'stop')
shell: sh
run: |
${{ steps.path.outputs.ectl }} stop
${{ steps.path.outputs.ectl }} stopped
################################################################ Logs #####
- if: contains(inputs.do, 'logs')
shell: sh
run: |
SUDO=sudo
[ "${{ inputs.for }}" = "deb" ] || SUDO=""
echo "::group::View ejabberd.log"
$SUDO cat ${{ steps.path.outputs.logs }}/ejabberd.log
echo "::endgroup::"
echo "::group::View error.log"
$SUDO cat ${{ steps.path.outputs.logs }}/error.log
echo "::endgroup::"
############################################################### Check #####
- if: contains(inputs.do, 'check')
shell: sh
run: |
grep -q 'is started' ${{ steps.path.outputs.logs }}/ejabberd.log
grep -q 'is stopped' ${{ steps.path.outputs.logs }}/ejabberd.log
test $(find ${{ steps.path.outputs.logs }}/ -empty -name error.log)
+3 -3
View File
@@ -1,6 +1,6 @@
#' Define default build variables
ARG OTP_VSN='27.3.2'
ARG ELIXIR_VSN='1.18.3'
ARG OTP_VSN='27.3.4.3'
ARG ELIXIR_VSN='1.18.4'
ARG UID='9000'
ARG USER='ejabberd'
ARG HOME="opt/$USER"
@@ -9,7 +9,7 @@ ARG VERSION='master'
################################################################################
#' Compile ejabberdapi
FROM docker.io/golang:1.24-alpine AS api
FROM docker.io/golang:1.25-alpine AS api
RUN go install -v \
github.com/processone/ejabberd-api/cmd/ejabberd@master \
&& mv bin/ejabberd bin/ejabberdapi
+9 -2
View File
@@ -151,7 +151,12 @@ api_permissions:
from: ejabberd_web_admin
who: admin
what: "*"
"admin access":
"adhoc commands":
from: mod_adhoc_api
who: admin
what: "*"
"http access":
from: mod_http_api
who:
access:
allow:
@@ -192,6 +197,7 @@ shaper_rules:
modules:
mod_adhoc: {}
mod_adhoc_api: {}
mod_admin_extra: {}
mod_announce:
access: announce
@@ -206,7 +212,7 @@ modules:
mod_fail2ban: {}
mod_http_api: {}
mod_http_upload:
put_url: https://@HOST@:5443/upload
put_url: https://@HOST_URL_ENCODE@:5443/upload
custom_headers:
"Access-Control-Allow-Origin": "https://@HOST@"
"Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
@@ -232,6 +238,7 @@ modules:
default_room_options:
mam: true
mod_muc_admin: {}
mod_muc_occupantid: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
+125 -6
View File
@@ -121,6 +121,9 @@ export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS
export SCRIPT_DIR
# Only required for Erlang/OTP 25:
export ERL_FLAGS="$ERL_FLAGS -enable-feature maybe_expr"
set_dist_client()
{
[ -n "$ERL_DIST_PORT" ] && ERLANG_OPTS="$ERLANG_OPTS -dist_listen false"
@@ -195,7 +198,9 @@ livewarning()
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To exit and detach this shell from ejabberd, press:"
echo "To stop ejabberd gracefully:"
echo " ejabberd:stop()."
echo "To quit erlang immediately, press:"
echo " control+g and then q"
echo ""
echo "--------------------------------------------------------------------"
@@ -363,6 +368,13 @@ post_waiter_loop()
# allow sync calls
wait_status()
{
wait_status_node "$ERLANG_NODE" $1 $2 $3
}
wait_status_node()
{
CONNECT_NODE=$1
shift
# args: status try delay
# return: 0 OK, 1 KO
timeout="$2"
@@ -374,9 +386,9 @@ wait_status()
status="$1"
else
run_erl "$(uid ctl)" -hidden -noinput \
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
-s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
-extra "$CONNECT_NODE" $NO_TIMEOUT status > /dev/null
status="$?"
fi
done
@@ -385,19 +397,26 @@ wait_status()
exec_other_command()
{
exec_other_command_node $ERLANG_NODE "$@"
}
exec_other_command_node()
{
CONNECT_NODE=$1
shift
if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
|| [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
|| [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
run_erl "$(uid ctl)" -hidden -noinput \
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
-s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
-extra "$CONNECT_NODE" $NO_TIMEOUT "$@"
result=$?
case $result in
3) help;;
*) :;;
esac
exit $result
return $result
else
exec_ctl_over_http_socket "$@"
fi
@@ -439,6 +458,103 @@ cd "$SPOOL_DIR" || {
exit 6
}
printe()
{
printf "\n"
printf "\e[1;40;32m==> %s\e[0m\n" "$1"
}
## Function copied from tools/make-installers
user_agrees()
{
question="$*"
if [ -t 0 ]
then
printe "$question (y/n) [n]"
read -r response
case "$response" in
[Yy]|[Yy][Ee][Ss])
return 0
;;
[Nn]|[Nn][Oo]|'')
return 1
;;
*)
echo 'Please respond with "yes" or "no".'
user_agrees "$question"
;;
esac
else # Assume 'yes' if not running interactively.
return 0
fi
}
mnesia_change()
{
ERLANG_NODE_OLD="$1"
[ "$ERLANG_NODE_OLD" = "" ] \
&& echo "Error: Please provide the old erlang node name, for example:" \
&& echo " ejabberdctl mnesia_change ejabberd@oldmachine" \
&& exit 1
SPOOL_DIR_BACKUP=$SPOOL_DIR/$ERLANG_NODE_OLD-backup/
OLDFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE_OLD.backup
NEWFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE.backup
printe "This changes your mnesia database from node name '$ERLANG_NODE_OLD' to '$ERLANG_NODE'"
[ -d "$SPOOL_DIR_BACKUP" ] && printe "WARNING! A backup of old node already exists in $SPOOL_DIR_BACKUP"
if ! user_agrees "Do you want to proceed?"
then
echo 'Operation aborted.'
exit 1
fi
printe "Starting ejabberd with old node name $ERLANG_NODE_OLD ..."
exec_erl "$ERLANG_NODE_OLD" $EJABBERD_OPTS -detached
wait_status_node $ERLANG_NODE_OLD 0 30 2
result=$?
case $result in
1) echo "There was a problem starting ejabberd with the old erlang node name. " \
&& echo "Check for log errors in $EJABBERD_LOG_PATH" \
&& exit $result;;
*) :;;
esac
exec_other_command_node $ERLANG_NODE_OLD "status"
printe "Making backup of old database to file $OLDFILE ..."
mkdir $SPOOL_DIR_BACKUP
exec_other_command_node $ERLANG_NODE_OLD backup "$OLDFILE"
printe "Changing node name in new backup file $NEWFILE ..."
exec_other_command_node $ERLANG_NODE_OLD mnesia_change_nodename "$ERLANG_NODE_OLD" "$ERLANG_NODE" "$OLDFILE" "$NEWFILE"
printe "Stopping old ejabberd..."
exec_other_command_node $ERLANG_NODE_OLD "stop"
wait_status_node $ERLANG_NODE_OLD 3 30 2 && stop_epmd
printe "Moving old mnesia spool files to backup subdirectory $SPOOL_DIR_BACKUP ..."
mv $SPOOL_DIR/*.DAT $SPOOL_DIR_BACKUP
mv $SPOOL_DIR/*.DCD $SPOOL_DIR_BACKUP
mv $SPOOL_DIR/*.LOG $SPOOL_DIR_BACKUP
printe "Starting ejabberd with new node name $ERLANG_NODE ..."
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -detached
wait_status 0 30 2
exec_other_command "status"
printe "Installing fallback of new mnesia..."
exec_other_command install_fallback "$NEWFILE"
printe "Stopping new ejabberd..."
exec_other_command "stop"
wait_status 3 30 2 && stop_epmd
printe "Finished, now you can start ejabberd normally"
}
# main
case $1 in
start)
@@ -501,6 +617,9 @@ case $1 in
set_dist_client
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
;;
mnesia_change)
mnesia_change $2
;;
post_waiter)
post_waiter_waiting
;;
+340 -207
View File
@@ -18,89 +18,42 @@ on:
- 'priv/**'
- '**.md'
env:
DEV: _build/dev/rel/ejabberd
jobs:
tests:
name: Tests
############################################################### Compile #####
compile:
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
otp: ['25', '26', '27']
runs-on: ubuntu-24.04
services:
redis:
image: public.ecr.aws/docker/library/redis
ports:
- 6379:6379
otp: ['27']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Test shell scripts
if: matrix.otp == '27'
run: |
shellcheck test/ejabberd_SUITE_data/gencerts.sh
shellcheck tools/captcha.sh
shellcheck ejabberd.init.template
shellcheck -x ejabberdctl.template
- name: Get specific Erlang/OTP
uses: erlef/setup-beam@v1
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- name: Install MS SQL Server
run: |
docker run -d -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=ejabberd_Test1" \
-v $(pwd)/test/docker/db/mssql/initdb/initdb_mssql.sql:/initdb_mssql.sql:ro \
-v $(pwd)/sql/mssql.sql:/mssql.sql:ro \
-v $(pwd)/sql/mssql.new.sql:/mssql.new.sql:ro \
-p 1433:1433 --name ejabberd-mssql "mcr.microsoft.com/mssql/server:2019-latest"
sleep 10
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libexpat1-dev libgd-dev libpam0g-dev
libsqlite3-dev libwebp-dev libyaml-dev
- name: Prepare databases
run: |
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;"
mysql -u root -proot -e "CREATE USER 'ejabberd_test'@'localhost'
IDENTIFIED BY 'ejabberd_test';"
mysql -u root -proot -e "GRANT ALL ON ejabberd_test.*
TO 'ejabberd_test'@'localhost';"
pg_isready
sudo -u postgres psql -c "CREATE DATABASE ejabberd_test;"
sudo -u postgres psql -c "CREATE USER ejabberd_test
WITH PASSWORD 'ejabberd_test';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES
ON DATABASE ejabberd_test TO ejabberd_test;"
sudo -u postgres psql -c "GRANT ALL ON SCHEMA public TO ejabberd_test;"
sudo -u postgres psql -c "ALTER DATABASE ejabberd_test OWNER TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
TABLES IN SCHEMA public
TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
SEQUENCES IN SCHEMA public
TO ejabberd_test;"
- name: Prepare libraries
run: |
sudo apt-get -qq update
sudo apt-get -y purge libgd3 nginx
sudo apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
- name: Remove syntax_tools from release
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
- name: Cache Hex.pm
- name: Cache rebar3
uses: actions/cache@v4
with:
path: |
~/.cache/rebar3/
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
_build/default/lib/
key: ci-${{ matrix.otp }}-${{hashFiles('rebar.config')}}
- name: Compile
run: |
@@ -108,71 +61,196 @@ jobs:
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all \
--disable-elixir \
--disable-mssql \
--disable-odbc
--disable-elixir
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
make
tar -cvf /tmp/compiled.tar .
- uses: actions/upload-artifact@v5
with:
name: compiled-${{ matrix.otp }}
path: /tmp/compiled.tar
retention-days: 1
########################################################## Static Tests #####
static:
needs: compile
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: actions/download-artifact@v5
with:
name: compiled-${{ matrix.otp }}
- run: tar -xf compiled.tar
- name: Test shell scripts
run: |
shellcheck ejabberd.init.template
shellcheck -x ejabberdctl.template
shellcheck .vscode/relive.sh
shellcheck rel/setup-dev.sh
shellcheck rel/setup-relive.sh
shellcheck test/ejabberd_SUITE_data/gencerts.sh
shellcheck tools/captcha.sh
shellcheck tools/emacs-indent.sh
shellcheck tools/generate-doap.sh
shellcheck tools/prepare-tr.sh
shellcheck tools/rebar3-format.sh
- run: make install -s
- run: make hooks
- run: make options
- run: make xref
- run: make dialyzer
- run: make test-eunit
- run: make elvis
if: matrix.otp > '25'
- name: Check Production Release
run: |
make rel
RE=_build/prod/rel/ejabberd
$RE/bin/ejabberdctl start
$RE/bin/ejabberdctl started
$RE/bin/ejabberdctl stop
$RE/bin/ejabberdctl stopped
cat $RE/logs/ejabberd.log
grep -q "is stopped in" $RE/logs/ejabberd.log
- run: make install -s
- 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
######################################################### Dynamic Tests #####
- name: Run XMPP Interoperability Tests against CI server.
dynamic:
needs: compile
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.otp < '27'
with:
packages: libexpat1-dev libgd-dev libpam0g-dev
libsqlite3-dev libwebp-dev libyaml-dev
- uses: actions/download-artifact@v5
with:
name: compiled-${{ matrix.otp }}
- run: tar -xf compiled.tar
- name: Check production release
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: deploy, start, stop, check
- name: Start development release
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: deploy, no_tls, start, register
username: user123
- name: Run XMPP Interoperability Tests against CI server
if: matrix.otp == '27'
continue-on-error: true
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.5.0
uses: XMPP-Interop-Testing/xmpp-interop-tests-action@v1.7.2
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
adminAccountUsername: 'user123'
adminAccountPassword: 's0mePass'
disabledSpecifications: RFC6121,XEP-0030,XEP-0045,XEP-0054,XEP-0060,
XEP-0080,XEP-0115,XEP-0118,XEP-0215,XEP-0347,
XEP-0363,XEP-0384,XEP-0421
- name: Stop Development Release
- name: Stop development release
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: stop, check
- name: View production logs
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: logs
- name: View development logs
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: logs
##################################################### Common Test Suite #####
ct:
needs: compile
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
backend: [agnostic, extauth, ldap, mnesia, mysql, pgsql, redis, sqlite]
schema: [single, multi]
exclude:
- backend: agnostic
schema: single
- backend: extauth
schema: single
- backend: ldap
schema: single
- backend: mnesia
schema: single
- backend: redis
schema: single
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: actions/download-artifact@v5
with:
name: compiled-${{ matrix.otp }}
- run: tar -xf compiled.tar
- name: Prepare database
uses: ./.github/actions/manage-database
with:
for: ${{ matrix.backend }}
do: install, start, user, create
- name: Setup multihost SQL schema
if: matrix.schema == 'multi'
run: |
RE=_build/dev/rel/ejabberd
$RE/bin/ejabberdctl stop
$RE/bin/ejabberdctl stopped
cat $RE/logs/ejabberd.log
grep -q "is stopped in" $RE/logs/ejabberd.log
sed -i 's|multihost_schema, false|multihost_schema, true|g' \
test/suite.erl
- name: Run tests
id: ct
run: CT_BACKENDS=${{ matrix.backend }} make test
- name: Send to coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
(cd priv && ln -sf ../sql)
sed -i -e 's/ct:pal/ct:log/' test/suite.erl
COMMIT=`echo $GITHUB_SHA | cut -c 1-7`
DATE=`date +%s`
REF_NAME=`echo $GITHUB_REF_NAME | tr "/" "_"`
NODENAME=$DATE@$GITHUB_RUN_NUMBER-$GITHUB_ACTOR-$REF_NAME-$COMMIT
LABEL=`git show -s --format=%s | cut -c 1-30`
./rebar3 ct --name $NODENAME --label "$LABEL"
./rebar3 cover
DIAGNOSTIC=1 ./rebar3 as test coveralls send
- name: Check results
if: always() && (steps.ct.outcome != 'skipped')
@@ -184,20 +262,45 @@ jobs:
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View logs failures
if: failure() && steps.ctresults.outcome == 'failure'
- name: View logs
if: always()
run: |
echo "::group::ejabberd.log"
find logs/ -name ejabberd.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::error.log"
find logs/ -name error.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::exunit.log"
find logs/ -name exunit.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::suite.log (only failures)"
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
find logs/ -name error.log -exec cat '{}' ';'
find logs/ -name exunit.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::suite.log (complete)"
cat logs/suite.log
echo "::endgroup::"
- name: Send to coveralls
if: matrix.otp == '27'
- name: Upload CT logs
if: failure()
uses: actions/upload-artifact@v5
with:
name: ct-logs-${{ matrix.otp }}-${{ matrix.backend }}-${{ matrix.schema }}
path: _build/test/logs
retention-days: 14
############################################################# Coveralls #####
cover:
needs: ct
if: always()
runs-on: ubuntu-24.04-arm
steps:
- name: Finish parallel upload to coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DIAGNOSTIC=1 ./rebar3 as test coveralls send
curl -v -k https://coveralls.io/webhook \
--header "Content-Type: application/json" \
--data '{"repo_name":"$GITHUB_REPOSITORY",
@@ -205,96 +308,126 @@ jobs:
"payload":{"build_num":$GITHUB_RUN_ID,
"status":"done"}}'
- name: Check for changes to trigger schema upgrade test
uses: dorny/paths-filter@v3
id: filter
################################################################ Schema #####
schema:
needs: compile
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['27']
steps:
- uses: erlef/setup-beam@v1
with:
filters: |
sql:
- 'sql/**'
- 'src/mod_admin_update_sql.erl'
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- name: Prepare for schema upgrade test
id: prepupgradetest
if: ${{ steps.filter.outputs.sql == 'true' }}
run: |
[[ -d logs ]] && rm -rf logs
[[ -d _build/test/logs ]] && rm -rf _build/test/logs || true
sed -i 's|update_sql, false|update_sql, true|g' test/suite.erl
- name: Run DB tests on upgraded schema (mssql, mysql, pgsql)
run: CT_BACKENDS=mssql,mysql,pgsql make test
if: always() && steps.prepupgradetest.outcome != 'skipped'
id: ctupgradedschema
- name: Check results
if: always() && steps.ctupgradedschema.outcome != 'skipped'
run: |
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
ln `find logs/ -name suite.log` logs/suite.log
grep 'TEST COMPLETE' logs/suite.log
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View logs failures
if: failure() && steps.ctupgradedschema.outcome != 'skipped'
run: |
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
find logs/ -name error.log -exec cat '{}' ';'
find logs/ -name exunit.log -exec cat '{}' ';'
- name: Prepare new schema
run: |
[[ -d logs ]] && rm -rf logs
[[ -d _build/test/logs ]] && rm -rf _build/test/logs || true
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-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';"
sudo -u postgres psql -c "CREATE DATABASE ejabberd_test;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES
ON DATABASE ejabberd_test TO ejabberd_test;"
sudo -u postgres psql -c "GRANT ALL ON SCHEMA public TO ejabberd_test;"
sudo -u postgres psql -c "ALTER DATABASE ejabberd_test OWNER TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
TABLES IN SCHEMA public
TO ejabberd_test;"
sudo -u postgres psql ejabberd_test -c "GRANT ALL PRIVILEGES ON ALL
SEQUENCES IN SCHEMA public
TO ejabberd_test;"
sed -i 's|new_schema, false|new_schema, true|g' test/suite.erl
- name: Run DB tests on new schema (mssql, mysql, pgsql)
run: CT_BACKENDS=mssql,mysql,pgsql make test
id: ctnewschema
- name: Check results
if: always() && steps.ctnewschema.outcome != 'skipped'
run: |
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
ln `find logs/ -name suite.log` logs/suite.log
grep 'TEST COMPLETE' logs/suite.log
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View logs failures
if: failure() && steps.ctnewschema.outcome != 'skipped'
run: |
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
find logs/ -name error.log -exec cat '{}' ';'
find logs/ -name exunit.log -exec cat '{}' ';'
- name: Upload CT logs
if: failure()
uses: actions/upload-artifact@v4
- uses: actions/download-artifact@v5
with:
name: ejabberd-ct-logs-${{matrix.otp}}
#
# Appending the wildcard character ("*") is a trick to make
# "ejabberd-packages" the root directory of the uploaded ZIP file:
#
# https://github.com/actions/upload-artifact#upload-using-multiple-paths-and-exclusions
#
path: _build/test/logs
retention-days: 14
name: compiled-${{ matrix.otp }}
- run: tar -xf compiled.tar
###################################### multi-changed ##
- name: Prepare databases
uses: ./.github/actions/manage-database
with:
for: mysql, pgsql
do: install, start, user, create
- name: Prepare configuration
run: |
CT_BACKENDS=mysql,pgsql ./rebar3 ct \
--suite=test/ejabberd_SUITE --group=configtest_single
make dev
cp test/ejabberd_SUITE_data/ejabberd.yml ${{ env.DEV }}/conf/
cp _build/test/logs/last/*.yml ${{ env.DEV }}/database/
echo "define_macro: [CONFIGTEST_CONFIG: {modules: {mod_muc: {}}}]" \
> ${{ env.DEV }}/database/configtest.yml
cp _build/test/logs/last/*.pem ${{ env.DEV }}/conf/
cp _build/test/logs/last/*.pem ${{ env.DEV }}/database/
- name: Run ejabberd
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: start, update_sql, stop, logs, check
username: user2
- name: Dump multihost databases
uses: ./.github/actions/manage-database
with:
for: mysql, pgsql
do: dump
dump-suffix: multi-changed
######################################### multi-auto ##
- name: Prepare databases
uses: ./.github/actions/manage-database
with:
for: mysql, pgsql
do: drop, create
- name: Configure multihost schema
run: |
sed -i 's|MULTIHOST_SCHEMA|true|g' ${{ env.DEV }}/conf/ejabberd.yml
- name: Run ejabberd for multihost
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: start, stop, logs, check
username: user2
- name: Dump multihost databases
uses: ./.github/actions/manage-database
with:
for: mysql, pgsql
do: dump
dump-suffix: multi-auto
############################################ compare ##
- name: View SQL schemas (mysql)
run: |
perl test/ejabberd_SUITE_data/sql_sort.pl \
<mysql-multi-auto.sql >mysql-multi2.sql
perl test/ejabberd_SUITE_data/sql_sort.pl \
<mysql-multi-changed.sql >mysql-changed2.sql
echo "::group::differences multi-auto > multi-changed"
diff -u mysql-multi2.sql mysql-changed2.sql || echo ok
echo "::endgroup::"
echo "::group::multi-auto.sql"
cat mysql-multi-auto.sql
echo "::endgroup::"
echo "::group::multi-changed.sql"
cat mysql-multi-changed.sql
echo "::endgroup::"
- name: View SQL schemas (pgsql)
run: |
perl test/ejabberd_SUITE_data/sql_sort.pl \
<pgsql-multi-auto.sql >pgsql-multi2.sql
perl test/ejabberd_SUITE_data/sql_sort.pl \
<pgsql-multi-changed.sql >pgsql-changed2.sql
echo "::group::differences (multi-auto > multi-changed)"
diff -u pgsql-multi2.sql pgsql-changed2.sql || echo ok
echo "::endgroup::"
echo "::group::multi-auto.sql"
cat pgsql-multi-auto.sql
echo "::endgroup::"
echo "::group::multi-changed.sql"
cat pgsql-multi-changed.sql
echo "::endgroup::"
- name: View ejabberd logs
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: logs
+63 -17
View File
@@ -15,25 +15,32 @@ env:
IMAGE_NAME: ${{ github.repository }}
jobs:
container:
name: Container
runs-on: ubuntu-22.04
build:
runs-on: ubuntu-24.04${{ matrix.suffix }}
strategy:
matrix:
platform: [amd64, arm64]
include:
- platform: amd64
suffix:
- platform: arm64
suffix: -arm
permissions:
packages: write
steps:
- name: Check out repository code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Checkout ejabberd-contrib
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: processone/ejabberd-contrib
path: .ejabberd-modules/sources/ejabberd-contrib
- name: Log in to the Container registry
uses: docker/login-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -43,24 +50,19 @@ jobs:
id: gitdescribe
run: echo "ver=$(git describe --tags)" >> $GITHUB_OUTPUT
- name: Extract metadata (tags, labels) for Docker
- uses: docker/metadata-action@v5
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=--${{ matrix.platform }}
labels: |
org.opencontainers.image.revision=${{ steps.gitdescribe.outputs.ver }}
org.opencontainers.image.licenses=GPL-2.0
org.opencontainers.image.vendor=ProcessOne
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v6
- uses: docker/build-push-action@v6
with:
build-args: |
VERSION=${{ steps.gitdescribe.outputs.ver }}
@@ -69,6 +71,50 @@ jobs:
context: .
file: .github/container/Dockerfile
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
platforms: linux/${{ matrix.platform }}
push: true
tags: ${{ steps.meta.outputs.tags }}
merge:
needs: [build]
runs-on: ubuntu-24.04
outputs:
image-uri: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.merge.outputs.digest }}
steps:
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta-amd
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=--amd64
- uses: docker/metadata-action@v5
id: meta-arm
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=--arm64
- uses: docker/metadata-action@v5
id: meta-result
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- uses: int128/docker-manifest-create-action@v2
id: merge
with:
index-annotations: ${{ steps.metadata.outputs.labels }}
tags: ${{ steps.meta-result.outputs.tags }}
sources: |
${{ steps.meta-amd.outputs.tags }}
${{ steps.meta-arm.outputs.tags }}
- uses: dataaxiom/ghcr-cleanup-action@v1
with:
dry-run: true
delete-tags: '*--a??64'
delete-untagged: true
delete-ghost-images: true
delete-partial-images: true
delete-orphaned-images: true
+40 -13
View File
@@ -20,14 +20,25 @@ on:
jobs:
binaries:
name: Binaries
runs-on: ubuntu-22.04
runs-on: ubuntu-22.04${{ matrix.suffix }}
strategy:
matrix:
platform: [amd64, arm64]
include:
- platform: amd64
suffix:
- platform: arm64
suffix: -arm
steps:
- name: Check out repository code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Cache build directory
uses: actions/cache@v4
with:
path: ~/build/
key: ${{runner.os}}-ct-ng-1.27.0
key: installers-ubuntu-22.04-${{runner.arch}}-${{hashFiles('tools/make-binaries')}}
- name: Install prerequisites
run: |
sudo apt-get -qq update
@@ -40,24 +51,40 @@ jobs:
run: |
gem install --no-document --user-install fpm
echo $HOME/.local/share/gem/ruby/*/bin >> $GITHUB_PATH
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build binary archives
run: CHECK_DEPS=false tools/make-binaries
- name: Build DEB and RPM packages
run: tools/make-packages
- name: Build installers
run: tools/make-installers
- run: echo "rel_name_vsn=$(ls *.run | sed 's/-1-linux.*//g')" >> $GITHUB_OUTPUT
id: vsn
- name: Test RUN
uses: ./.github/actions/manage-ejabberd
with:
for: run
do: deploy, no_acme, start, register, stop, logs, check
username: user1
host: $(hostname --fqdn)
rel_name_vsn: ${{ steps.vsn.outputs.rel_name_vsn }}
- name: Test DEB
uses: ./.github/actions/manage-ejabberd
with:
for: deb
do: deploy, register, stop, logs
username: user1
host: $(hostname --fqdn)
rel_name_vsn: ${{ steps.vsn.outputs.rel_name_vsn }}
- name: Collect packages
run: |
mkdir ejabberd-packages
mv ejabberd_*.deb ejabberd-*.rpm ejabberd-*.run ejabberd-packages
- name: Upload packages
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ejabberd-packages
name: ejabberd-packages-${{ matrix.platform }}
#
# Appending the wildcard character ("*") is a trick to make
# "ejabberd-packages" the root directory of the uploaded ZIP file:
@@ -74,11 +101,11 @@ jobs:
if: github.ref_type == 'tag'
steps:
- name: Download packages
uses: actions/download-artifact@v4
with:
name: ejabberd-packages
uses: actions/download-artifact@v5
- name: Draft Release
uses: softprops/action-gh-release@v2
with:
draft: true
files: ejabberd-packages/*
files: |
ejabberd-packages-amd64/*
ejabberd-packages-arm64/*
+115 -255
View File
@@ -26,176 +26,129 @@ on:
jobs:
################################################################ Rebars #####
rebars:
name: Rebars
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
otp: ['20', '25', '26', '27']
otp: ['25', '26', '27', '28']
rebar: ['rebar', 'rebar3']
exclude:
- otp: '27'
rebar: 'rebar'
runs-on: ubuntu-24.04
- otp: '28'
rebar: 'rebar'
container:
image: public.ecr.aws/docker/library/erlang:${{ matrix.otp }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Get compatible Rebar binaries
if: matrix.otp < 24
- name: Get recent compatible Rebar binaries
if: matrix.otp < 25
run: |
rm rebar
rm rebar3
wget https://github.com/processone/ejabberd/raw/21.12/rebar
wget https://github.com/processone/ejabberd/raw/21.12/rebar3
wget https://github.com/processone/ejabberd/raw/24.12/rebar
wget https://github.com/processone/ejabberd/raw/24.12/rebar3
chmod +x rebar
chmod +x rebar3
- name: Prepare libraries
run: |
apt-get -qq update
apt-get purge -y libgd3 nginx
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
apt-get -q -y install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
- name: Cache Hex.pm
- name: Cache rebar3
if: matrix.rebar == 'rebar3'
uses: actions/cache@v4
with:
path: |
~/.cache/rebar3/
key: ${{matrix.otp}}-${{hashFiles('rebar.config')}}
- name: Get compatible Rebar binaries
if: matrix.rebar == 'rebar3' && matrix.otp < 21
run: rebar3 unlock eredis
_build/default/lib/
key: runtime-${{ matrix.otp }}-${{matrix.rebar}}-${{hashFiles('rebar.config')}}
- name: Compile
run: |
./autogen.sh
./configure --with-rebar=./${{ matrix.rebar }} \
--prefix=/tmp/ejabberd \
--with-min-erlang=9.0.5 \
--enable-all \
--disable-elixir \
--disable-tools \
--disable-odbc
--disable-elixir
make
- run: make hooks
- run: make options
- run: make xref
- run: make dialyzer
- run: make elvis
if: matrix.otp > '25' && matrix.rebar == 'rebar3'
- name: Prepare rel (rebar2)
if: matrix.rebar == 'rebar'
run: |
mkdir -p _build/prod && ln -s `pwd`/rel/ _build/prod/rel
mkdir -p _build/dev && ln -s `pwd`/rel/ _build/dev/rel
- name: Production
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: deploy, start, register, stop, logs, check
tool: ${{ matrix.rebar }}
username: user1
- name: Run rel
run: |
make rel
_build/prod/rel/ejabberd/bin/ejabberdctl start \
&& _build/prod/rel/ejabberd/bin/ejabberdctl started
_build/prod/rel/ejabberd/bin/ejabberdctl register user1 localhost s0mePass
_build/prod/rel/ejabberd/bin/ejabberdctl registered_users localhost > registered.log
_build/prod/rel/ejabberd/bin/ejabberdctl stop \
&& _build/prod/rel/ejabberd/bin/ejabberdctl stopped
- name: Development
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: deploy, start, register, stop, logs, check
tool: ${{ matrix.rebar }}
username: user2
- name: Run dev
run: |
make dev
_build/dev/rel/ejabberd/bin/ejabberdctl start \
&& _build/dev/rel/ejabberd/bin/ejabberdctl started
_build/dev/rel/ejabberd/bin/ejabberdctl register user2 localhost s0mePass
_build/dev/rel/ejabberd/bin/ejabberdctl registered_users localhost >> registered.log
_build/dev/rel/ejabberd/bin/ejabberdctl stop \
&& _build/dev/rel/ejabberd/bin/ejabberdctl stopped
- name: Installed
uses: ./.github/actions/manage-ejabberd
with:
for: install
do: deploy, start, register, stop, logs, check
tool: ${{ matrix.rebar }}
username: user3
- name: Run install
run: |
make install
/tmp/ejabberd/sbin/ejabberdctl start \
&& /tmp/ejabberd/sbin/ejabberdctl started
/tmp/ejabberd/sbin/ejabberdctl register user3 localhost s0mePass
/tmp/ejabberd/sbin/ejabberdctl registered_users localhost >> registered.log
/tmp/ejabberd/sbin/ejabberdctl stop \
&& /tmp/ejabberd/sbin/ejabberdctl stopped
- name: View logs
run: |
echo "===> Registered:"
cat registered.log
echo "===> Prod:"
cat _build/prod/rel/ejabberd/logs/*
echo "===> Dev:"
cat _build/dev/rel/ejabberd/logs/*
echo "===> Install:"
cat /tmp/ejabberd/var/log/ejabberd/*
- name: Check logs
run: |
grep -q '^user1$' registered.log
grep -q '^user2$' registered.log
grep -q '^user3$' registered.log
grep -q 'is started' _build/prod/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/prod/rel/ejabberd/logs/ejabberd.log
test $(find _build/prod/rel/ -empty -name error.log)
grep -q 'is started' _build/dev/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/dev/rel/ejabberd/logs/ejabberd.log
test $(find _build/dev/rel/ -empty -name error.log)
grep -q 'is started' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
grep -q 'is stopped' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
test $(find /tmp/ejabberd/var/log/ejabberd/ -empty -name error.log)
- name: View logs failures
if: always()
run: |
cat _build/prod/rel/ejabberd/logs/ejabberd.log
cat _build/prod/rel/ejabberd/logs/error.log
cat _build/dev/rel/ejabberd/logs/ejabberd.log
cat _build/dev/rel/ejabberd/logs/error.log
cat /tmp/ejabberd/var/log/ejabberd/ejabberd.log
cat /tmp/ejabberd/var/log/ejabberd/error.log
####################################################### Rebar3 + Elixir #####
rebar3-elixir:
name: Rebar3+Elixir
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
elixir: ['1.13', '1.14', '1.15', '1.16', '1.17', '1.18']
runs-on: ubuntu-24.04
elixir: ['1.14', '1.18', '1.19']
container:
image: public.ecr.aws/docker/library/elixir:${{ matrix.elixir }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Prepare libraries
run: |
apt-get -qq update
apt-get -y purge libgd3 nginx
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
apt-get -q -y install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
- name: Enable Module.Example and an Elixir dependency
run: |
sed -i "s|^modules:|modules:\n 'Ejabberd.Module.Example': {}|g" ejabberd.yml.example
sed -i "s|^modules:|modules:\n 'Ejabberd.Module.Example': {}|g" \
ejabberd.yml.example
cat ejabberd.yml.example
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
- name: Cache hex.pm
uses: actions/cache@v4
with:
path: |
~/.cache/rebar3/
key: ${{matrix.elixir}}-${{hashFiles('rebar.config')}}
key: runtime-${{matrix.elixir}}-${{hashFiles('rebar.config')}}
- name: Install Hex and Rebar3 manually on older Elixir
if: matrix.elixir <= '1.14'
if: matrix.elixir < '1.15'
run: |
mix local.hex --force
mix local.rebar --force
@@ -205,105 +158,59 @@ jobs:
./autogen.sh
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all \
--disable-odbc
make
--enable-all
make scripts deps
./rebar3 eunit --verbose
- run: make hooks
- run: make options
- run: make xref
#- run: make dialyzer
- run: make elvis
if: matrix.otp > '25'
- name: Run rel
run: |
make rel
_build/prod/rel/ejabberd/bin/ejabberdctl start \
&& _build/prod/rel/ejabberd/bin/ejabberdctl started
_build/prod/rel/ejabberd/bin/ejabberdctl register user1 localhost s0mePass
_build/prod/rel/ejabberd/bin/ejabberdctl registered_users localhost > registered.log
_build/prod/rel/ejabberd/bin/ejabberdctl stop \
&& _build/prod/rel/ejabberd/bin/ejabberdctl stopped
- name: Production
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: deploy, start, register, stop, logs, check
username: user1
- name: Run dev
run: |
make dev
_build/dev/rel/ejabberd/bin/ejabberdctl start \
&& _build/dev/rel/ejabberd/bin/ejabberdctl started
_build/dev/rel/ejabberd/bin/ejabberdctl register user2 localhost s0mePass
_build/dev/rel/ejabberd/bin/ejabberdctl registered_users localhost >> registered.log
_build/dev/rel/ejabberd/bin/ejabberdctl stop \
&& _build/dev/rel/ejabberd/bin/ejabberdctl stopped
- name: Release
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: deploy, start, register, stop, logs, check
username: user2
- name: Run install
run: |
make install
/tmp/ejabberd/sbin/ejabberdctl start \
&& /tmp/ejabberd/sbin/ejabberdctl started
/tmp/ejabberd/sbin/ejabberdctl register user3 localhost s0mePass
/tmp/ejabberd/sbin/ejabberdctl registered_users localhost >> registered.log
/tmp/ejabberd/sbin/ejabberdctl stop \
&& /tmp/ejabberd/sbin/ejabberdctl stopped
- name: Installed
uses: ./.github/actions/manage-ejabberd
with:
for: install
do: deploy, start, register, stop, logs, check
username: user3
- name: View logs
if: always()
run: |
echo "===> Registered:"
cat registered.log
echo "===> Prod:"
cat _build/prod/rel/ejabberd/logs/*
echo "===> Dev:"
cat _build/dev/rel/ejabberd/logs/*
echo "===> Install:"
cat /tmp/ejabberd/var/log/ejabberd/*
- name: Check logs
if: always()
run: |
grep -q '^user1$' registered.log
grep -q '^user2$' registered.log
grep -q '^user3$' registered.log
grep -q 'is started' _build/prod/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/prod/rel/ejabberd/logs/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' _build/prod/rel/ejabberd/logs/ejabberd.log
test $(find _build/prod/ -empty -name error.log)
grep -q 'is started' _build/dev/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/dev/rel/ejabberd/logs/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' _build/dev/rel/ejabberd/logs/ejabberd.log
test $(find _build/dev/ -empty -name error.log)
grep -q 'is started' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
grep -q 'is stopped' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
test $(find /tmp/ejabberd/var/log/ejabberd/ -empty -name error.log)
- name: View logs failures
if: failure()
run: |
cat _build/prod/rel/ejabberd/logs/ejabberd.log
cat _build/prod/rel/ejabberd/logs/error.log
cat _build/dev/rel/ejabberd/logs/ejabberd.log
cat _build/dev/rel/ejabberd/logs/error.log
cat /tmp/ejabberd/var/log/ejabberd/ejabberd.log
cat /tmp/ejabberd/var/log/ejabberd/error.log
################################################################### Mix #####
mix:
name: Mix
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
elixir: ['1.13', '1.14', '1.15', '1.16', '1.17', '1.18']
runs-on: ubuntu-24.04
elixir: ['1.14', '1.18', '1.19']
container:
image: public.ecr.aws/docker/library/elixir:${{ matrix.elixir }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Prepare libraries
run: |
apt-get -qq update
apt-get -y purge libgd3 nginx
apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
apt-get -q -y install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
- name: Remove Elixir Matchers
- name: Remove Elixir matchers
run: |
echo "::remove-matcher owner=elixir-mixCompileWarning::"
echo "::remove-matcher owner=elixir-credoOutputDefault::"
@@ -313,20 +220,21 @@ jobs:
- name: Enable Module.Example and an Elixir dependency
run: |
sed -i "s|^modules:|modules:\n 'Ejabberd.Module.Example': {}|g" ejabberd.yml.example
sed -i "s|^modules:|modules:\n 'Ejabberd.Module.Example': {}|g" \
ejabberd.yml.example
cat ejabberd.yml.example
sed -i 's|^{deps, \(.*\)|{deps, \1\n {decimal, ".*", {git, "https://github.com/ericmj/decimal", {branch, "main"}}}, |g' rebar.config
cat rebar.config
- name: Cache Hex.pm
- name: Cache hex.pm
uses: actions/cache@v4
with:
path: |
~/.hex/
key: ${{matrix.elixir}}-${{hashFiles('mix.exs')}}
key: runtime-${{matrix.elixir}}-${{hashFiles('mix.exs')}}
- name: Install Hex and Rebar3 manually on older Elixir
if: matrix.elixir <= '1.14'
if: matrix.elixir < '1.15'
run: |
mix local.hex --force
mix local.rebar --force
@@ -339,80 +247,32 @@ jobs:
--enable-all
make
- run: make hooks
- run: make options
- run: make xref
- run: make dialyzer
- run: make elvis
if: matrix.otp > '25'
- run: make edoc
if: matrix.elixir >= '1.14'
- name: Run rel
run: |
make rel
_build/prod/rel/ejabberd/bin/ejabberdctl start \
&& _build/prod/rel/ejabberd/bin/ejabberdctl started
_build/prod/rel/ejabberd/bin/ejabberdctl register user1 localhost s0mePass
_build/prod/rel/ejabberd/bin/ejabberdctl registered_users localhost > registered.log
_build/prod/rel/ejabberd/bin/ejabberdctl stop \
&& _build/prod/rel/ejabberd/bin/ejabberdctl stopped
- name: Production
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: deploy, start, register, stop, logs, check
username: user1
- name: Run dev
run: |
make dev
_build/dev/rel/ejabberd/bin/ejabberdctl start \
&& _build/dev/rel/ejabberd/bin/ejabberdctl started
_build/dev/rel/ejabberd/bin/ejabberdctl register user2 localhost s0mePass
_build/dev/rel/ejabberd/bin/ejabberdctl registered_users localhost >> registered.log
_build/dev/rel/ejabberd/bin/ejabberdctl stop \
&& _build/dev/rel/ejabberd/bin/ejabberdctl stopped
- name: Development
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: deploy, start, register, stop, logs, check
username: user2
- name: Run install
run: |
make install
/tmp/ejabberd/sbin/ejabberdctl start \
&& /tmp/ejabberd/sbin/ejabberdctl started
/tmp/ejabberd/sbin/ejabberdctl register user3 localhost s0mePass
/tmp/ejabberd/sbin/ejabberdctl registered_users localhost >> registered.log
/tmp/ejabberd/sbin/ejabberdctl stop \
&& /tmp/ejabberd/sbin/ejabberdctl stopped
- name: View logs
if: always()
run: |
echo "===> Registered:"
cat registered.log
echo "===> Prod:"
cat _build/prod/rel/ejabberd/logs/*
echo "===> Dev:"
cat _build/dev/rel/ejabberd/logs/*
echo "===> Install:"
cat /tmp/ejabberd/var/log/ejabberd/*
- name: Check logs
if: always()
run: |
grep -q '^user1$' registered.log
grep -q '^user2$' registered.log
grep -q '^user3$' registered.log
grep -q 'is started' _build/prod/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/prod/rel/ejabberd/logs/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' _build/prod/rel/ejabberd/logs/ejabberd.log
test $(find _build/prod/ -empty -name error.log)
grep -q 'is started' _build/dev/rel/ejabberd/logs/ejabberd.log
grep -q 'is stopped' _build/dev/rel/ejabberd/logs/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' _build/dev/rel/ejabberd/logs/ejabberd.log
test $(find _build/dev/ -empty -name error.log)
grep -q 'is started' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
grep -q 'is stopped' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
grep -q 'Stopping Ejabberd.Module.Example' /tmp/ejabberd/var/log/ejabberd/ejabberd.log
test $(find /tmp/ejabberd/var/log/ejabberd/ -empty -name error.log)
- name: View logs failures
if: failure()
run: |
cat _build/prod/rel/ejabberd/logs/ejabberd.log
cat _build/prod/rel/ejabberd/logs/error.log
cat _build/dev/rel/ejabberd/logs/ejabberd.log
cat _build/dev/rel/ejabberd/logs/error.log
cat /tmp/ejabberd/var/log/ejabberd/ejabberd.log
cat /tmp/ejabberd/var/log/ejabberd/error.log
- name: Installed
uses: ./.github/actions/manage-ejabberd
with:
for: install
do: deploy, start, register, stop, logs, check
username: user3
+183
View File
@@ -0,0 +1,183 @@
name: Weekly
on:
schedule:
- cron: '30 9 * * 0'
jobs:
############################################################### Compile #####
test:
runs-on: ubuntu-24.04-arm
strategy:
matrix:
otp: ['25', '26', '27', '28']
steps:
- uses: actions/checkout@v5
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
hexpm-mirrors: |
https://cdn.jsdelivr.net/hex
https://builds.hex.pm
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libexpat1-dev libgd-dev libpam0g-dev
libsqlite3-dev libwebp-dev libyaml-dev
- name: Compile
run: |
./autogen.sh
./configure --with-rebar=./rebar3 \
--prefix=/tmp/ejabberd \
--enable-all \
--disable-elixir \
--disable-mssql \
--disable-odbc
sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
make
########################################################## Static Tests #####
- name: Test shell scripts
run: |
shellcheck ejabberd.init.template
shellcheck -x ejabberdctl.template
shellcheck .vscode/relive.sh
shellcheck rel/setup-dev.sh
shellcheck rel/setup-relive.sh
shellcheck test/ejabberd_SUITE_data/gencerts.sh
shellcheck tools/captcha.sh
shellcheck tools/emacs-indent.sh
shellcheck tools/generate-doap.sh
shellcheck tools/prepare-tr.sh
shellcheck tools/rebar3-format.sh
- run: make hooks
- run: make options
- run: make xref
- run: make dialyzer
- run: make test-eunit
- run: make elvis
if: matrix.otp > '25'
- run: make install -s
######################################################### Dynamic Tests #####
- name: Check production release
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: deploy, start, stop, check
- name: Start development release
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: deploy, no_tls, start, register
username: user123
- name: Stop development release
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: stop, check
- name: View production logs
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: prod
do: logs
- name: View development logs
if: always()
uses: ./.github/actions/manage-ejabberd
with:
for: dev
do: logs
##################################################### Common Test Suite #####
- name: Prepare database
uses: ./.github/actions/manage-database
with:
for: mysql, pgsql, redis
do: install, start, user, create
- name: Setup multihost SQL schema
run: |
sed -i 's|new_schema, false|new_schema, true|g' test/suite.erl
- name: Run tests
id: ct
run: make test
- name: Send to coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DIAGNOSTIC=1 ./rebar3 as test coveralls send
- name: Check results
if: always() && (steps.ct.outcome != 'skipped')
id: ctresults
run: |
[[ -d _build ]] && ln -s _build/test/logs/last/ logs || true
ln `find logs/ -name suite.log` logs/suite.log
grep 'TEST COMPLETE' logs/suite.log
grep -q 'TEST COMPLETE,.* 0 failed' logs/suite.log
test $(find logs/ -empty -name error.log)
- name: View logs
if: always()
run: |
echo "::group::ejabberd.log"
find logs/ -name ejabberd.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::error.log"
find logs/ -name error.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::exunit.log"
find logs/ -name exunit.log -exec cat '{}' ';'
echo "::endgroup::"
echo "::group::suite.log (only failures)"
cat logs/suite.log | awk \
'BEGIN{RS="\n=case";FS="\n"} /=result\s*failed/ {print "=case" $0}'
echo "::endgroup::"
echo "::group::suite.log (complete)"
cat logs/suite.log
echo "::endgroup::"
- name: Upload CT logs
if: failure()
uses: actions/upload-artifact@v5
with:
name: ct-logs-${{ matrix.otp }}
path: _build/test/logs
retention-days: 14
############################################################# Coveralls #####
cover:
needs: test
if: always()
runs-on: ubuntu-24.04-arm
steps:
- name: Finish parallel upload to coveralls
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -v -k https://coveralls.io/webhook \
--header "Content-Type: application/json" \
--data '{"repo_name":"$GITHUB_REPOSITORY",
"repo_token":"$GITHUB_TOKEN",
"payload":{"build_num":$GITHUB_RUN_ID,
"status":"done"}}'
+198
View File
@@ -1,3 +1,201 @@
## Version 25.10
#### Ad-hoc Commands
- `mod_configure`: New ad-hoc commands that were missing from XEP-0133
- `mod_adhoc_api`: Add support for asynchronous command calling
- `mod_adhoc_api`: If argument is a list of jids, type is `jid-multi`
- `mod_adhoc_api`: If field has several values, type is `text-multi`
#### API Commands
- Add commands argument type `binary_or_list`
- `mod_http_api`: Format sub elements for tuples from maps
- `mod_admin_extra`: Improve roster API commands documentation
- `mod_announce`: New API commands, reusing existing ad-hoc functions
- `ejabberd_admin`: New API command `restart_kindly`, improve `stop_kindly`
- `mod_admin_extra`: New API commands `list_banned` and `count_banned`
- `mod_admin_extra`: Improve API command `status_list`: support for status to be a list
- `mod_muc_admin`: New API commands `muc_get_registered_nick` and nicks ([#4468](https://github.com/processone/ejabberd/issues/4468))
- Use `mod_private:del_data` in `unban_account` API command
#### Configuration
- Rename `New` SQL schema to `Multihost`, and `Default` to `Singlehost` ([#4456](https://github.com/processone/ejabberd/issues/4456))
- Add config transformer from `use_new_schema` -> `sql_multihost_schema`
- `mod_sip`: Fix problem parsing `via` in `yconf` library ([#4444](https://github.com/processone/ejabberd/issues/4444))
#### Erlang/OTP support
- Enable feature `maybe_expr` in the compiler for Erlang/OTP 26 ([#4459](https://github.com/processone/ejabberd/issues/4459))
- Enable feature `maybe_expr` also in the runtime for Erlang/OTP 25
- Runtime: Remove Erlang 24 which won't work anymore with `maybe_expr`
- Remove `EX_RULE` and `EX_STACK` macros only used with ancient erlang
#### GitHub Workflows
- CI: Bump XMPP-Interop-Testing/xmpp-interop-tests-action ([#4469](https://github.com/processone/ejabberd/issues/4469))
- CI: Don't care to include commit details in the CT logs HTML page
- CI and Runtime: Reorganize steps to run in parallel, and ARM runner ([#4460](https://github.com/processone/ejabberd/issues/4460))
- Add local composite actions to manage ejabberd and databases
- Container: Build ARM in native runner instead of QEMU, merge and clean
- Installers: Generate ARM installers in native runner
- Tests: Run agnostic-database tests only once, not for every backend
- Tests: The odbc backend is not actually used in Commont Tests
- Weekly: New workflow that condenses CI, test all erlang without caching
#### Installers and Container
- Bump Erlang/OTP version to 27.3.4.3 in installers and container
- Bump Expat 2.7.3, OpenSSL 3.5.4, unixODBC 2.3.14 in installers
#### MUC
- `mod_mam`: New option `archive_muc_as_mucsub`
- `mod_muc`: Check if room is hibernated before calling mod_muc process
- `mod_muc`: Update implementation of XEP-0317 Hats to version 0.3.1 ([#4380](https://github.com/processone/ejabberd/issues/4380))
- `mod_muc`: Make mod_muc_sql properly handle new hats data ([#4380](https://github.com/processone/ejabberd/issues/4380))
- `mod_muc_room`: Don't require password if user is owner of room
- `mod_muc_admin`: Use in WebAdmin the new API commands that get nick registers
#### Core and Modules
- `ejabberd_http_ws`: Pass HTTP headers from WS to C2S connection ([#4471](https://github.com/processone/ejabberd/issues/4471))
- `ejabberd_listener`: Properly pass `send_timeout` option to listener sockets
- `ejabberdctl`: When ping returns pang, return also status code 1 ([#4327](https://github.com/processone/ejabberd/issues/4327))
- `ext_mod`: Print module status message after installation
- `misc`: json_encode should always call json with our filter
- `mod_admin_update_sql`: Use same index name than when creating database
- `mod_block_strangers`: Clarify `access` and `captcha` documentation ([#4221](https://github.com/processone/ejabberd/issues/4221))
- `mod_http_upload`: Encode URL before parsing, as done before `bba1a1e3c` ([#4450](https://github.com/processone/ejabberd/issues/4450))
- `mod_private`: Add `del_data/3`, `get_users_with_data/2`, `count_users_with_data/2`
- `mod_pubsub`: Don't catch `exit:{aborted, _}` inside mnesia transactions
- `mod_push`: Run new hook `push_send_notification` ([#4383](https://github.com/processone/ejabberd/issues/4383))
- WebAdmin: Respect newline and whitespace characters in results
## Version 25.08
#### API Commands
- `ban_account`: Run `sm_kick_user` event when kicking account ([#4415](https://github.com/processone/ejabberd/issues/4415))
- `ban_account`: No need to change password ([#4415](https://github.com/processone/ejabberd/issues/4415))
- `mnesia_change`: New command in `ejabberdctl` script that helps changing the mnesia node name
#### Configuration
- Rename `auth_password_types_hidden_in_scram1` option to `auth_password_types_hidden_in_sasl1`
- `econf`: If a host in configuration is encoded IDNA, decode it ([#3519](https://github.com/processone/ejabberd/issues/3519))
- `ejabberd_config`: New predefined keyword `HOST_URL_ENCODE`
- `ejabberd.yml.example`: Use `HOST_URL_ENCODE` to handle case when vhost is non-latin1
- `mod_conversejs`: Add option `conversejs_plugins` ([#4413](https://github.com/processone/ejabberd/issues/4413))
- `mod_matrix_gw`: Add `leave_timeout` option ([#4386](https://github.com/processone/ejabberd/issues/4386))
#### Documentation and Tests
- `COMPILE.md`: Mention dependencies and add link to Docs ([#4431](https://github.com/processone/ejabberd/issues/4431))
- `ejabberd_doc`: Document commands tags for modules
- CI: bump XMPP-Interop-Testing/xmpp-interop-tests-action ([#4425](https://github.com/processone/ejabberd/issues/4425))
- Runtime: Raise the minimum Erlang tested to Erlang/OTP 24
#### Installers and Container
- Bump Erlang/OTP version to 27.3.4.2
- Bump OpenSSL version to 3.5.2
- `make-binaries`: Disable Linux-PAM's `logind` support
#### Core and Modules
- Bump `p1_acme` to fix `'AttributePKCS-10'` and OTP 28 ([processone/p1_acme#4](https://github.com/processone/p1_acme/issues/4))
- Prevent loops in `xml_compress:decode` with corrupted data
- `ejabberd_auth_mnesia`: Fix issue with filtering duplicates in `get_users()`
- `ejabberd_listener`: Add secret in temporary unix domain socket path ([#4422](https://github.com/processone/ejabberd/issues/4422))
- `ejabberd_listener`: Log error when cannot set definitive unix socket ([#4422](https://github.com/processone/ejabberd/issues/4422))
- `ejabberd_listener`: Try to create provisional socket in final directory ([#4422](https://github.com/processone/ejabberd/issues/4422))
- `ejabberd_logger`: Print log lines colorized in console when using rebar3
- `mod_conversejs`: Ensure assets_path ends in `/` as required by Converse ([#4414](https://github.com/processone/ejabberd/issues/4414))
- `mod_conversejs`: Ensure plugins URL is separated with `/` ([#4413](https://github.com/processone/ejabberd/issues/4413))
- `mod_http_upload`: Encode URLs into IDNA when showing to XMPP client ([#3519](https://github.com/processone/ejabberd/issues/3519))
- `mod_matrix_gw`: Add support for null values in `is_canonical_json` ([#4421](https://github.com/processone/ejabberd/issues/4421))
- `mod_matrix_gw`: Don't send empty direct Matrix messages ([#4420](https://github.com/processone/ejabberd/issues/4420))
- `mod_matrix_gw`: Matrix gateway updates
- `mod_muc`: Report db failures when restoring rooms
- `mod_muc`: Unsubscribe users from members-only rooms when expelled ([#4412](https://github.com/processone/ejabberd/issues/4412))
- `mod_providers`: New module to serve easily XMPP Providers files
- `mod_register`: Don't duplicate welcome subject and message
- `mod_scram_upgrade`: Fix format of passwords updates
- `mod_scram_upgrade`: Only offer upgrades to methods that aren't already stored
## Version 25.07
#### Security fix
- `ext_mod`: Add temporary workaround for zip including absolute path
#### Compilation
- Raise the minimum Elixir tested version to 1.14.0 ([#4281](https://github.com/processone/ejabberd/issues/4281))
- Raise Erlang/OTP minimum requirement to 25.0 ([#4281](https://github.com/processone/ejabberd/issues/4281))
- `configure.ac`: Allow to specify minimal erlang version using `--with-min-erlang`
- `Makefile.in`: Add target `test-<group>`
- `rebar3-format.sh`: Replace csplit with perl
- Container: Bump Erlang/OTP 27.3.4.1, Elixir 1.18.4
- Installers: Bump Erlang/OTP 27.3.4.1, Elixir 1.18.4, libexpat 2.7.1, OpenSSL 3.5.1
#### Configuration and Tests
- Add `rest_proxy*` options to configure proxy used by rest module
- `ejabberd_c2s`: Add `auth_password_types_hidden_in_scram1` option
- `ejabberd_http`: Remove unused `default_host` option and state element
- `ejabberd_http`: New option `hosts_alias` and function `resolve_host_alias/1` ([#4400](https://github.com/processone/ejabberd/issues/4400))
- New predefined keywords: `CONFIG_PATH` and `LOG_PATH`
- Fix macro used in string options when defined in env var
- Use auxiliary function to get `$HOME`, use Mnesia directory when not set ([#4402](https://github.com/processone/ejabberd/issues/4402))
- `ejabberd_config`: Better `lists:uniq` substitute
- Tests: update readme and compose to work with current sw versions
- Update Elvis to 4.1.1, fix some warnings and enable their tests
#### Erlang/OTP 28 support
- Add workaround in `p1_acme` for Jose 1.11.10 not supporting OTP 28 `ecPrivkeyVer1` ([#4393](https://github.com/processone/ejabberd/issues/4393))
- Bump `fast_xml` and `xmpp` for improved Erlang/OTP 28 support
- Bump `xmpp` and `p1_acme` patched with Erlang/OTP 28 support
- Fix `make options` in Erlang/OTP 28 ([#4352](https://github.com/processone/ejabberd/issues/4352))
- Fix crash in `rebar3 cover` with Erlang/OTP 28 ([#4353](https://github.com/processone/ejabberd/issues/4353))
- Rebar/Rebar3: Update binaries to work with Erlang/OTP 25-28 ([#4354](https://github.com/processone/ejabberd/issues/4354))
- CI and Runtime: Add Erlang/OTP 28 to the versions matrix
#### SQL
- Fix mnesia to sql exporter after changes to auth tables
- Update code for switching to new schema type to users table changes
- Add mssql specific implementation of `delete_old_mam_messages`
- Make `delete_old_mam_messages_batch` work with sqlite
- `ejabberd_sm_sql`: Use misc:encode_pid/1
- `mysql.sql`: Fix typo in commit 7862c6a when creating users table
- `pg.sql`: Fix missing comma in postgres schema ([#4409](https://github.com/processone/ejabberd/issues/4409))
#### Core and Modules
- `ejabberd_s2s_in`: Allow S2S connections to accept client certificates that have only server purpose ([#4392](https://github.com/processone/ejabberd/issues/4392))
- `ext_mod`: Recommend to write README.md instead txt (processone/ejabberd-contrib#363)
- `ext_mod`: Support library path installed from Debian (processone/ejabberd-contrib#363)
- `ext_mod`: When upgrading module, clean also the compiled directories
- `gen_mod`: Add support to prepare module stopping before actually stopping any module
- `mod_antispam`: Imported from ejabberd-contrib and improved ([#4373](https://github.com/processone/ejabberd/issues/4373))
- `mod_auth_fast`: Clear tokens on kick, change pass and unregister ([#4397](https://github.com/processone/ejabberd/issues/4397))([#4398](https://github.com/processone/ejabberd/issues/4398))([#4399](https://github.com/processone/ejabberd/issues/4399))
- `mod_conversejs`: Add link in WebAdmin to local Converse if configured
- `mod_mam`: Present mam full text search in xep-431 compatible way
- `mod_mam_mnesia`: Handle objects that don't need conversion in `transform/0`
- `mod_matrix_gw`: Don't send empty messages in Matrix rooms ([#4385](https://github.com/processone/ejabberd/issues/4385))
- `mod_matrix_gw`: Support older Matrix rooms versions starting from version 4
- `mod_matrix_gw`: When encoding JSON, handle term that is key-value list ([#4379](https://github.com/processone/ejabberd/issues/4379))
- `mod_matrix_gw_s2s`: Fix key validation in `check_signature`
- `mod_mix` and `mod_muc_rtbl`: Support list of IDs in `pubsub-items-retract` (processone/xmpp#100)
- `mod_pubsub_serverinfo`: Imported module from ejabberd-contrib ([#4408](https://github.com/processone/ejabberd/issues/4408))
- `mod_register`: Normalize username when determining if user want to change pass
- `mod_register`: Strip query data when returning errors
- WebAdmin: New hooks `webadmin_menu_system` to add items to system menu
## Version 25.04
#### Security fixes
+7 -3
View File
@@ -19,7 +19,7 @@ To compile ejabberd you need:
- GCC
- Libexpat ≥ 1.95
- Libyaml ≥ 0.1.4
- Erlang/OTP ≥ 20.0
- Erlang/OTP ≥ 25.0
- OpenSSL ≥ 1.0.0
Other optional libraries are:
@@ -28,8 +28,7 @@ Other optional libraries are:
- PAM library, for Pluggable Authentication Modules (PAM)
- ImageMagick's Convert program and Ghostscript fonts, for CAPTCHA
challenges
- Elixir ≥ 1.10.3, for Elixir support. It is recommended Elixir 1.13.4 or higher
and Erlang/OTP 23.0 or higher.
- Elixir ≥ 1.10.3, for Elixir support. It is recommended Elixir 1.14.0 or higher
If your system splits packages in libraries and development headers,
install the development packages too.
@@ -66,6 +65,11 @@ To configure the compilation, features, install paths...
./configure --help
The build tool automatically downloads and compiles the
erlang libraries that [ejabberd depends on][docs-repo].
[docs-repo]: https://docs.ejabberd.im/developer/repositories/
Install in the System
---------------------
+150 -4
View File
@@ -310,6 +310,152 @@ now you can define the admin account JID using an environment variable:
Check the [full example](#customized-example) for other example.
### ejabberd-contrib
This section addresses those topics related to
[ejabberd-contrib](https://docs.ejabberd.im/admin/guide/modules/#ejabberd-contrib):
- [Download source code](#download-source-code)
- [Install a module](#install-a-module)
- [Install git for dependencies](#install-git-for-dependencies)
- [Install your module](#install-your-module)
---
#### Download source code
The `ejabberd` container image includes the ejabberd-contrib git repository source code,
but `ecs` does not, so first download it:
```bash
$ docker exec ejabberd ejabberdctl modules_update_specs
```
#### Install a module
Compile and install any of the contributed modules, for example:
```bash
docker exec ejabberd ejabberdctl module_install mod_statsdx
Module mod_statsdx has been installed and started.
It's configured in the file:
/opt/ejabberd/.ejabberd-modules/mod_statsdx/conf/mod_statsdx.yml
Configure the module in that file, or remove it
and configure in your main ejabberd.yml
```
#### Install git for dependencies
Some modules depend on erlang libraries,
but the container images do not include `git` or `mix` to download them.
Consequently, when you attempt to install such a module,
there will be error messages like:
```bash
docker exec ejabberd ejabberdctl module_install ejabberd_observer_cli
I'll download "recon" using git because I can't use Mix to fetch from hex.pm:
/bin/sh: mix: not found
Fetching dependency observer_cli:
/bin/sh: git: not found
...
```
the solution is to install `git` in the container image:
```bash
docker exec --user root ejabberd apk add git
fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.21/community/x86_64/APKINDEX.tar.gz
(1/3) Installing pcre2 (10.43-r0)
(2/3) Installing git (2.47.2-r0)
(3/3) Installing git-init-template (2.47.2-r0)
Executing busybox-1.37.0-r12.trigger
OK: 27 MiB in 42 packages
```
and now you can upgrade the module:
```bash
docker exec ejabberd ejabberdctl module_upgrade ejabberd_observer_cli
I'll download "recon" using git because I can't use Mix to fetch from hex.pm:
/bin/sh: mix: not found
Fetching dependency observer_cli: Cloning into 'observer_cli'...
Fetching dependency os_stats: Cloning into 'os_stats'...
Fetching dependency recon: Cloning into 'recon'...
Inlining: inline_size=24 inline_effort=150
Old inliner: threshold=0 functions=[{insert,2},{merge,2}]
Module ejabberd_observer_cli has been installed.
Now you can configure it in your ejabberd.yml
I'll download "recon" using git because I can't use Mix to fetch from hex.pm:
/bin/sh: mix: not found
```
#### Install your module
If you [developed an ejabberd module](https://docs.ejabberd.im/developer/extending-ejabberd/modules/),
you can install it in your container image:
1. Create a local directory for `ejabberd-modules`:
``` sh
mkdir docker-modules
```
2. Then create the directory structure for your custom module:
``` sh
cd docker-modules
mkdir -p sources/mod_hello_world/
touch sources/mod_hello_world/mod_hello_world.spec
mkdir sources/mod_hello_world/src/
mv mod_hello_world.erl sources/mod_hello_world/src/
mkdir sources/mod_hello_world/conf/
echo -e "modules:\n mod_hello_world: {}" > sources/mod_hello_world/conf/mod_hello_world.yml
cd ..
```
3. Grant ownership of that directory to the UID that ejabberd will use inside the Docker image:
``` sh
sudo chown 9000 -R docker-modules/
```
4. Start ejabberd in the container:
``` sh
sudo docker run \
--name hellotest \
-d \
--volume "$(pwd)/docker-modules:/home/ejabberd/.ejabberd-modules/" \
-p 5222:5222 \
-p 5280:5280 \
ejabberd/ecs
```
5. Check the module is available for installing, and then install it:
``` sh
sudo docker exec -it hellotest ejabberdctl modules_available
mod_hello_world []
sudo docker exec -it hellotest ejabberdctl module_install mod_hello_world
```
6. If the module works correctly, you will see `Hello` in the ejabberd logs when it starts:
``` sh
sudo docker exec -it hellotest grep Hello logs/ejabberd.log
2020-10-06 13:40:13.154335+00:00 [info]
<0.492.0>@mod_hello_world:start/2:15 Hello, ejabberd world!
```
### ejabberdapi
When the container is running (and thus ejabberd), you can exec commands inside the container
@@ -418,12 +564,12 @@ it is necessary to change the old hostname stored in Mnesia.
This section is equivalent to the ejabberd Documentation
[Change Computer Hostname](https://docs.ejabberd.im/admin/guide/managing/#change-computer-hostname),
but particularized to containers that use this
ecs container image from ejabberd 23.01 or older.
`ecs` container image from ejabberd 23.01 or older.
#### Setup Old Container
Let's assume a container running ejabberd 23.01 (or older) from
this ecs container image, with the database directory binded
this `ecs` container image, with the database directory binded
and one registered account.
This can be produced with:
```bash
@@ -926,10 +1072,10 @@ Let's summarize the differences between both container images. Legend:
| Generated by | [container.yml](https://github.com/processone/ejabberd/blob/master/.github/workflows/container.yml) | [tests.yml](https://github.com/processone/docker-ejabberd/blob/master/.github/workflows/tests.yml) |
| Built for | stable releases <br /> `master` branch | stable releases <br /> [`master` branch zip](https://github.com/processone/docker-ejabberd/actions/workflows/tests.yml) |
| Architectures | `linux/amd64` <br /> `linux/arm64` | `linux/amd64` |
| Software | Erlang/OTP 27.3.2-alpine <br /> Elixir 1.18.3 | Alpine 3.19 <br /> Erlang/OTP 26.2 <br /> Elixir 1.15.7 |
| Software | Erlang/OTP 27.3.4.3-alpine <br /> Elixir 1.18.4 | Alpine 3.22 <br /> Erlang/OTP 26.2 <br /> Elixir 1.18.3 |
| Published in | [ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd) | [docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/) <br /> [ghcr.io/processone/ecs](https://github.com/processone/docker-ejabberd/pkgs/container/ecs) |
| :black_square_button: **Additional content** |
| [ejabberd-contrib](https://docs.ejabberd.im/admin/guide/modules/#ejabberd-contrib) | included | not included |
| [ejabberd-contrib](#ejabberd-contrib) | included | not included |
| [ejabberdapi](#ejabberdapi) | included :orange_circle: | included |
| :black_square_button: **Ports** |
| [1880](#ports) for WebAdmin | yes :orange_circle: | yes :orange_circle: |
+21 -5
View File
@@ -2,8 +2,11 @@
#' definitions
#
# Only required for Erlang/OTP 25
MAYBE=ERL_FLAGS="-enable-feature maybe_expr"
ESCRIPT = @ESCRIPT@
REBAR = @rebar@ # rebar|rebar3|mix binary (or path to binary)
REBAR = $(MAYBE) @rebar@ # rebar|rebar3|mix binary (or path to binary)
REBAR3 = @REBAR3@ # path to rebar3 binary
MIX = @rebar@
AWK = @AWK@
@@ -123,7 +126,7 @@ REBAR_VER_318:=$(shell $(REBAR) --version | $(AWK) -F '[ .]' '/rebar / {print ($
endif
ifeq "$(REBAR_VER)" "6"
REBAR=$(MIX)
REBAR=$(MAYBE) $(MIX)
SKIPDEPS=
LISTDEPS=deps.tree
UPDATEDEPS=deps.update
@@ -141,7 +144,7 @@ ifeq "$(REBAR_VER)" "6"
ELIXIR_LIBDIR=":$(ELIXIR_LIBDIR_RAW)"
REBARREL=MIX_ENV=prod $(REBAR) release --overwrite
REBARDEV=MIX_ENV=dev $(REBAR) release --overwrite
RELIVECMD=$(ESCRIPT) rel/relive.escript && MIX_ENV=dev RELIVE=true $(IEX) --name ejabberd@localhost -S mix run
RELIVECMD=$(ESCRIPT) rel/relive.escript && MIX_ENV=dev RELIVE=true $(MAYBE) $(IEX) --name ejabberd@localhost -S mix run
REL_LIB_DIR = _build/dev/rel/ejabberd/lib
COPY_REL_TARGET = dev
GET_DEPS_TRANSLATIONS=MIX_ENV=translations $(REBAR) $(GET_DEPS)
@@ -190,9 +193,10 @@ else
XREFOPTIONS=
CLEANARG=
REBARREL=$(REBAR) generate
REBARDEV=
REBARDEV=@echo "Rebar2 detected... dev not supported.\
\nTry: make prod, or: ./configure --with-rebar=rebar3 ; make dev"
RELIVECMD=@echo "Rebar2 detected... relive not supported.\
\nTry: ./configure --with-rebar=rebar3 ; make relive"
\nTry: ./configure --with-rebar=rebar3 ; make relive"
REL_LIB_DIR = rel/ejabberd/lib
COPY_REL_TARGET = rel
endif
@@ -661,6 +665,17 @@ test:
@cd priv && ln -sf ../sql
$(REBAR) $(SKIPDEPS) ct
.PHONY: test-%
define test-group-target
test-$1:
$(REBAR) $(SKIPDEPS) ct --suite=test/ejabberd_SUITE --group=$1
endef
ifneq ($(filter test-%,$(MAKECMDGOALS)),)
group_to_test := $(patsubst test-%,%,$(filter test-%,$(MAKECMDGOALS)))
$(eval $(call test-group-target,$(group_to_test)))
endif
test-eunit:
$(REBAR) $(SKIPDEPS) eunit --verbose
@@ -711,6 +726,7 @@ help:
@echo " hooks Run hooks validator"
@echo " test Run Common Tests suite [rebar3]"
@echo " test-eunit Run EUnit suite [rebar3]"
@echo " test-<group> Run Common Test suite for specific group only [rebar3]"
@echo " xref Run cross reference analysis [rebar3]"
#.
+24 -7
View File
@@ -2,8 +2,17 @@
# 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 25.04` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="9.0.5 (Erlang/OTP 20.0)"
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 25.10` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
AC_ARG_WITH(min-erlang,
AS_HELP_STRING([--with-min-erlang=version],[set minimal required erlang version, default to OTP25]),
[if test "X$withval" = "X"; then
REQUIRE_ERLANG_MIN="13.0 (Erlang/OTP 25.0)"
else
REQUIRE_ERLANG_MIN="$withval"
fi
], [REQUIRE_ERLANG_MIN="13.0 (Erlang/OTP 25.0)"])
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
@@ -183,12 +192,20 @@ AC_ARG_ENABLE(mysql,
esac],[if test "x$mysql" = "x"; then mysql=false; fi])
AC_ARG_ENABLE(new_sql_schema,
[AS_HELP_STRING([--enable-new-sql-schema],[use new SQL schema by default (default: no)])],
[AS_HELP_STRING([--enable-new-sql-schema],[obsolete, use --enable-multihost-sql-schema instead (default: no)])],
[case "${enableval}" in
yes) new_sql_schema=true ;;
no) new_sql_schema=false ;;
yes) multihost_sql_schema=true ;;
no) multihost_sql_schema=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-new-sql-schema) ;;
esac],[new_sql_schema=false])
esac],[multihost_sql_schema=false])
AC_ARG_ENABLE(multihost_sql_schema,
[AS_HELP_STRING([--enable-multihost-sql-schema],[use multihost SQL schema by default (default: no)])],
[case "${enableval}" in
yes) multihost_sql_schema=true ;;
no) multihost_sql_schema=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-multihost-sql-schema) ;;
esac],[multihost_sql_schema=false])
AC_ARG_ENABLE(odbc,
[AS_HELP_STRING([--enable-odbc],[enable pure ODBC support (default: no)])],
@@ -309,7 +326,7 @@ esac
AC_MSG_RESULT([build tool to use (change using --with-rebar): $rebar])
AC_SUBST(roster_gateway_workaround)
AC_SUBST(new_sql_schema)
AC_SUBST(multihost_sql_schema)
AC_SUBST(full_xml)
AC_SUBST(odbc)
AC_SUBST(mssql)
+24 -6
View File
@@ -299,7 +299,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0133.html"/>
<xmpp:version>1.3.0</xmpp:version>
<xmpp:version>1.3.1</xmpp:version>
<xmpp:since>13.10</xmpp:since>
<xmpp:status>partial</xmpp:status>
<xmpp:note>mod_configure</xmpp:note>
@@ -578,10 +578,10 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0317.html"/>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:version>0.3.1</xmpp:version>
<xmpp:since>21.12</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_muc_room, 0.2.0 since 25.03</xmpp:note>
<xmpp:note>mod_muc_room, 0.3.1 since 25.10</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
@@ -773,6 +773,15 @@
<xmpp:note>mod_mam</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0431.html"/>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>24.12</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_mam</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0440.html"/>
@@ -821,10 +830,19 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0485.html"/>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>24.02</xmpp:since>
<xmpp:version>0.1.1</xmpp:version>
<xmpp:since>25.07</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>, mod_pubsub_serverinfo in ejabberd-contrib.git</xmpp:note>
<xmpp:note>mod_pubsub_serverinfo</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0486.html"/>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>24.07</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_muc</xmpp:note>
</xmpp:SupportedXep>
</implements>
</Project>
+1 -1
View File
@@ -179,7 +179,7 @@ modules:
mod_fail2ban: {}
mod_http_api: {}
mod_http_upload:
put_url: https://@HOST@:5443/upload
put_url: https://@HOST_URL_ENCODE@:5443/upload
custom_headers:
"Access-Control-Allow-Origin": "https://@HOST@"
"Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
+124 -6
View File
@@ -121,6 +121,9 @@ export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS
export SCRIPT_DIR
# Only required for Erlang/OTP 25:
export ERL_FLAGS="$ERL_FLAGS -enable-feature maybe_expr"
set_dist_client()
{
[ -n "$ERL_DIST_PORT" ] && ERLANG_OPTS="$ERLANG_OPTS -dist_listen false"
@@ -318,6 +321,13 @@ check_start()
# allow sync calls
wait_status()
{
wait_status_node "$ERLANG_NODE" $1 $2 $3
}
wait_status_node()
{
CONNECT_NODE=$1
shift
# args: status try delay
# return: 0 OK, 1 KO
timeout="$2"
@@ -329,9 +339,9 @@ wait_status()
status="$1"
else
exec_erl "$(uid ctl)" -hidden -noinput \
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
-s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
-extra "$CONNECT_NODE" $NO_TIMEOUT status > /dev/null
status="$?"
fi
done
@@ -340,19 +350,26 @@ wait_status()
exec_other_command()
{
exec_other_command_node $ERLANG_NODE "$@"
}
exec_other_command_node()
{
CONNECT_NODE=$1
shift
if [ -z "$CTL_OVER_HTTP" ] || [ ! -S "$CTL_OVER_HTTP" ] \
|| [ ! -x "$(command -v curl)" ] || [ -z "$1" ] || [ "$1" = "help" ] \
|| [ "$1" = "mnesia_info_ctl" ]|| [ "$1" = "print_sql_schema" ] ; then
exec_erl "$(uid ctl)" -hidden -noinput \
-eval 'net_kernel:connect_node('"'$ERLANG_NODE'"')' \
-eval 'net_kernel:connect_node('"'$CONNECT_NODE'"')' \
-s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
-extra "$CONNECT_NODE" $NO_TIMEOUT "$@"
result=$?
case $result in
3) help;;
*) :;;
esac
exit $result
return $result
else
exec_ctl_over_http_socket "$@"
fi
@@ -393,6 +410,103 @@ cd "$SPOOL_DIR" || {
exit 6
}
printe()
{
printf "\n"
printf "\e[1;40;32m==> %s\e[0m\n" "$1"
}
## Function copied from tools/make-installers
user_agrees()
{
question="$*"
if [ -t 0 ]
then
printe "$question (y/n) [n]"
read -r response
case "$response" in
[Yy]|[Yy][Ee][Ss])
return 0
;;
[Nn]|[Nn][Oo]|'')
return 1
;;
*)
echo 'Please respond with "yes" or "no".'
user_agrees "$question"
;;
esac
else # Assume 'yes' if not running interactively.
return 0
fi
}
mnesia_change()
{
ERLANG_NODE_OLD="$1"
[ "$ERLANG_NODE_OLD" = "" ] \
&& echo "Error: Please provide the old erlang node name, for example:" \
&& echo " ejabberdctl mnesia_change ejabberd@oldmachine" \
&& exit 1
SPOOL_DIR_BACKUP=$SPOOL_DIR/$ERLANG_NODE_OLD-backup/
OLDFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE_OLD.backup
NEWFILE=$SPOOL_DIR_BACKUP/$ERLANG_NODE.backup
printe "This changes your mnesia database from node name '$ERLANG_NODE_OLD' to '$ERLANG_NODE'"
[ -d "$SPOOL_DIR_BACKUP" ] && printe "WARNING! A backup of old node already exists in $SPOOL_DIR_BACKUP"
if ! user_agrees "Do you want to proceed?"
then
echo 'Operation aborted.'
exit 1
fi
printe "Starting ejabberd with old node name $ERLANG_NODE_OLD ..."
exec_erl "$ERLANG_NODE_OLD" $EJABBERD_OPTS -detached
wait_status_node $ERLANG_NODE_OLD 0 30 2
result=$?
case $result in
1) echo "There was a problem starting ejabberd with the old erlang node name. " \
&& echo "Check for log errors in $EJABBERD_LOG_PATH" \
&& exit $result;;
*) :;;
esac
exec_other_command_node $ERLANG_NODE_OLD "status"
printe "Making backup of old database to file $OLDFILE ..."
mkdir $SPOOL_DIR_BACKUP
exec_other_command_node $ERLANG_NODE_OLD backup "$OLDFILE"
printe "Changing node name in new backup file $NEWFILE ..."
exec_other_command_node $ERLANG_NODE_OLD mnesia_change_nodename "$ERLANG_NODE_OLD" "$ERLANG_NODE" "$OLDFILE" "$NEWFILE"
printe "Stopping old ejabberd..."
exec_other_command_node $ERLANG_NODE_OLD "stop"
wait_status_node $ERLANG_NODE_OLD 3 30 2 && stop_epmd
printe "Moving old mnesia spool files to backup subdirectory $SPOOL_DIR_BACKUP ..."
mv $SPOOL_DIR/*.DAT $SPOOL_DIR_BACKUP
mv $SPOOL_DIR/*.DCD $SPOOL_DIR_BACKUP
mv $SPOOL_DIR/*.LOG $SPOOL_DIR_BACKUP
printe "Starting ejabberd with new node name $ERLANG_NODE ..."
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -detached
wait_status 0 30 2
exec_other_command "status"
printe "Installing fallback of new mnesia..."
exec_other_command install_fallback "$NEWFILE"
printe "Stopping new ejabberd..."
exec_other_command "stop"
wait_status 3 30 2 && stop_epmd
printe "Finished, now you can start ejabberd normally"
}
# main
case $1 in
start)
@@ -442,7 +556,8 @@ case $1 in
-noinput -hidden \
-eval 'net_kernel:connect_node('"'$PEER'"')' \
-eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \
-s erlang halt -output text
-eval 'halt(case net_adm:ping('"'$PEER'"') of pong -> 0; pang -> 1 end).' \
-output text
;;
started)
set_dist_client
@@ -452,6 +567,9 @@ case $1 in
set_dist_client
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
;;
mnesia_change)
mnesia_change $2
;;
*)
set_dist_client
exec_other_command "$@"
+3 -2
View File
@@ -16,17 +16,18 @@
{elvis_style, function_naming_convention, disable},
{elvis_style, god_modules, #{limit => 300}},
{elvis_style, invalid_dynamic_call, disable},
{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_boolean_in_comparison, 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_operation_on_same_value, disable},
{elvis_style, no_receive_without_timeout, disable},
{elvis_style, no_single_clause_case, disable},
{elvis_style, no_spec_with_records, disable},
{elvis_style, no_throw, disable},
+5 -1
View File
@@ -27,10 +27,14 @@
rescode | restuple.
%% The 'any' and 'atom' argument types and 'any' result type
%% should only be used %% by commands with tag 'internal',
%% should only be used by commands with tag 'internal',
%% which are meant to be used only internally in ejabberd,
%% and not called using external frontends.
%% When a command with tag 'async' is called by mod_adhoc_api,
%% it is spawned in a new process for the command execution,
%% and the command immediately returns success.
%% The purpose of a command can either be:
%% - informative: its purpose is to obtain information
%% - modifier: its purpose is to produce some change in the server
+1 -1
View File
@@ -72,4 +72,4 @@
-record(sql_schema_info,
{db_type :: pgsql | mysql | sqlite,
db_version :: any(),
new_schema = true :: boolean()}).
multihost_schema = true :: boolean()}).
+31 -5
View File
@@ -39,20 +39,46 @@
-else.
-include_lib("kernel/include/logger.hrl").
-define(CLEAD, "\e[1"). % bold
-define(CMID, "\e[0"). % normal
-define(CCLEAN, "\e[0m"). % clean
-define(CDEFAULT, ";49;95m"). % light magenta
-define(CDEBUG, ";49;90m"). % dark gray
-define(CINFO, ";49;92m"). % green
-define(CWARNING, ";49;93m"). % light yellow
-define(CERROR, ";49;91m"). % light magenta
-define(CCRITICAL,";49;31m"). % light red
-define(DEBUG(Format, Args),
begin ?LOG_DEBUG(Format, Args), ok end).
begin ?LOG_DEBUG(Format, Args,
#{clevel => ?CLEAD ++ ?CDEBUG,
ctext => ?CMID ++ ?CDEBUG}),
ok end).
-define(INFO_MSG(Format, Args),
begin ?LOG_INFO(Format, Args), ok end).
begin ?LOG_INFO(Format, Args,
#{clevel => ?CLEAD ++ ?CINFO,
ctext => ?CCLEAN}),
ok end).
-define(WARNING_MSG(Format, Args),
begin ?LOG_WARNING(Format, Args), ok end).
begin ?LOG_WARNING(Format, Args,
#{clevel => ?CLEAD ++ ?CWARNING,
ctext => ?CMID ++ ?CWARNING}),
ok end).
-define(ERROR_MSG(Format, Args),
begin ?LOG_ERROR(Format, Args), ok end).
begin ?LOG_ERROR(Format, Args,
#{clevel => ?CLEAD ++ ?CERROR,
ctext => ?CMID ++ ?CERROR}),
ok end).
-define(CRITICAL_MSG(Format, Args),
begin ?LOG_CRITICAL(Format, Args), ok end).
begin ?LOG_CRITICAL(Format, Args,
#{clevel => ?CLEAD++ ?CCRITICAL,
ctext => ?CMID ++ ?CCRITICAL}),
ok end).
-endif.
%% Use only when trying to troubleshoot test problem with ExUnit
@@ -18,10 +18,19 @@
%%%
%%%----------------------------------------------------------------------
-ifdef(DEPRECATED_GET_STACKTRACE).
-define(EX_RULE(Class, Reason, Stack), Class:Reason:Stack).
-define(EX_STACK(Stack), Stack).
-else.
-define(EX_RULE(Class, Reason, _), Class:Reason).
-define(EX_STACK(_), erlang:get_stacktrace()).
-endif.
-define(MODULE_ANTISPAM, mod_antispam).
-type url() :: binary().
-type filename() :: binary() | none | false.
-type jid_set() :: sets:set(ljid()).
-type url_set() :: sets:set(url()).
-define(DEFAULT_RTBL_DOMAINS_NODE, <<"spam_source_domains">>).
-record(rtbl_service,
{host = none :: binary() | none,
node = ?DEFAULT_RTBL_DOMAINS_NODE :: binary(),
subscribed = false :: boolean(),
retry_timer = undefined :: reference() | undefined}).
-type rtbl_service() :: #rtbl_service{}.
+9 -1
View File
@@ -21,8 +21,16 @@
-record(room_version,
{id :: binary(),
%% use the same field names as in Synapse
enforce_key_validity :: boolean(),
special_case_aliases_auth :: boolean(),
strict_canonicaljson :: boolean(),
limit_notifications_power_levels :: boolean(),
knock_join_rule :: boolean(),
restricted_join_rule :: boolean(),
restricted_join_rule_fix :: boolean(),
knock_restricted_join_rule :: boolean(),
enforce_int_power_levels :: boolean(),
implicit_room_creator :: boolean(),
updated_redaction_rules :: boolean()
updated_redaction_rules :: boolean(),
hydra :: boolean()
}).
+3 -2
View File
@@ -23,8 +23,9 @@
opts = [] :: list() | '_'}).
-record(muc_registered,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
nick = <<"">> :: binary()}).
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary() | '$1', binary() | '$2'},
binary() | '_'} | '$1',
nick = <<"">> :: binary() | '$3'}).
-record(muc_online_room,
{name_host :: {binary(), binary()} | '$1' | {'_', binary()} | '_',
+2 -1
View File
@@ -126,7 +126,8 @@
history = #lqueue{} :: lqueue(),
subject = [] :: [text()],
subject_author = {<<"">>, #jid{}} :: {binary(), jid()},
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
hats_defs = #{} :: #{binary() => {binary(), binary()}},
hats_users = #{} :: #{ljid() => [binary()]},
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
room_shaper = none :: ejabberd_shaper:shaper(),
+1 -1
View File
@@ -19,6 +19,6 @@
%%%----------------------------------------------------------------------
-record(private_storage,
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
{usns = {<<"">>, <<"">>, <<"">>} :: {binary() | '$1' | '_', binary(), binary() |
'$1' | '_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
+644 -34
View File
@@ -2,12 +2,12 @@
.\" Title: ejabberd.yml
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/>
.\" Date: 04/16/2025
.\" Date: 10/28/2025
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "04/16/2025" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "10/28/2025" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * 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/25\&.04/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/25\&.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 25\&.04\&. The options that changed in this version are marked with 🟤\&.
This section describes top level options of ejabberd 25\&.10\&. The options that changed in this version are marked with 🟤\&.
.PP
\fBaccess_rules\fR: \fI{AccessName: {allow|deny: ACLName|ACLDefinition}}\fR
.RS 4
@@ -461,6 +461,12 @@ option\&.
.sp
The default value is \fIplain\fR\&.
.PP
\fBauth_password_types_hidden_in_sasl1\fR: \fI[plain | scram_sha1 | scram_sha256 | scram_sha512]\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. List of password types that should not be offered in SASL1 authenticatication\&. Because SASL1, unlike SASL2, can\(cqt have list of available mechanisms tailored to individual user, it\(cqs possible that offered mechanisms will not be compatible with stored password, especially if new password type was added recently\&. This option allows disabling offering some mechanisms in SASL1, to a time until new password type will be available for all users\&.
.RE
.PP
\fBauth_scram_hash\fR: \fIsha | sha256 | sha512\fR
.RS 4
Hash algorithm that should be used to store password in
@@ -697,13 +703,19 @@ A list of Erlang nodes to connect on ejabberd startup\&. This option is mostly i
\fBdefault_db\fR: \fImnesia | sql\fR
.RS 4
\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
to store persistent data in ejabberd\&. Some components can be configured with specific toplevel options like
\fIoauth_db_type\fR\&. Many modules can be configured with specific module options, usually named
db_type\&. The default value is
\fImnesia\fR\&.
.RE
.PP
\fBdefault_ram_db\fR: \fImnesia | redis | sql\fR
.RS 4
Default volatile (in\-memory) storage for ejabberd\&. Modules and other components (e\&.g\&. session management) may have its own value\&. The default value is
Default volatile (in\-memory) storage for ejabberd\&. Some components can be configured with specific toplevel options like
\fIrouter_db_type\fR
and
\fIsm_db_type\fR\&. Some modules can be configured with specific module options, usually named
ram_db_type\&. The default value is
\fImnesia\fR\&.
.RE
.PP
@@ -951,6 +963,34 @@ List of one or more
option\&.
.RE
.PP
\fBhosts_alias\fR: \fI{Alias: Host}\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. Define aliases for existing vhosts managed by ejabberd\&. An alias may be a regexp expression\&. This option is only consulted by the
\fIejabberd_http\fR
listener\&.
.sp
\fBExample\fR:
.sp
.if n \{\
.RS 4
.\}
.nf
hosts:
\- domain\&.tld
\- example\&.org
hosts_alias:
xmpp\&.domain\&.tld: domain\&.tld
jabber\&.domain\&.tld: domain\&.tld
mytest\&.net: example\&.org
"exa*": example\&.org
.fi
.if n \{\
.RE
.\}
.RE
.PP
\fBinclude_config_file\fR: \fI[Filename, \&.\&.\&.] | {Filename: Options}\fR
.RS 4
Read and
@@ -1242,17 +1282,12 @@ This option can be used to tune tick time parameter of
\fI1 minute\fR\&.
.RE
.PP
\fBnew_sql_schema\fR: \fItrue | false\fR
\fBnew_sql_schema 🟤\fR: \fItrue | false\fR
.RS 4
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/25\&.04/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
schema is best when serving several XMPP domains and/or changing domains from time to time\&. This avoid need to manage several databases and handle complex configuration changes\&. The default depends on configuration flag
\fI\-\-enable\-new\-sql\-schema\fR
which is set at compile time\&.
\fINote\fR
about this option: obsoleted in 25\&.10\&. This option was renamed to
\fIsql_schema_multihost\fR
in ejabberd 25\&.10\&. Please update your configuration to use the new option name
.RE
.PP
\fBoauth_access\fR: \fIAccessName\fR
@@ -1513,6 +1548,30 @@ XMPP Core: section 7\&.7\&.2\&.2\&. The default value is
\fIcloseold\fR\&.
.RE
.PP
\fBrest_proxy\fR: \fIHost\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. Address of a HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
.RE
.PP
\fBrest_proxy_password\fR: \fIstring()\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. Password used to authenticate to HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
.RE
.PP
\fBrest_proxy_port\fR: \fI1\&.\&.65535\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. Port of a HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
.RE
.PP
\fBrest_proxy_username\fR: \fIstring()\fR
.RS 4
\fINote\fR
about this option: added in 25\&.07\&. Username used to authenticate to HTTP Connect proxy used by modules issuing rest calls (like ejabberd_oauth_rest)
.RE
.PP
\fBrouter_cache_life_time\fR: \fItimeout()\fR
.RS 4
Same as
@@ -1894,6 +1953,22 @@ or
if the latter is not set\&.
.RE
.PP
\fBsql_schema_multihost 🟤\fR: \fItrue | false\fR
.RS 4
\fINote\fR
about this option: renamed in 25\&.10\&. Whether to use the
\fIdatabase\&.md#default\-and\-new\-schemas|multihost SQL schema\fR\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/25\&.10/sql\&. There are two schemas available\&. The legacy
\fIsinglehost\fR
schema stores one XMPP domain into one ejabberd database\&. The
\fImultihost\fR
schema can handle several XMPP domains in a single ejabberd database\&. The
\fImultihost\fR
schema is preferable when serving several XMPP domains and/or changing domains from time to time\&. This avoid need to manage several databases and handle complex configuration changes\&. The default depends on configuration flag
\fI\-\-enable\-sql\-schema\-multihost\fR
which is set at compile time\&.
.RE
.PP
\fBsql_server\fR: \fIHost | IP Address | ODBC Connection String | Unix Socket Path\fR
.RS 4
\fINote\fR
@@ -2043,9 +2118,13 @@ seconds\&.
.RE
.SH "MODULES"
.sp
This section describes modules options of ejabberd 25\&.04\&. The modules that changed in this version are marked with 🟤\&.
This section describes modules options of ejabberd 25\&.10\&. The modules that changed in this version are marked with 🟤\&.
.SS "mod_adhoc"
.sp
def:ad\-hoc command
.sp
: Command that can be executed by an XMPP client using XEP\-0050\&.
.sp
This module implements XEP\-0050: Ad\-Hoc Commands\&. It\(cqs an auxiliary module and is only needed by some of the other modules\&.
.sp
.it 1 an-trap
@@ -2066,7 +2145,7 @@ Provide the Commands item in the Service Discovery\&. Default value:
.sp
\fINote\fR about this option: added in 25\&.03\&.
.sp
Execute API Commands in a XMPP client using XEP\-0050: Ad\-Hoc Commands\&. This module requires \fImod_adhoc\fR (to execute the commands), and recommends \fImod_disco\fR (to discover the commands)\&.
Execute (def:API commands) in a XMPP client using XEP\-0050: Ad\-Hoc Commands\&. This module requires \fImod_adhoc\fR (to execute the commands), and recommends \fImod_disco\fR (to discover the commands)\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@@ -2078,7 +2157,7 @@ Execute API Commands in a XMPP client using XEP\-0050: Ad\-Hoc Commands\&. This
.PP
\fBdefault_version\fR: \fIinteger() | string()\fR
.RS 4
What API version to use\&. If setting an ejabberd version, it will use the latest API version that was available in that ejabberd version\&. For example, setting
What API version to use\&. If setting an ejabberd version, it will use the latest API version that was available in that (def:c2s) ejabberd version\&. For example, setting
\fI"24\&.06"\fR
in this option implies
\fI2\fR\&. The default value is the latest version\&.
@@ -2199,6 +2278,8 @@ ejabberdctl srg_create g1 example\&.org "\*(AqGroup number 1\*(Aq" this_is_g1 g1
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#accounts|accounts\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#erlang|erlang\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#last|last\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#private|private\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#roster|roster\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#session|session\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#shared_roster_group|shared_roster_group\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#stanza|stanza\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#statistics|statistics\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#vcard|vcard\fR
.RE
.SS "mod_admin_update_sql"
.sp
@@ -2207,7 +2288,7 @@ This module can be used to update existing SQL database from the default to the
The module has no options\&.
.SS "mod_announce"
.sp
This module enables configured users to broadcast announcements and to set the message of the day (MOTD)\&. Configured users can perform these actions with an XMPP client either using Ad\-Hoc Commands or sending messages to specific JIDs\&.
This module enables configured users to broadcast announcements and to set the message of the day (MOTD)\&. Configured users can perform these actions with an XMPP client either using Ad\-Hoc Commands or sending messages to specific JIDs\&. Equivalent API commands are also available\&.
.if n \{\
.sp
.\}
@@ -2392,6 +2473,138 @@ Same as top\-level
\fIuse_cache\fR
option, but applied to this module only\&.
.RE
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#announce|announce\fR
.RE
.SS "mod_antispam"
.sp
\fINote\fR about this option: added in 25\&.07\&.
.sp
Filter spam messages and subscription requests received from remote servers based on Real\-Time Block Lists (RTBL), lists of known spammer JIDs and/or URLs mentioned in spam messages\&. Traffic classified as spam is rejected with an error (and an \fI[info]\fR message is logged) unless the sender is subscribed to the recipient\(cqs presence\&.
.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
\fBaccess_spam\fR: \fIAccess\fR
.RS 4
Access rule that controls what accounts may receive spam messages\&. If the rule returns
\fIallow\fR
for a given recipient, spam messages aren\(cqt rejected for that recipient\&. The default value is
\fInone\fR, which means that all recipients are subject to spam filtering verification\&.
.RE
.PP
\fBcache_size\fR: \fIpos_integer()\fR
.RS 4
Maximum number of JIDs that will be cached due to sending spam URLs\&. If that limit is exceeded, the least recently used entries are removed from the cache\&. Setting this option to
\fI0\fR
disables the caching feature\&. Note that separate caches are used for each virtual host, and that the caches aren\(cqt distributed across cluster nodes\&. The default value is
\fI10000\fR\&.
.RE
.PP
\fBrtbl_services\fR: \fI[Service]\fR
.RS 4
Query a RTBL service to get domains to block, as provided by
xmppbl\&.org\&. Please note right now this option only supports one service in that list\&. For blocking spam and abuse on MUC channels, please use
\fImod_muc_rtbl\fR
for now\&. If only the host is provided, the default node names will be assumed\&. If the node name is different than
\fIspam_source_domains\fR, you can setup the custom node name with the option
\fIspam_source_domains_node\fR\&. The default value is an empty list of services\&.
.sp
\fBExample\fR:
.sp
.if n \{\
.RS 4
.\}
.nf
rtbl_services:
\- pubsub\&.server1\&.localhost:
spam_source_domains_node: actual_custom_pubsub_node
.fi
.if n \{\
.RE
.\}
.RE
.PP
\fBspam_domains_file\fR: \fInone | Path\fR
.RS 4
Path to a plain text file containing a list of known spam domains, one domain per line\&. Messages and subscription requests sent from one of the listed domains are classified as spam if sender is not in recipient\(cqs roster\&. This list of domains gets merged with the one retrieved by an RTBL host if any given\&. Use an absolute path, or the
\fI@CONFIG_PATH@\fR
predefined keyword
if the file is available in the configuration directory\&. The default value is
\fInone\fR\&.
.RE
.PP
\fBspam_dump_file\fR: \fIfalse | true | Path\fR
.RS 4
Path to the file to store blocked messages\&. Use an absolute path, or the
\fI@LOG_PATH@\fR
predefined keyword
to store logs in the same place that the other ejabberd log files\&. If set to
\fIfalse\fR, it doesn\(cqt dump stanzas, which is the default\&. If set to
\fItrue\fR, it stores in
\fI"@LOG_PATH@/spam_dump_@HOST@\&.log"\fR\&.
.RE
.PP
\fBspam_jids_file\fR: \fInone | Path\fR
.RS 4
Path to a plain text file containing a list of known spammer JIDs, one JID per line\&. Messages and subscription requests sent from one of the listed JIDs are classified as spam\&. Messages containing at least one of the listed JIDsare classified as spam as well\&. Furthermore, the sender\(cqs JID will be cached, so that future traffic originating from that JID will also be classified as spam\&. Use an absolute path, or the
\fI@CONFIG_PATH@\fR
predefined keyword
if the file is available in the configuration directory\&. The default value is
\fInone\fR\&.
.RE
.PP
\fBspam_urls_file\fR: \fInone | Path\fR
.RS 4
Path to a plain text file containing a list of URLs known to be mentioned in spam message bodies\&. Messages containing at least one of the listed URLs are classified as spam\&. Furthermore, the sender\(cqs JID will be cached, so that future traffic originating from that JID will be classified as spam as well\&. Use an absolute path, or the
\fI@CONFIG_PATH@\fR
predefined keyword
if the file is available in the configuration directory\&. The default value is
\fInone\fR\&.
.RE
.PP
\fBwhitelist_domains_file\fR: \fInone | Path\fR
.RS 4
Path to a file containing a list of domains to whitelist from being blocked, one per line\&. If either it is in
\fIspam_domains_file\fR
or more realistically in a domain sent by a RTBL host (see option
\fIrtbl_services\fR) then this domain will be ignored and stanzas from there won\(cqt be blocked\&. Use an absolute path, or the
\fI@CONFIG_PATH@\fR
predefined keyword
if the file is available in the configuration directory\&. The default value is
\fInone\fR\&.
.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_antispam:
rtbl_services:
\- xmppbl\&.org
spam_jids_file: "@CONFIG_PATH@/spam_jids\&.txt"
spam_dump_file: "@LOG_PATH@/spam/host\-@HOST@\&.log"
.fi
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#spam|spam\fR
.RE
.SS "mod_auth_fast"
.sp
@@ -2510,9 +2723,11 @@ The option is supposed to be used when
\fIallow_local_users\fR
and
\fIallow_transports\fR
are not enough\&. It\(cqs an ACL where
are not enough\&. It\(cqs an Access Rule where
\fIdeny\fR
means the message will be rejected (or a CAPTCHA would be generated for a presence, if configured), and
means the stanza will be rejected; there\(cqs an exception if option
\fIcaptcha\fR
is configured\&. And
\fIallow\fR
means the sender is whitelisted and the stanza will pass through\&. The default value is
\fInone\fR, which means nothing is whitelisted\&.
@@ -2534,7 +2749,7 @@ and some server\(cqs JID is in user\(cqs roster, then messages from any user of
.PP
\fBcaptcha\fR: \fItrue | false\fR
.RS 4
Whether to generate CAPTCHA or not in response to messages from strangers\&. See also section
Whether to generate CAPTCHA challenges in response to incoming presence subscription requests from strangers\&. See also section
\fIbasic\&.md#captcha|CAPTCHA\fR
of the Configuration Guide\&. The default value is
\fIfalse\fR\&.
@@ -2758,7 +2973,9 @@ While a client is inactive, queue presence stanzas that indicate (un)availabilit
\fItrue\fR\&.
.RE
.RE
.SS "mod_configure"
.SS "mod_configure 🟤"
.sp
\fINote\fR about this option: improved in 25\&.10\&.
.sp
The module provides server configuration functionalities using XEP\-0030: Service Discovery and XEP\-0050: Ad\-Hoc Commands:
.sp
@@ -2796,6 +3013,100 @@ XEP\-0133: Service Administration
Additional custom ad\-hoc commands specific to ejabberd
.RE
.sp
Ad\-hoc commands from XEP\-0133 that behave differently to the XEP:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
get\-user\-roster: returns standard fields instead of roster items that client cannot display
.RE
.sp
Those ad\-hoc commands from XEP\-0133 do not include in the response the client that executed the command:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
get\-active\-users\-num
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
get\-idle\-users\-num
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
get\-active\-users
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
get\-idle\-users
.RE
.sp
Those ad\-hoc commands from XEP\-0133 are not implemented:
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
edit\-blacklist
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
edit\-whitelist
.RE
.sp
.RS 4
.ie n \{\
\h'-04'\(bu\h'+03'\c
.\}
.el \{\
.sp -1
.IP \(bu 2.3
.\}
edit\-admin
.RE
.sp
This module requires \fImod_adhoc\fR (to execute the commands), and recommends \fImod_disco\fR (to discover the commands)\&.
.sp
Please notice that all the ad\-hoc commands implemented by this module have an equivalent API Command that you can execute using \fImod_adhoc_api\fR or any other API frontend\&.
@@ -2846,7 +3157,7 @@ modules:
.RE
.SS "mod_conversejs"
.sp
\fINote\fR about this option: added in 21\&.12 and improved in 22\&.05\&.
\fINote\fR about this option: improved in 25\&.07\&.
.sp
This module serves a simple page for the Converse XMPP web browser client\&.
.sp
@@ -2856,6 +3167,8 @@ Make sure either \fImod_bosh\fR or \fIlisten\&.md#ejabberd_http_ws|ejabberd_http
.sp
When \fIconversejs_css\fR and \fIconversejs_script\fR are \fIauto\fR, by default they point to the public Converse client\&.
.sp
This module is available since ejabberd 21\&.12\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
@@ -2888,6 +3201,17 @@ about this option: added in 22\&.05\&. Specify additional options to be passed t
Converse configuration\&. Only boolean, integer and string values are supported; lists are not supported\&.
.RE
.PP
\fBconversejs_plugins\fR: \fI[Filename]\fR
.RS 4
List of additional local files to include as scripts in the homepage\&. Please make sure those files are available in the path specified in
\fIconversejs_resources\fR
option, in subdirectory
\fIplugins/\fR\&. If using the public Converse client, then
\fI"libsignal"\fR
gets replaced with the URL of the public library\&. The default value is
\fI[]\fR\&.
.RE
.PP
\fBconversejs_resources\fR: \fIPath\fR
.RS 4
\fINote\fR
@@ -2946,6 +3270,7 @@ listen:
modules:
mod_bosh: {}
mod_conversejs:
conversejs_plugins: ["libsignal"]
websocket_url: "ws://@HOST@:5280/websocket"
.fi
.if n \{\
@@ -2969,7 +3294,9 @@ listen:
modules:
mod_conversejs:
conversejs_resources: "/home/ejabberd/conversejs\-9\&.0\&.0/package/dist"
conversejs_resources: "/home/ejabberd/conversejs\-x\&.y\&.z/package/dist"
conversejs_plugins: ["libsignal\-protocol\&.min\&.js"]
# File path is: /home/ejabberd/conversejs\-x\&.y\&.z/package/dist/plugins/libsignal\-protocol\&.min\&.js
.fi
.if n \{\
.RE
@@ -3232,6 +3559,8 @@ hour\&.
The number of C2S authentication failures to trigger the IP ban\&. The default value is
\fI20\fR\&.
.RE
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#accounts|accounts\fR
.RE
.SS "mod_host_meta"
.sp
@@ -3592,7 +3921,9 @@ A name of the service in the Service Discovery\&. The default value is
.RS 4
This option specifies the initial part of the PUT URLs used for file uploads\&. The keyword
\fI@HOST@\fR
is replaced with the virtual host name\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is
is replaced with the virtual host name\&. And
\fI@HOST_URL_ENCODE@\fR
is replaced with the host name encoded for URL, useful when your virtual hosts contain non\-latin characters\&. NOTE: different virtual hosts cannot use the same PUT URL\&. The default value is
\fI"https://@HOST@:5443/upload"\fR\&.
.RE
.PP
@@ -3873,6 +4204,13 @@ This access rule defines who is allowed to modify the MAM preferences\&. The def
\fIall\fR\&.
.RE
.PP
\fBarchive_muc_as_mucsub 🟤\fR: \fItrue | false\fR
.RS 4
\fINote\fR
about this option: added in 25\&.10\&. When this option is enabled incoming groupchat messages for users that have mucsub subscription to a room from which message originated will have those messages archived after being converted to mucsub event messages\&.The default value is
\fIfalse\fR\&.
.RE
.PP
\fBassume_mam_usage\fR: \fItrue | false\fR
.RS 4
This option determines how ejabberd\(cqs stream management code (see
@@ -3955,12 +4293,14 @@ option, but applied to this module only\&.
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
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#mam|mam\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
.RE
.SS "mod_matrix_gw"
.sp
\fINote\fR about this option: improved in 25\&.03\&.
\fINote\fR about this option: improved in 25\&.08\&.
.sp
Matrix gateway\&. Erlang/OTP 25 or higher is required to use this module\&. This module is available since ejabberd 24\&.02\&.
Matrix gateway\&. Supports room versions 9, 10 and 11 since ejabberd 25\&.03; room versions 4 and higher since ejabberd 25\&.07; room version 12 (hydra rooms) since ejabberd 25\&.08\&. Erlang/OTP 25 or higher is required to use this module\&. This module is available since ejabberd 24\&.02\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
@@ -3990,6 +4330,13 @@ Value of the matrix signing key, in base64\&.
Name of the matrix signing key\&.
.RE
.PP
\fBleave_timeout\fR: \fIinteger()\fR
.RS 4
Delay in seconds between a user leaving a MUC room and sending
\fIleave\fR
Matrix event\&.
.RE
.PP
\fBmatrix_domain\fR: \fIDomain\fR
.RS 4
Specify a domain in the Matrix federation\&. The keyword
@@ -4014,6 +4361,11 @@ is the JID of the gateway service as set by the
option\&. The default is
\fIfalse\fR\&.
.RE
.PP
\fBnotary_servers\fR: \fI[Server, \&.\&.\&.]\fR
.RS 4
A list of notary servers\&.
.RE
.RE
.sp
.it 1 an-trap
@@ -4629,10 +4981,10 @@ in order to accept their join in the room\&. The default value is
Short description of the room\&. The default value is an empty string\&.
.RE
.PP
\fBenable_hats\fR: \fItrue | false\fR
\fBenable_hats 🟤\fR: \fItrue | false\fR
.RS 4
\fINote\fR
about this option: improved in 25\&.03\&. Allow extended roles as defined in XEP\-0317 Hats\&. Check the
about this option: improved in 25\&.10\&. Allow extended roles as defined in XEP\-0317 Hats\&. Check the
\fI\&.\&./\&.\&./tutorials/muc\-hats\&.md|MUC Hats\fR
tutorial\&. The default value is
\fIfalse\fR\&.
@@ -4978,6 +5330,8 @@ about this option: added in 22\&.05\&. How many users can be subscribed to a roo
API\&. The default value is
\fI50\fR\&.
.RE
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc|muc\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc_room|muc_room\fR, \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#muc_sub|muc_sub\fR
.RE
.SS "mod_muc_log"
.sp
@@ -5555,6 +5909,8 @@ modules:
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#offline|offline\fR
.RE
.SS "mod_ping"
.sp
@@ -5798,6 +6154,8 @@ Same as top\-level
\fIuse_cache\fR
option, but applied to this module only\&.
.RE
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#private|private\fR
.RE
.SS "mod_privilege"
.sp
@@ -5965,6 +6323,199 @@ modules:
.RE
.\}
.RE
.SS "mod_providers"
.sp
\fINote\fR about this option: added in 25\&.08\&.
.sp
This module serves JSON provider files API v2 as described by XMPP Providers\&.
.sp
It attempts to fill some properties gathering values automatically from your existing ejabberd configuration\&. Try enabling the module, check what values are displayed, and then customize using the options\&.
.sp
To use this module, in addition to adding it to the \fImodules\fR section, you must also enable it in \fIlisten\fR → \fIejabberd_http\fR → \fIlisten\-options\&.md#request_handlers|request_handlers\fR\&. Notice you should set in \fIlisten\&.md#ejabberd_http|ejabberd_http\fR the option \fIlisten\-options\&.md#tls|tls\fR enabled\&.
.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
\fBalternativeJids\fR: \fI[string()]\fR
.RS 4
List of JIDs (XMPP server domains) a provider offers for registration other than its main JID\&. The default value is
\fI[]\fR\&.
.RE
.PP
\fBbusFactor\fR: \fIinteger()\fR
.RS 4
Bus factor of the XMPP service (i\&.e\&., the minimum number of team members that the service could not survive losing) or
\fI\-1\fR
for n/a\&. The default value is
\fI\-1\fR\&.
.RE
.PP
\fBfreeOfCharge\fR: \fItrue | false\fR
.RS 4
Whether the XMPP service can be used for free\&. The default value is
\fIfalse\fR\&.
.RE
.PP
\fBlanguages\fR: \fI[string()]\fR
.RS 4
List of language codes that your pages are available\&. Some options define URL where the keyword
\fI@LANGUAGE_URL@\fR
will be replaced with each of those language codes\&. The default value is a list with the language set in the option
\fIlanguage\fR, for example:
\fI[en]\fR\&.
.RE
.PP
\fBlegalNotice\fR: \fIstring()\fR
.RS 4
Legal notice web page (per language)\&. The keyword
\fI@LANGUAGE_URL@\fR
is replaced with each language\&. The default value is
\fI""\fR\&.
.RE
.PP
\fBmaximumHttpFileUploadStorageTime\fR: \fIinteger()\fR
.RS 4
Maximum storage duration of each shared file (number in days,
\fI0\fR
for no limit or
\fI\-1\fR
for less than 1 day)\&. The default value is the same as option
\fImax_days\fR
from module
\fImod_http_upload_quota\fR, or
\fI0\fR
otherwise\&.
.RE
.PP
\fBmaximumHttpFileUploadTotalSize\fR: \fIinteger()\fR
.RS 4
Maximum size of all shared files in total per user (number in megabytes (MB),
\fI0\fR
for no limit or
\fI\-1\fR
for less than 1 MB)\&. Attention: MB is used instead of MiB (e\&.g\&., 104,857,600 bytes = 100 MiB H 104 MB)\&. This property is not about the maximum size of each shared file, which is already retrieved via XMPP\&. The default value is the value of the shaper value of option
\fIaccess_hard_quota\fR
from module
\fImod_http_upload_quota\fR, or
\fI0\fR
otherwise\&.
.RE
.PP
\fBmaximumMessageArchiveManagementStorageTime\fR: \fIinteger()\fR
.RS 4
Maximum storage duration of each exchanged message (number in days,
\fI0\fR
for no limit or
\fI\-1\fR
for less than 1 day)\&. The default value is
\fI0\fR\&.
.RE
.PP
\fBorganization\fR: \fIstring()\fR
.RS 4
Type of organization providing the XMPP service\&. Allowed values are:
\fIcompany\fR,
\fI"commercial person"\fR,
\fI"private person"\fR,
\fIgovernmental\fR,
\fI"non\-governmental"\fR
or
\fI""\fR\&. The default value is
\fI""\fR\&.
.RE
.PP
\fBpasswordReset\fR: \fIstring()\fR
.RS 4
Password reset web page (per language) used for an automatic password reset (e\&.g\&., via email) or describing how to manually reset a password (e\&.g\&., by contacting the provider)\&. The keyword
\fI@LANGUAGE_URL@\fR
is replaced with each language\&. The default value is an URL built automatically if
\fImod_register_web\fR
is configured as a
\fIrequest_handler\fR, or
\fI""\fR
otherwise\&.
.RE
.PP
\fBprofessionalHosting\fR: \fItrue | false\fR
.RS 4
Whether the XMPP server is hosted with good internet connection speed, uninterruptible power supply, access protection and regular backups\&. The default value is
\fIfalse\fR\&.
.RE
.PP
\fBserverLocations\fR: \fI[string()]\fR
.RS 4
List of language codes of Server/Backup locations\&. The default value is an empty list:
\fI[]\fR\&.
.RE
.PP
\fBserverTesting\fR: \fItrue | false\fR
.RS 4
Whether tests against the provider\(cqs server are allowed (e\&.g\&., certificate checks and uptime monitoring)\&. The default value is
\fIfalse\fR\&.
.RE
.PP
\fBsince\fR: \fIstring()\fR
.RS 4
Date since the XMPP service is available\&. The default value is an empty string:
\fI""\fR\&.
.RE
.PP
\fBwebsite\fR: \fIstring()\fR
.RS 4
Provider website\&. The keyword
\fI@LANGUAGE_URL@\fR
is replaced with each language\&. The default value is
\fI""\fR\&.
.RE
.RE
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBExample:\fR
.RS 4
.sp
.if n \{\
.RS 4
.\}
.nf
listen:
\-
port: 443
module: ejabberd_http
tls: true
request_handlers:
/\&.well\-known/xmpp\-provider\-v2\&.json: mod_providers
modules:
mod_providers:
alternativeJids: ["example1\&.com", "example2\&.com"]
busFactor: 1
freeOfCharge: true
languages: [ag, ao, bg, en]
legalNotice: "http://@HOST@/legal/@LANGUAGE_URL@/"
maximumHttpFileUploadStorageTime: 0
maximumHttpFileUploadTotalSize: 0
maximumMessageArchiveManagementStorageTime: 0
organization: "non\-governmental"
passwordReset: "http://@HOST@/reset/@LANGUAGE_URL@/"
professionalHosting: true
serverLocations: [ao, bg]
serverTesting: true
since: "2025\-12\-31"
website: "http://@HOST@/website/@LANGUAGE_URL@/"
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_proxy65"
.sp
This module implements XEP\-0065: SOCKS5 Bytestreams\&. It allows ejabberd to act as a file transfer proxy between two XMPP clients\&.
@@ -6426,6 +6977,61 @@ modules:
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
.RE
.SS "mod_pubsub_serverinfo"
.sp
\fINote\fR about this option: added in 25\&.07\&.
.sp
This module adds support for XEP\-0485: PubSub Server Information to expose S2S information over the Pub/Sub service\&.
.sp
Active S2S connections are published to a local PubSub node\&. Currently the node name is hardcoded as \fI"serverinfo"\fR\&.
.sp
Connections that support this feature are exposed with their domain names, otherwise they are shown as anonymous nodes\&. At startup a list of well known public servers is fetched\&. Those are not shown as anonymous even if they don\(cqt support this feature\&.
.sp
Please note that the module only shows S2S connections established while the module is running\&. If you install the module at runtime, run \fIstop_s2s_connections\fR API or restart ejabberd to force S2S reconnections that the module will detect and publish\&.
.sp
This module depends on \fImod_pubsub\fR and \fImod_disco\fR\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBAvailable options:\fR
.RS 4
.PP
\fBpubsub_host\fR: \fIundefined | string()\fR
.RS 4
Use this local PubSub host to advertise S2S connections\&. This must be a host local to this service handled by
\fImod_pubsub\fR\&. This option is only needed if your configuration has more than one host in mod_pubsub\(cqs
\fIhosts\fR
option\&. The default value is the first host defined in mod_pubsub
\fIhosts\fR
option\&.
.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_pubsub_serverinfo:
pubsub_host: custom\&.pubsub\&.domain\&.local
.fi
.if n \{\
.RE
.\}
.RE
.SS "mod_push"
.sp
@@ -6496,6 +7102,8 @@ Same as top\-level
\fIuse_cache\fR
option, but applied to this module only\&.
.RE
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#purge|purge\fR
.RE
.SS "mod_push_keepalive"
.sp
@@ -6845,6 +7453,8 @@ modules:
.if n \{\
.RE
.\}
.sp
\fBAPI Tags:\fR \fI\&.\&./\&.\&./developer/ejabberd\-api/admin\-tags\&.md#roster|roster\fR
.RE
.SS "mod_s2s_bidi"
.sp
@@ -8372,7 +8982,7 @@ Should the operating system be revealed or not\&. The default value is
.RE
.SH "LISTENERS"
.sp
This section describes listeners options of ejabberd 25\&.04\&.
This section describes listeners options of ejabberd 25\&.10\&.
.sp
TODO
.SH "AUTHOR"
@@ -8380,13 +8990,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 25\&.04\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 25\&.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/25\&.04/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/25\&.10/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp
+7 -7
View File
@@ -85,7 +85,6 @@ defmodule Ejabberd.MixProject do
result = [{:d, :ELIXIR_ENABLED}] ++
cond_options() ++
Enum.map(includes, fn (path) -> {:i, path} end) ++
if_version_above(~c"20", [{:d, :DEPRECATED_GET_STACKTRACE}]) ++
if_version_above(~c"20", [{:d, :HAVE_URI_STRING}]) ++
if_version_above(~c"20", [{:d, :HAVE_ERL_ERROR}]) ++
if_version_below(~c"21", [{:d, :USE_OLD_HTTP_URI}]) ++
@@ -99,6 +98,7 @@ defmodule Ejabberd.MixProject do
if_version_below(~c"25", [{:d, :OTP_BELOW_25}]) ++
if_version_below(~c"26", [{:d, :OTP_BELOW_26}]) ++
if_version_below(~c"27", [{:d, :OTP_BELOW_27}]) ++
if_version_below(~c"27", [{:feature, :maybe_expr, :enable}]) ++
if_version_below(~c"28", [{:d, :OTP_BELOW_28}])
defines = for {:d, value} <- result, do: {:d, value}
result ++ [{:d, :ALL_DEFS, defines}]
@@ -110,7 +110,7 @@ defmodule Ejabberd.MixProject do
{config(:debug), :debug_info},
{not config(:debug), {:debug_info, false}},
{config(:roster_gateway_workaround), {:d, :ROSTER_GATEWAY_WORKAROUND}},
{config(:new_sql_schema), {:d, :NEW_SQL_SCHEMA}}
{config(:multihost_sql_schema), {:d, :MULTIHOST_SQL_SCHEMA}}
], do:
option
end
@@ -120,18 +120,18 @@ defmodule Ejabberd.MixProject do
{:dialyxir, "~> 1.2", only: [:test], runtime: false},
{:eimp, "~> 1.0"},
{:ex_doc, "~> 0.31", only: [:edoc], runtime: false},
{:fast_tls, "~> 1.1.22"},
{:fast_xml, "~> 1.1.53"},
{:fast_tls, "~> 1.1.24"},
{:fast_xml, "~> 1.1.56"},
{:fast_yaml, "~> 1.0"},
{:idna, "~> 6.0"},
{:mqtree, "~> 1.0"},
{:p1_acme, "~> 1.0"},
{:p1_acme, ">= 1.0.28"},
{:p1_oauth2, "~> 0.6"},
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:xmpp, "~> 1.10.0"},
{:yconf, ">= 1.0.18"}]
{:xmpp, ">= 1.11.2"},
{:yconf, ">= 1.0.22"}]
++ cond_deps()
end
+19 -19
View File
@@ -1,20 +1,20 @@
%{
"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.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"cache_tab": {:hex, :cache_tab, "1.0.33", "e2542afb34f17ee3ca19d2b0f546a074922c2b99fb6b2acfb38160d7d0336ec3", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "4258009eb050b22aabe0c848e230bba58401a6895c58c2ff74dfb635e3c35900"},
"dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"eimp": {:hex, :eimp, "1.0.24", "853db317ba394d479d2f1181e0b0135a3cd3c2c7eb48ff6cfb035a28dd354b14", [:rebar3], [{:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "7d61432eb8a45659c0be475f44e75eeb651743aa64a1de8adf785cdad81961ad"},
"eimp": {:hex, :eimp, "1.0.26", "c0b05f32e35629c4d9bcfb832ff879a92b0f92b19844bc7835e0a45635f2899a", [:rebar3], [{:p1_utils, "~> 1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "d96d4e8572b9dfc40f271e47f0cb1d8849373bc98a21223268781765ed52044c"},
"epam": {:hex, :epam, "1.0.14", "aa0b85d27f4ef3a756ae995179df952a0721237e83c6b79d644347b75016681a", [:rebar3], [], "hexpm", "2f3449e72885a72a6c2a843f561add0fc2f70d7a21f61456930a547473d4d989"},
"eredis": {:hex, :eredis, "1.7.1", "39e31aa02adcd651c657f39aafd4d31a9b2f63c6c700dc9cece98d4bc3c897ab", [:mix, :rebar3], [], "hexpm", "7c2b54c566fed55feef3341ca79b0100a6348fd3f162184b7ed5118d258c3cc1"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"esip": {:hex, :esip, "1.0.57", "4b14e4832d08b9ffc10d855b5d10b3083232b1d53deb4c046679496ce85569c4", [: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.17", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "19c357e1817b1e04792ef359bf900400f3e6d0e5ade929fd72f88ea9b44af2ed"},
"ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [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", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"},
"esip": {:hex, :esip, "1.0.59", "eb202f8c62928193588091dfedbc545fe3274c34ecd209961f86dcb6c9ebce88", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.21", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "0bdf2e3c349dc0b144f173150329e675c6a51ac473d7a0b2e362245faad3fbe6"},
"ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [:mix], [{:earmark_parser, "~> 1.4.44", [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", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"},
"exsync": {:hex, :exsync, "0.4.1", "0a14fe4bfcb80a509d8a0856be3dd070fffe619b9ba90fec13c58b316c176594", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "cefb22aa805ec97ffc5b75a4e1dc54bcaf781e8b32564bf74abbe5803d1b5178"},
"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.22", "44356b256afad4399c2fc5059a3066669dafd8bd4e4e796c9c1cf8910ddd265e", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "e65779aefb7ab15c4755230fef8077e687d20cc5a3984a5974f9f657e8e2485b"},
"fast_xml": {:hex, :fast_xml, "1.1.55", "ace020f2521f2a484ac8467d2822af85534a346e2aae03ffcbc34f29318befaf", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "83f3e23a780ed5f567cdec73953f06c95b838d709dbfa86b59a98a8d23c99f85"},
"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.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"ezlib": {:hex, :ezlib, "1.0.15", "d74f5df191784744726a5b1ae9062522c606334f11086363385eb3b772d91357", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "dd14ba6c12521af5cfe6923e73e3d545f4a0897dc66bfab5287fbb7ae3962eab"},
"fast_tls": {:hex, :fast_tls, "1.1.25", "da8ed6f05a2452121b087158b17234749f36704c1f2b74dc51db99a1e27ed5e8", [:rebar3], [{:p1_utils, "~> 1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "59e183b5740e670e02b8aa6be673b5e7779e5fe5bfcc679fe2d4993d1949a821"},
"fast_xml": {:hex, :fast_xml, "1.1.57", "31efc0f9bceda92069704f7a25830407da5dc3dad1272b810d6f2e13e73cc11a", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "eec34e90adacafe467d5ddab635a014ded73b98b4061554b2d1972173d929c39"},
"fast_yaml": {:hex, :fast_yaml, "1.0.39", "2e71168091949bab0e5f583b340a99072b4d22d93eb86624e7850a12b1517be4", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "24c7b9ab9e2b9269d64e45f4a2a1280966adb17d31e63365cfd3ee277fb0a78d"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"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"},
@@ -22,18 +22,18 @@
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [: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", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"mqtree": {:hex, :mqtree, "1.0.17", "82f54b8f2d22b4445db1d6cccb7fe9ead049d61410c29e32475f3ceb3ee62a89", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "5fe8b7cf8fbc4783d0fceb94654ac2bbf3242a58cd0397d249ded8ae021be2a3"},
"mqtree": {:hex, :mqtree, "1.0.19", "d769c25f898810725fc7db0dbffe5f72098647048b1be2e6d772f1c2f31d8476", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "c81065715c49a1882812f80a5ae2d842e80dd3f2d130530df35990248bf8ce3c"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"p1_acme": {:hex, :p1_acme, "1.0.25", "db91f0d6c193cd1d5c0b0fa3939a898dbf56a6075db4347cde26e802715de50c", [: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.17", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "a7b55b47495ddb4f98a15e65451ec3ad43f4637b955c74cd695d98e6a645d08c"},
"p1_acme": {:hex, :p1_acme, "1.0.29", "86372c34de9ce7e498842828f761060c6b7726001e2853ff118893f6dd192e24", [: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.17", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "08fd38f7fbe2dc28a237aa1b38b306b73455695cc8881a3ddd6a11b7c51f7bc7"},
"p1_mysql": {:hex, :p1_mysql, "1.0.26", "574d07c9936c53b1ec3556db3cf064cc14a6c39039835b3d940471bfa5ac8e2b", [:rebar3], [], "hexpm", "ea138083f2c54719b9cf549dbf5802a288b0019ea3e5449b354c74cc03fafdec"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.14", "1c5f82535574de87e2059695ac4b91f8f9aebacbc1c80287dae6f02552d47aea", [:rebar3], [], "hexpm", "1fd3ac474e43722d9d5a87c6df8d36f698ed87af7bb81cbbb66361451d99ae8f"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.32", "3f95d7e3413fc8f0be80abb4be1a0d7f67066a36905085cd5a423145598b0cb0", [:rebar3], [{:xmpp, "~> 1.10.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "268b01e8f4eb75c211a31495a25c2815c549aecce2f0df1a161c6e0a2cde061e"},
"p1_utils": {:hex, :p1_utils, "1.0.26", "67b0c4ac9fa3ba3ef563b31aa111b0a004439a37fac85e027f1c3617e1c7ec6c", [:rebar3], [], "hexpm", "d0379e8c1156b98bd64f8129c1de022fcca4f2fdb7486ce73bf0ed2c3376b04c"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.36", "4bc0535f2f9a2355674dfdc9e1cfc18a722325fed588977fff1c88c5915ae3eb", [:rebar3], [{:xmpp, "~> 1.11.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "82bca8b895c84f4600eb8d609a32cb5fdd72a7f5bd938dfb29179e08c643fd09"},
"p1_utils": {:hex, :p1_utils, "1.0.28", "9a7088a98d788b4c4880fd3c82d0c135650db13f2e4ef7e10db179791bc94d59", [:rebar3], [], "hexpm", "c49bd44bc4a40ad996691af826dd7e0aa56d4d0cd730817190a1f84d1a7f0033"},
"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.31", "fa1688c156dd271722aa18c423a4163e710d2f4f475ad0bc220910df669b53af", [:rebar3], [{:p1_utils, "1.0.26", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "e9699c88e8db16b3a41f0e45ac6874a4da81a6e4854a77d76ede6d09b08e3530"},
"stun": {:hex, :stun, "1.2.17", "c54614a592812ea125a2e6827aac5a438571b591616426ec1419ba9b48252f54", [: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", "6b318244c21e8524a9aae3ac9a05cd8234ee994c1c2c815de68d306086ad768d"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"xmpp": {:hex, :xmpp, "1.10.0", "68a6dff8db8987c4592b2d5dd71d3f947b4ebd15209c9acaca5909a642670630", [: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", "ceeae43b8fe97649d8f8546b3f7f2b38ecfc931c0cdd5c7445ffb3f80fcb7d85"},
"yconf": {:hex, :yconf, "1.0.18", "e565edc8aabb8164c3bebc86969095d296ad315dcbb46af65dccbc6c71eae0f6", [:rebar3], [{:fast_yaml, "1.0.37", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "fa950ec6503f92d6417fb8cc1d982403f041697e8e1bbf4d4588fb919b9562ea"},
"stringprep": {:hex, :stringprep, "1.0.33", "22f42866b4f6f3c238ea2b9cb6241791184ddedbab55e94a025511f46325f3ca", [:rebar3], [{:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "96f8b30bc50887f605b33b46bca1d248c19a879319b8c482790e3b4da5da98c0"},
"stun": {:hex, :stun, "1.2.21", "735855314ad22cb7816b88597d2f5ca22e24aa5e4d6010a0ef3affb33ceed6a5", [:rebar3], [{:fast_tls, "1.1.25", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.28", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3d7fe8efb9d05b240a6aa9a6bf8b8b7bff2d802895d170443c588987dc1e12d9"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"xmpp": {:hex, :xmpp, "1.11.2", "6ef43a6e5fb71506af7eccd05bbb6cccb58eb1856c539613e76fd9a5c4e936ba", [: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.26", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stringprep, "~> 1.0.29", [hex: :stringprep, repo: "hexpm", optional: false]}], "hexpm", "bb681644e15e3efc0008ab3a717944d67cf611a4b7e344382aa6367447bd52d2"},
"yconf": {:hex, :yconf, "1.0.22", "52a435f9b60ab1e13950dfe3f7131ecdd8b3d1ca72c44bf66fc74b4571027124", [:rebar3], [{:fast_yaml, "1.0.39", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "aca83457ceabe70756484b5c87ba7b1955f511d499168687eaeaa7c300e857f1"},
}
+4 -1
View File
@@ -131,9 +131,12 @@ ul li #navhead a, ul li #navheadsub a, ul li #navheadsubsub a {
background: #424a55;
color: #fff;
}
#navitemlogin-start {
border-top: 0.2em solid #cae7e4;
}
#navitemlogin {
padding: 0.5em;
border-top: 0.2em solid #cae7e4;
border-bottom: 0.2em solid #cae7e4;
padding-left: 0.5em;
}
+23 -2
View File
@@ -15,7 +15,6 @@
{"Access model","Model d'Accés"}.
{"Account doesn't exist","El compte no existeix"}.
{"Action on user","Acció en l'usuari"}.
{"Add a hat to a user","Afegir un barret a un usuari"}.
{"Add User","Afegir usuari"}.
{"Administration of ","Administració de "}.
{"Administration","Administració"}.
@@ -49,6 +48,7 @@
{"API Commands","Comandaments API"}.
{"April","Abril"}.
{"Arguments","Arguments"}.
{"Assign a hat to a user","Asignar un barret a un usuari"}.
{"Attribute 'channel' is required for this request","L'atribut 'channel' és necessari per a aquesta petició"}.
{"Attribute 'id' is mandatory for MIX messages","L'atribut 'id' es necessari per a missatges MIX"}.
{"Attribute 'jid' is not allowed here","L'atribut 'jid' no està permès ací"}.
@@ -94,7 +94,9 @@
{"Configuration of room ~s","Configuració de la sala ~s"}.
{"Configuration","Configuració"}.
{"Contact Addresses (normally, room owner or owners)","Adreces de contacte (normalment, propietaris de la sala)"}.
{"Contacts","Contactes"}.
{"Country","Pais"}.
{"Create a Hat","Crear un barret"}.
{"Current Discussion Topic","Assumpte de discussió actual"}.
{"Database failure","Error a la base de dades"}.
{"Database Tables Configuration at ","Configuració de la base de dades en "}.
@@ -106,6 +108,8 @@
{"Delete User","Eliminar Usuari"}.
{"Deliver event notifications","Entrega de notificacions d'events"}.
{"Deliver payloads with event notifications","Enviar payloads junt a les notificacions d'events"}.
{"Destroy a Hat","Destruir un barret"}.
{"Disable User","Deshabilitar un Usuari"}.
{"Disc only copy","Còpia sols en disc"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","No li donis la teva contrasenya a ningú, ni tan sols als administradors del servidor XMPP."}.
{"Dump Backup to Text File at ","Exporta còpia de seguretat a fitxer de text en "}.
@@ -157,12 +161,19 @@
{"Full List of Room Admins","Llista completa de administradors de la sala"}.
{"Full List of Room Owners","Llista completa de propietaris de la sala"}.
{"Full Name","Nom complet"}.
{"Get List of Active Users","Obté la llista d'usuaris actius"}.
{"Get List of Disabled Users","Obté la llista d'usuaris deshabilitats"}.
{"Get List of Idle Users","Obté la llista d'usuaris inactius"}.
{"Get List of Online Users","Obté la llista d'usuaris en línia"}.
{"Get List of Registered Users","Obté la llista d'usuaris registrats"}.
{"Get Number of Active Users","Obtenir Número d'Usuaris Actius"}.
{"Get Number of Disabled Users","Obtenir Número d'Usuaris Deshabilitats"}.
{"Get Number of Idle Users","Obtenir Número d'Usuaris Inactius"}.
{"Get Number of Online Users","Obtenir Número d'Usuaris Connectats"}.
{"Get Number of Registered Users","Obtenir Número d'Usuaris Registrats"}.
{"Get Pending","Obtenir Pendents"}.
{"Get User Last Login Time","Obtenir la última connexió d'Usuari"}.
{"Get User Roster","Obtenir llista de contactes de l'usuari"}.
{"Get User Statistics","Obtenir Estadístiques d'Usuari"}.
{"Given Name","Nom propi"}.
{"Grant voice to this person?","Concedir veu a aquesta persona?"}.
@@ -171,7 +182,9 @@
{"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 computed from the list of hats available in a room","Hash computat de la llista de barrets disponibles a una sala"}.
{"Hash of the vCard-temp avatar of this room","Hash del avatar a vCard-temp d'esta sala"}.
{"Hat hue","To de color del barret"}.
{"Hat title","Títol del barret"}.
{"Hat URI","URI del barret"}.
{"Hats limit exceeded","El límit de tràfic ha sigut sobrepassat"}.
@@ -225,7 +238,7 @@
{"Last year","Últim any"}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Els bits menys significants del hash SHA-256 del text deurien ser iguals a l'etiqueta hexadecimal"}.
{"leaves the room","surt de la sala"}.
{"List of users with hats","Llista d'usuaris amb barrets"}.
{"List of Hats","Llista de barrets"}.
{"List users with hats","Llista d'usuaris amb barrets"}.
{"Logged Out","Desconectat"}.
{"Logging","Registre"}.
@@ -319,7 +332,10 @@
{"Notify subscribers when the node configuration changes","Notificar subscriptors quan canvia la configuració del node"}.
{"Notify subscribers when the node is deleted","Notificar subscriptors quan el node és eliminat"}.
{"November","Novembre"}.
{"Number of active users","Número d'usuaris actius"}.
{"Number of answers required","Número de respostes requerides"}.
{"Number of disabled users","Número d'Usuaris Deshabilitats"}.
{"Number of idle users","Número d'usuaris inactius"}.
{"Number of occupants","Número d'ocupants"}.
{"Number of Offline Messages","Número de missatges offline"}.
{"Number of online users","Número d'usuaris connectats"}.
@@ -395,6 +411,7 @@
{"Receive notification of new items only","Rebre notificació només de nous elements"}.
{"Receive notification of new nodes only","Rebre notificació només de nous nodes"}.
{"Recipient is not in the conference room","El receptor no està en la sala de conferència"}.
{"Re-Enable User","Rehabilitar usuari"}.
{"Register an XMPP account","Registrar un compte XMPP"}.
{"Register","Registrar"}.
{"Remote copy","Còpia remota"}.
@@ -489,6 +506,9 @@
{"The JIDs of those to contact with questions","Els JIDs a qui contactar amb preguntes"}.
{"The JIDs of those with an affiliation of owner","Els JIDs de qui tenen una afiliació de propietaris"}.
{"The JIDs of those with an affiliation of publisher","Els JIDs de qui tenen una afiliació de publicadors"}.
{"The list of all active users","La llista de tots els usuaris actius"}.
{"The list of all disabled users","La llista de tots els usuaris deshabilitats"}.
{"The list of all idle users","La llista de tots els usuaris inactius"}.
{"The list of all online users","La llista de tots els usuaris en línia"}.
{"The list of all users","La llista de tots els usuaris"}.
{"The list of JIDs that may associate leaf nodes with a collection","La llista de JIDs que poden associar nodes fulla amb una col·lecció"}.
@@ -510,6 +530,7 @@
{"The presence states for which an entity wants to receive notifications","El estats de presencia per als quals una entitat vol rebre notificacions"}.
{"The query is only allowed from local users","La petició està permesa només d'usuaris locals"}.
{"The query must not contain <item/> elements","La petició no pot contenir elements <item/>"}.
{"The role","El rol"}.
{"The room subject can be modified by participants","El tema de la sala pot modificar-lo els participants"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","La informació semàntica de les dades al node, usualment especificat pel espai de noms de la càrrega util (si n'hi ha)"}.
{"The sender of the last received message","Qui ha enviat l'ultim missatge rebut"}.
+48 -1
View File
@@ -12,9 +12,10 @@
{"A Web Page","Μία ιστοσελίδα"}.
{"Accept","Αποδοχή"}.
{"Access denied by service policy","Άρνηση πρόσβασης, λόγω τακτικής παροχής υπηρεσιών"}.
{"Access model","Καθορίστε το μοντέλο πρόσβασης"}.
{"Access model","Μοντέλο πρόσβασης"}.
{"Account doesn't exist","Ο λογαριασμός δεν υπάρχει"}.
{"Action on user","Eνέργεια για το χρήστη"}.
{"Add a hat to a user","Προσθέστε ένα καπέλο σε έναν χρήστη"}.
{"Add User","Προσθήκη Χρήστη"}.
{"Administration of ","Διαχείριση του "}.
{"Administration","Διαχείριση"}.
@@ -45,7 +46,9 @@
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Όποιος έχει συνδρομή παρουσίας και των δύο ή από μπορεί να εγγραφεί και να ανακτήσει στοιχεία"}.
{"Anyone with Voice","Οποιοσδήποτε με Φωνή"}.
{"Anyone","Οποιοσδήποτε"}.
{"API Commands","Εντολές του API"}.
{"April","Απρίλιος"}.
{"Arguments","Επιχειρήματα"}.
{"Attribute 'channel' is required for this request","Το δηλωτικό 'channel' απαιτείται για αυτό το Ερώτημα"}.
{"Attribute 'id' is mandatory for MIX messages","Το δηλωτικό 'id' επιτακτικό για μηνύματα MIX"}.
{"Attribute 'jid' is not allowed here","Το δηλωτικό 'jid' δεν επιτρέπεται εδώ"}.
@@ -71,6 +74,7 @@
{"Changing role/affiliation is not allowed","Η αλλαγή ρόλου/ομάδας δεν επιτρέπεται"}.
{"Channel already exists","Το κανάλι υπάρχει ήδη"}.
{"Channel does not exist","Το κανάλι δεν υπάρχει"}.
{"Channel JID","JID καναλιού"}.
{"Channels","Κανάλια"}.
{"Characters not allowed:","Χαρακτήρες που δεν επιτρέπονται:"}.
{"Chatroom configuration modified","Η ρύθμιση παραμέτρων της αίθουσας σύνεδριασης τροποποιηθηκε"}.
@@ -84,6 +88,7 @@
{"Choose whether to approve this entity's subscription.","Επιλέξτε αν θα εγκρίθεί η εγγραφή αυτής της οντότητας."}.
{"City","Πόλη"}.
{"Client acknowledged more stanzas than sent by server","Ο πελάτης γνωρίζει περισσότερα δωμάτια από αυτά που στάλθηκαν από τον εξυπηρετητή"}.
{"Clustering","Συσταδοποίηση"}.
{"Commands","Εντολές"}.
{"Conference room does not exist","Η αίθουσα σύνεδριασης δεν υπάρχει"}.
{"Configuration of room ~s","Διαμόρφωση δωματίου ~ s"}.
@@ -119,6 +124,7 @@
{"ejabberd","ejabberd"}.
{"Email Address","Ηλεκτρονική Διεύθυνση"}.
{"Email","Ηλεκτρονικό ταχυδρομείο"}.
{"Enable hats","Ενεργοποίηση καπέλων"}.
{"Enable logging","Ενεργοποίηση καταγραφής"}.
{"Enable message archiving","Ενεργοποιήστε την αρχειοθέτηση μηνυμάτων"}.
{"Enabling push without 'node' attribute is not supported","Η ενεργοποίηση της ώθησης χωρίς το χαρακτηριστικό 'κόμβος' δεν υποστηρίζεται"}.
@@ -151,6 +157,8 @@
{"Full List of Room Admins","Πλήρης Κατάλογος Διαχειριστών αιθουσών"}.
{"Full List of Room Owners","Πλήρης Κατάλογος Ιδιοκτητών αιθουσών"}.
{"Full Name","Ονοματεπώνυμο"}.
{"Get List of Online Users","Λίστα online χρηστών"}.
{"Get List of Registered Users","Λίστα εγγεγραμμένων χρηστών"}.
{"Get Number of Online Users","Έκθεση αριθμού συνδεδεμένων χρηστών"}.
{"Get Number of Registered Users","Έκθεση αριθμού εγγεγραμμένων χρηστών"}.
{"Get Pending","Λήψη των εκκρεμοτήτων"}.
@@ -163,6 +171,10 @@
{"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","Hash του vCard-temp avatar αυτού του δωματίου"}.
{"Hat title","Τίτλος καπέλου"}.
{"Hat URI","Καπέλο URI"}.
{"Hats limit exceeded","Υπέρβαση του ορίου καπέλων"}.
{"Host unknown","Άγνωστος εξυπηρετητής"}.
{"HTTP File Upload","Ανέβασμα αρχείου"}.
{"Idle connection","Αδρανής σύνδεση"}.
@@ -183,6 +195,8 @@
{"Incorrect value of 'action' attribute","Λανθασμένη τιμή του χαρακτηριστικού 'action'"}.
{"Incorrect value of 'action' in data form","Λανθασμένη τιμή 'action' στη φόρμα δεδομένων"}.
{"Incorrect value of 'path' in data form","Λανθασμένη τιμή 'path' στη φόρμα δεδομένων"}.
{"Installed Modules:","Εγκατεστημένες ενότητες:"}.
{"Install","Εγκατάσταση"}.
{"Insufficient privilege","Ανεπαρκή προνόμια"}.
{"Internal server error","Εσωτερικό σφάλμα"}.
{"Invalid 'from' attribute in forwarded message","Μη έγκυρο χαρακτηριστικό 'από' στο προωθούμενο μήνυμα"}.
@@ -198,6 +212,8 @@
{"January","Ιανουάριος"}.
{"JID normalization denied by service policy","Απετράπη η κανονικοποίηση του JID, λόγω της τακτικής Παροχής Υπηρεσιών"}.
{"JID normalization failed","Απετράπη η κανονικοποίηση του JID"}.
{"Joined MIX channels of ~ts","Ενσωματωμένα κανάλια MIX του ~ts"}.
{"Joined MIX channels:","Ενσωματωμένα κανάλια MIX:"}.
{"joins the room","συνδέεται στην αίθουσα"}.
{"July","Ιούλιος"}.
{"June","Ιούνιος"}.
@@ -209,6 +225,9 @@
{"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"}.
@@ -220,6 +239,7 @@
{"Malformed username","Λανθασμένη μορφή ονόματος χρήστη"}.
{"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 payload size in bytes","Μέγιστο μέγεθος φορτίου σε bytes"}.
{"Maximum file size","Μέγιστο μέγεθος αρχείου"}.
{"Maximum Number of History Messages Returned by Room","Μέγιστος αριθμός μηνυμάτων Ιστορικού που επιστρέφονται από την Αίθουσα"}.
@@ -290,6 +310,7 @@
{"Node ~p","Κόμβος ~p"}.
{"Nodeprep has failed","Το Nodeprep απέτυχε"}.
{"Nodes","Κόμβοι"}.
{"Node","Κόμβος"}.
{"None","Κανένα"}.
{"Not allowed","Δεν επιτρέπεται"}.
{"Not Found","Δε βρέθηκε"}.
@@ -303,7 +324,9 @@
{"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","Επιτρέψτε στους χρήστες να αλλάζουν το Θέμα"}.
{"October","Οκτώβριος"}.
{"OK","Εντάξει"}.
@@ -317,6 +340,7 @@
{"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 can approve voice requests","Μόνο οι συντονιστές μπορούν να εγκρίνουν τις αιτήσεις φωνής"}.
{"Only occupants are allowed to send messages to the conference","Μόνο οι συμμετέχοντες επιτρέπεται να στέλνουν μηνύματα στο συνέδριο"}.
{"Only occupants are allowed to send queries to the conference","Μόνο οι συμμετέχοντες επιτρέπεται να στείλουν ερωτήματα στη διάσκεψη"}.
@@ -326,9 +350,11 @@
{"Only those on a whitelist may subscribe and retrieve items","Μόνο όσοι βρίσκονται στη λίστα επιτρεπόμενων μπορούν να εγγραφούν και να ανακτήσουν αντικείμενα"}.
{"Organization Name","Όνομα Οργανισμού"}.
{"Organization Unit","Μονάδα Οργανισμού"}.
{"Other Modules Available:","Διαθέσιμες άλλες ενότητες:"}.
{"Outgoing s2s Connections","Εξερχόμενες S2S Συνδέσεις"}.
{"Owner privileges required","Aπαιτούνται προνόμια ιδιοκτήτη"}.
{"Packet relay is denied by service policy","Απαγορεύεται η αναμετάδοση πακέτων, λόγω της τακτικής Παροχής Υπηρεσιών"}.
{"Participant ID","ID συμμετέχοντος"}.
{"Participant","Συμμετέχων"}.
{"Password Verification","Επαλήθευση κωδικού πρόσβασης"}.
{"Password Verification:","Επαλήθευση κωδικού πρόσβασης:"}.
@@ -336,6 +362,7 @@
{"Password:","Κωδικός πρόσβασης:"}.
{"Path to Dir","Τοποθεσία κατάλογου αρχείων"}.
{"Path to File","Τοποθεσία Αρχείου"}.
{"Payload semantic type information","Πληροφορίες σημασιολογικού τύπου ωφέλιμου φορτίου"}.
{"Period: ","Περίοδος: "}.
{"Persist items to storage","Μόνιμη αποθήκευση στοιχείων"}.
{"Persistent","Μόνιμη"}.
@@ -371,6 +398,7 @@
{"Register an XMPP account","Καταχωρείστε έναν XMPP λογαριασμό χρήστη"}.
{"Register","Καταχωρήστε"}.
{"Remote copy","Εξ αποστάσεως αντίγραφο"}.
{"Remove a hat from a user","Αφαίρεση ενός καπέλου από έναν χρήστη"}.
{"Remove User","Αφαίρεση χρήστη"}.
{"Replaced by new connection","Αντικαταστάθηκε από μια νέα σύνδεση"}.
{"Request has timed out","Το αίτημα έληξε"}.
@@ -383,6 +411,7 @@
{"Restore binary backup immediately:","Επαναφορά δυαδικού αντιγράφου ασφαλείας αμέσως:"}.
{"Restore plain text backup immediately:","Επαναφορά αντιγράφου ασφαλείας από αρχείο κειμένου αμέσως:"}.
{"Restore","Επαναφορά Αντιγράφου Ασφαλείας"}.
{"Result","Αποτέλεσμα"}.
{"Roles and Affiliations that May Retrieve Member List","Ρόλοι και δεσμοί που μπορούν να λάβουν την λίστα μελών"}.
{"Roles for which Presence is Broadcasted","Ρόλοι των οποίων η παρουσία δηλώνεται δημόσια"}.
{"Roles that May Send Private Messages","Ρόλοι που επιτρέπεται να αποστέλλουν ιδιωτικά μηνύματα"}.
@@ -414,13 +443,16 @@
{"Set message of the day on all hosts and send to online users","Ορίστε μήνυμα ημέρας και άμεση αποστολή στους συνδεδεμένους χρήστες σε όλους τους κεντρικούς υπολογιστές"}.
{"Shared Roster Groups","Κοινές Ομάδες Καταλόγων Επαφών"}.
{"Show Integral Table","Δείτε Ολοκληρωτικό Πίνακα"}.
{"Show Occupants Join/Leave","Εμφάνιση ενοίκων Join/Leave"}.
{"Show Ordinary Table","Δείτε Κοινό Πίνακα"}.
{"Shut Down Service","Τερματισμός Υπηρεσίας"}.
{"SOCKS5 Bytestreams","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","Καθορίστε το μοντέλο πρόσβασης"}.
{"Specify the event message type","Καθορίστε τον τύπο μηνύματος συμβάντος"}.
{"Specify the publisher model","Καθορίστε το μοντέλο εκδότη"}.
{"Stanza id is not valid","Το Stanza id δεν είναι έγκυρο"}.
{"Stanza ID","Ταυτότητα Δωματίου"}.
{"Statically specify a replyto of the node owner(s)","Προσδιορίστε (στατικά) το Απάντηση Προς του ιδιοκτήτη-ων του κόμβου"}.
{"Stopped Nodes","Σταματημένοι Κόμβοι"}.
@@ -457,7 +489,10 @@
{"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","Ο κατάλογος όλων των online χρηστών"}.
{"The list of all users","Ο κατάλογος όλων των χρηστών"}.
{"The list of JIDs that may associate leaf nodes with a collection","Λίστα των JIDs που μπορούν να σχετίζουν leaf κόμβους με μια Συλλογή"}.
{"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","Ο κόμβος είναι κόμβος Συλλογής"}.
@@ -476,6 +511,7 @@
{"The query is only allowed from local users","Το ερώτημα επιτρέπεται μόνο από τοπικούς χρήστες"}.
{"The query must not contain <item/> elements","Το ερώτημα δεν πρέπει να περιέχει στοιχείο <item/>"}.
{"The room subject can be modified by participants","Το θέμα μπορεί να τροποποιηθεί από τους συμμετέχοντες"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","Οι πληροφορίες σημασιολογικού τύπου των δεδομένων στον κόμβο, που συνήθως καθορίζονται από το χώρο ονομάτων του ωφέλιμου φορτίου (εάν υπάρχει)"}.
{"The sender of the last received message","Ο αποστολέας του τελευταίου εισερχομένου μηνύματος"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Η stanza ΠΡΕΠΕΙ να περιέχει μόνο ένα στοιχείο <active />, ένα στοιχείο <default /> ή ένα στοιχείο <list />"}.
{"The subscription identifier associated with the subscription request","Το αναγνωριστικό συνδρομής συσχετίστηκε με το αίτημα συνδρομής"}.
@@ -505,6 +541,7 @@
{"Too many unacked stanzas","Πάρα πολλές μη αναγνωρισμένες stanzas"}.
{"Too many users in this conference","Πάρα πολλοί χρήστες σε αυτή τη διάσκεψη"}.
{"Traffic rate limit is exceeded","Υπέρφορτωση"}.
{"~ts's MAM Archive","Αρχείο MAM του ~ts"}.
{"~ts's Offline Messages Queue","~ts's Χωρίς Σύνδεση Μηνύματα"}.
{"Tuesday","Τρίτη"}.
{"Unable to generate a CAPTCHA","Αδύνατη η δημιουργία CAPTCHA"}.
@@ -512,17 +549,23 @@
{"Unauthorized","Χωρίς Εξουσιοδότηση"}.
{"Unexpected action","Απροσδόκητη ενέργεια"}.
{"Unexpected error condition: ~p","Απροσδόκητες συνθήκες σφάλματος: ~p"}.
{"Uninstall","Απεγκατάσταση"}.
{"Unregister an XMPP account","Καταργήση λογαριασμού XMPP"}.
{"Unregister","Καταργήση εγγραφής"}.
{"Unsupported <index/> element","Μη υποστηριζόμενο στοιχείο <index />"}.
{"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","Προδιαγραφές ενημέρωσης"}.
{"Updating the vCard is not supported by the vCard storage backend","Η ενημέρωση της vCard δεν υποστηρίζεται από το backend αποθήκευσης vCard"}.
{"Upgrade","Αναβάθμιση"}.
{"URL for Archived Discussion Logs","URL αρχειοθετημένων καταγραφών συζητήσεων"}.
{"User already exists","Ο χρήστης υπάρχει ήδη"}.
{"User JID","JID Χρήστη"}.
{"User (jid)","Χρήστης (jid)"}.
{"User Management","Διαχείριση χρηστών"}.
{"User not allowed to perform an IQ set on another user's vCard.","Ο χρήστης δεν επιτρέπεται να εκτελέσει ένα σετ IQ στην vCard ενός άλλου χρήστη."}.
{"User removed","Ο Χρήστης αφαιρέθηκε"}.
{"User session not found","Η περίοδος σύνδεσης χρήστη δεν βρέθηκε"}.
{"User session terminated","Η περίοδος σύνδεσης χρήστη τερματίστηκε"}.
@@ -538,12 +581,14 @@
{"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","Eικονικοί κεντρικοί υπολογιστές"}.
{"Visitors are not allowed to change their nicknames in this room","Οι επισκέπτες δεν επιτρέπεται να αλλάξουν τα ψευδώνυμα τους σε αυτή την αίθουσα"}.
{"Visitors are not allowed to send messages to all occupants","Οι επισκέπτες δεν επιτρέπεται να στείλουν μηνύματα σε όλους τους συμμετέχοντες"}.
{"Visitor","Επισκέπτης"}.
{"Voice requests are disabled in this conference","Τα αιτήματα φωνής είναι απενεργοποιημένα, σε αυτό το συνέδριο"}.
{"Voice request","Αίτημα φωνής"}.
{"Web client which allows to join the room anonymously","Web client που επιτρέπει την ανώνυμη είσοδο στην αίθουσα"}.
{"Wednesday","Τετάρτη"}.
{"When a new subscription is processed and whenever a subscriber comes online","Όταν μία νέα συνδρομή βρίσκεται εν επεξεργασία και όποτε ένας συνδρομητής συνδεθεί"}.
{"When a new subscription is processed","Όταν μία νέα συνδρομή βρίσκεται εν επεξεργασία"}.
@@ -556,6 +601,7 @@
{"Whether to allow subscriptions","Εάν επιτρέπονται συνδρομές"}.
{"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","Ποιός μπορεί να συσχετίζει leaf nodes με μία συλλογή"}.
{"Wrong parameters in the web formulary","Εσφαλμένες παράμετροι στην διαμόρφωση τυπικότητας του δυκτίου"}.
{"Wrong xmlns","Εσφαλμένο xmlns"}.
@@ -567,6 +613,7 @@
{"XMPP Show Value of XA (Extended Away)","Δείξε τιμή XMPP Αξία του Λίαν Απομακρυσμένος"}.
{"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","Σας έχει απαγορευθεί η είσοδος σε αυτή την αίθουσα"}.
+23 -2
View File
@@ -15,7 +15,6 @@
{"Access model","Modelo de Acceso"}.
{"Account doesn't exist","La cuenta no existe"}.
{"Action on user","Acción en el usuario"}.
{"Add a hat to a user","Añade un sombrero a un usuario"}.
{"Add User","Añadir usuario"}.
{"Administration of ","Administración de "}.
{"Administration","Administración"}.
@@ -49,6 +48,7 @@
{"API Commands","Comandos API"}.
{"April","Abril"}.
{"Arguments","Argumentos"}.
{"Assign a hat to a user","Asigna un sombrero a un usuario"}.
{"Attribute 'channel' is required for this request","El atributo 'channel' es necesario para esta petición"}.
{"Attribute 'id' is mandatory for MIX messages","El atributo 'id' es necesario para mensajes MIX"}.
{"Attribute 'jid' is not allowed here","El atributo 'jid' no está permitido aqui"}.
@@ -94,7 +94,9 @@
{"Configuration of room ~s","Configuración para la sala ~s"}.
{"Configuration","Configuración"}.
{"Contact Addresses (normally, room owner or owners)","Direcciones de contacto (normalmente la del dueño o dueños de la sala)"}.
{"Contacts","Contactos"}.
{"Country","País"}.
{"Create a Hat","Crear un sombrero"}.
{"Current Discussion Topic","Tema de discusión actual"}.
{"Database failure","Error en la base de datos"}.
{"Database Tables Configuration at ","Configuración de tablas de la base de datos en "}.
@@ -106,6 +108,8 @@
{"Delete User","Borrar usuario"}.
{"Deliver event notifications","Entregar notificaciones de eventos"}.
{"Deliver payloads with event notifications","Enviar contenidos junto con las notificaciones de eventos"}.
{"Destroy a Hat","Destruir un sombrero"}.
{"Disable User","Deshabilitar un usuario"}.
{"Disc only copy","Copia en disco solamente"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","No le digas tu contraseña a nadie, ni siquiera a los administradores del servidor XMPP."}.
{"Dump Backup to Text File at ","Exporta copia de seguridad a fichero de texto en "}.
@@ -157,12 +161,19 @@
{"Full List of Room Admins","Lista completa de administradores de la sala"}.
{"Full List of Room Owners","Lista completa de dueños de la sala"}.
{"Full Name","Nombre completo"}.
{"Get List of Active Users","Ver lista de usuarios activos"}.
{"Get List of Disabled Users","Ver lista de usuarios deshabilitados"}.
{"Get List of Idle Users","Ver lista de usuarios inactivos"}.
{"Get List of Online Users","Ver lista de usuarios conectados"}.
{"Get List of Registered Users","Ver lista de usuarios registrados"}.
{"Get Number of Active Users","Ver número de usuarios activos"}.
{"Get Number of Disabled Users","Ver número de usuarios deshabilitados"}.
{"Get Number of Idle Users","Ver número de usuarios inactivos"}.
{"Get Number of Online Users","Ver número de usuarios conectados"}.
{"Get Number of Registered Users","Ver número de usuarios registrados"}.
{"Get Pending","Obtener pendientes"}.
{"Get User Last Login Time","Ver fecha de la última conexión de usuario"}.
{"Get User Roster","Ver lista de contactos del usuario"}.
{"Get User Statistics","Ver estadísticas de usuario"}.
{"Given Name","Nombre de pila"}.
{"Grant voice to this person?","¿Conceder voz a esta persona?"}.
@@ -171,7 +182,9 @@
{"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 computed from the list of hats available in a room","Hash computado a partir de la lista de sombreros disponibles en una sala"}.
{"Hash of the vCard-temp avatar of this room","Hash del avatar vCard-temp de esta sala"}.
{"Hat hue","Tono de color del sombrero"}.
{"Hat title","Título del sombrero"}.
{"Hat URI","Dirección del sombrero"}.
{"Hats limit exceeded","Se ha excedido el límite de sombreros"}.
@@ -225,7 +238,7 @@
{"Last year","Último año"}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Los bits menos significativos del hash SHA-256 del texto deberían ser iguales a la etiqueta hexadecimal"}.
{"leaves the room","sale de la sala"}.
{"List of users with hats","Lista de usuarios con sombreros"}.
{"List of Hats","Lista de sombreros"}.
{"List users with hats","Listar usuarios con sombreros"}.
{"Logged Out","Desconectad@"}.
{"Logging","Histórico de mensajes"}.
@@ -319,7 +332,10 @@
{"Notify subscribers when the node configuration changes","Notificar subscriptores cuando cambia la configuración del nodo"}.
{"Notify subscribers when the node is deleted","Notificar subscriptores cuando el nodo se borra"}.
{"November","Noviembre"}.
{"Number of active users","Número de usuarios activos"}.
{"Number of answers required","Número de respuestas necesarias"}.
{"Number of disabled users","Número de usuarios deshabilitados"}.
{"Number of idle users","Número de usuarios inactivos"}.
{"Number of occupants","Número de ocupantes"}.
{"Number of Offline Messages","Número de mensajes diferidos"}.
{"Number of online users","Número de usuarios conectados"}.
@@ -395,6 +411,7 @@
{"Receive notification of new items only","Recibir notificaciones solo de nuevos elementos"}.
{"Receive notification of new nodes only","Recibir notificaciones solo de nuevos nodos"}.
{"Recipient is not in the conference room","El receptor no está en la sala de conferencia"}.
{"Re-Enable User","Rehabilitar usuario"}.
{"Register an XMPP account","Registrar una cuenta XMPP"}.
{"Register","Registrar"}.
{"Remote copy","Copia remota"}.
@@ -489,6 +506,9 @@
{"The JIDs of those to contact with questions","Los JIDs a quienes contactar con preguntas"}.
{"The JIDs of those with an affiliation of owner","Los JIDs de quienes tienen una afiliación de dueños"}.
{"The JIDs of those with an affiliation of publisher","Los JIDs de quienes tienen una afiliación de publicadores"}.
{"The list of all active users","La lista de todos los usuarios activos"}.
{"The list of all disabled users","La lista de todos los usuarios deshabilitados"}.
{"The list of all idle users","La lista de todos los usuarios inactivos"}.
{"The list of all online users","La lista de todos los usuarios conectados"}.
{"The list of all users","La lista de todos los usuarios"}.
{"The list of JIDs that may associate leaf nodes with a collection","La lista de JIDs que pueden asociar nodos hijo con una colección"}.
@@ -510,6 +530,7 @@
{"The presence states for which an entity wants to receive notifications","Los estados de presencia para los cuales una entidad quiere recibir notificaciones"}.
{"The query is only allowed from local users","La solicitud está permitida solo para usuarios locales"}.
{"The query must not contain <item/> elements","La solicitud no debe contener elementos <item/>"}.
{"The role","El rol"}.
{"The room subject can be modified by participants","El asunto de la sala puede ser modificado por los participantes"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","La información semántica de los datos del nodo, normalmente es especificada por el espacio de los nombres de la carga útil (si existe)"}.
{"The sender of the last received message","El emisor del último mensaje recibido"}.
+1 -1
View File
@@ -358,7 +358,7 @@
{"Too many child elements","Túl sok gyermekelem"}.
{"Too many <item/> elements","Túl sok <item/> elem"}.
{"Too many <list/> elements","Túl sok <list/> elem"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Túl sok (~p) sikertelen hitelesítés erről az IP-címről (~ts) A cím ~ts-kor lesz feloldva UTC szerint"}.
{"Too many (~p) failed authentications from this IP address (~s). The address will be unblocked at ~s UTC","Túl sok (~p) sikertelen hitelesítés erről az IP-címről (~s) A cím ~s-kor lesz feloldva UTC szerint"}.
{"Too many receiver fields were specified","Túl sok fogadómező lett meghatározva"}.
{"Too many unacked stanzas","Túl sok nyugtázatlan stanza"}.
{"Too many users in this conference","Túl sok felhasználó ebben a konferenciában"}.
+192 -5
View File
@@ -5,11 +5,14 @@
{" (Add * to the end of field to match substring)"," (Добавьте * в конец поля для поиска подстроки)"}.
{" has set the subject to: "," установил(а) тему: "}.
{"# participants","# участников"}.
{"A description of the node","Описание узла"}.
{"A friendly name for the node","Легко запоминаемое имя для узла"}.
{"A password is required to enter this room","Чтобы войти в эту конференцию, нужен пароль"}.
{"A Web Page","Веб-страница"}.
{"Accept","Принять"}.
{"Access denied by service policy","Доступ запрещён политикой службы"}.
{"Access model","Правила доступа"}.
{"Account doesn't exist","Учётная запись не существует"}.
{"Action on user","Действие над пользователем"}.
{"Add User","Добавить пользователя"}.
@@ -18,6 +21,7 @@
{"Administrator privileges required","Требуются права администратора"}.
{"All activity","Вся статистика"}.
{"All Users","Все пользователи"}.
{"Allow subscription","Разрешить подписку"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Разрешить этому Jabber ID подписаться на данный узел?"}.
{"Allow this person to register with the room?","Разрешить пользователю зарегистрироваться в комнате?"}.
{"Allow users to change the subject","Разрешить пользователям изменять тему"}.
@@ -28,12 +32,27 @@
{"Allow visitors to send private messages to","Разрешить посетителям посылать приватные сообщения"}.
{"Allow visitors to send status text in presence updates","Разрешить посетителям вставлять текcт статуса в сообщения о присутствии"}.
{"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 в соответствии с определением группы, зависящим от реализации или развертывания."}.
{"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 may publish","Любой может публиковать"}.
{"Anyone may subscribe and retrieve items","Любой может подписываться и получать элементы"}.
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Любой с подпиской присутствия (или с) может подписываться и получать элементы"}.
{"Anyone with Voice","Любой с голосовым доступом"}.
{"Anyone","Любой"}.
{"API Commands","Команды API"}.
{"April","апреля"}.
{"Arguments","Аргументы"}.
{"Attribute 'channel' is required for this request","Атрибут 'channel' является обязательным для этого запроса"}.
{"Attribute 'id' is mandatory for MIX messages","Атрибут 'id' является обязательным для MIX сообщений"}.
{"Attribute 'jid' is not allowed here","Атрибут 'jid' здесь недопустим"}.
{"Attribute 'node' is not allowed here","Атрибут 'node' здесь недопустим"}.
{"Attribute 'to' of stanza that triggered challenge","Атрибут «кому» строфы, запросившей проверку"}.
{"August","августа"}.
{"Automatic node creation is not enabled","Автоматическое создание узлов недоступно"}.
{"Backup Management","Управление резервным копированием"}.
@@ -47,12 +66,14 @@
{"Cannot remove active list","Невозможно удалить активный список"}.
{"Cannot remove default list","Невозможно удалить список по умолчанию"}.
{"CAPTCHA web page","Ссылка на капчу"}.
{"Challenge ID","ИД проверки"}.
{"Change Password","Сменить пароль"}.
{"Change User Password","Изменить пароль пользователя"}.
{"Changing password is not allowed","Изменение пароля не разрешено"}.
{"Changing role/affiliation is not allowed","Изменение роли/ранга не разрешено"}.
{"Channel already exists","Канал уже существует"}.
{"Channel does not exist","Канал не существует"}.
{"Channel JID","JID канала"}.
{"Channels","Каналы"}.
{"Characters not allowed:","Недопустимые символы:"}.
{"Chatroom configuration modified","Конфигурация комнаты изменилась"}.
@@ -66,11 +87,14 @@
{"Choose whether to approve this entity's subscription.","Решите: предоставить ли подписку этому объекту."}.
{"City","Город"}.
{"Client acknowledged more stanzas than sent by server","Клиент подтвердил больше сообщений чем было отправлено сервером"}.
{"Clustering","Кластеризация"}.
{"Commands","Команды"}.
{"Conference room does not exist","Конференция не существует"}.
{"Configuration of room ~s","Конфигурация комнаты ~s"}.
{"Configuration","Конфигурация"}.
{"Contact Addresses (normally, room owner or owners)","Контактные адреса (обычно владелец или владельцы комнаты)"}.
{"Country","Страна"}.
{"Current Discussion Topic","Текущая тема обсуждения"}.
{"Database failure","Ошибка базы данных"}.
{"Database Tables Configuration at ","Конфигурация таблиц базы данных на "}.
{"Database","База данных"}.
@@ -82,10 +106,14 @@
{"Deliver event notifications","Доставлять уведомления о событиях"}.
{"Deliver payloads with event notifications","Доставлять вместе с уведомлениями o публикациях сами публикации"}.
{"Disc only copy","только диск"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Не говорите никому свой пароль, даже администраторам сервера."}.
{"Dump Backup to Text File at ","Копирование в текстовый файл на "}.
{"Dump to Text File","Копирование в текстовый файл"}.
{"Duplicated groups are not allowed by RFC6121","Группы с одинаковыми названиями запрещены стандартом RFC6121"}.
{"Dynamically specify a replyto of the item publisher","Динамическое указание кому ответить (replyto) издателя элемента"}.
{"Edit Properties","Изменить параметры"}.
{"Either approve or decline the voice request.","Подтвердите или отклоните право голоса."}.
{"ejabberd HTTP Upload service","Служба закачки файлов по HTTP ejabberd"}.
{"ejabberd MUC module","ejabberd MUC модуль"}.
{"ejabberd Multicast service","ejabberd Multicast сервис"}.
{"ejabberd Publish-Subscribe module","Модуль ejabberd Публикации-Подписки"}.
@@ -93,7 +121,9 @@
{"ejabberd vCard module","ejabberd vCard модуль"}.
{"ejabberd Web Admin","Web-интерфейс администрирования ejabberd"}.
{"ejabberd","ejabberd"}.
{"Email Address","Адрес электронной почты"}.
{"Email","Электронная почта"}.
{"Enable hats","Включить шляпы"}.
{"Enable logging","Включить журналирование"}.
{"Enable message archiving","Включить хранение сообщений"}.
{"Enabling push without 'node' attribute is not supported","Включение push-режима без атрибута 'node' не поддерживается"}.
@@ -104,6 +134,7 @@
{"Enter path to jabberd14 spool file","Введите путь к файлу из спула jabberd14"}.
{"Enter path to text file","Введите путь к текстовому файлу"}.
{"Enter the text you see","Введите увиденный текст"}.
{"Erlang XMPP Server","Сервер XMPP на Erlang"}.
{"Exclude Jabber IDs from CAPTCHA challenge","Исключить показ капчи для списка Jabber ID"}.
{"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):"}.
@@ -116,10 +147,17 @@
{"Failed to parse HTTP response","Ошибка разбора HTTP ответа"}.
{"Failed to process option '~s'","Ошибка обработки опции '~s'"}.
{"Family Name","Фамилия"}.
{"FAQ Entry","Часто задаваемые вопросы"}.
{"February","февраля"}.
{"File larger than ~w bytes","Файл больше ~w байт"}.
{"Fill in the form to search for any matching XMPP User","Заполните форму для поиска совпадающих пользователей XMPP"}.
{"Friday","Пятница"}.
{"From ~ts","От ~ts"}.
{"Full List of Room Admins","Полный список админов комнаты"}.
{"Full List of Room Owners","Полный список владельцев комнаты"}.
{"Full Name","Полное имя"}.
{"Get List of Online Users","Получить список подключённых пользователей"}.
{"Get List of Registered Users","Получить список зарегистрированных пользователей"}.
{"Get Number of Online Users","Получить количество подключённых пользователей"}.
{"Get Number of Registered Users","Получить количество зарегистрированных пользователей"}.
{"Get Pending","Получить отложенные"}.
@@ -132,6 +170,9 @@
{"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 URI","URI шляпы"}.
{"Hats limit exceeded","Превышено ограничение шляп"}.
{"Host unknown","Неизвестное имя сервера"}.
{"HTTP File Upload","Передача файлов по HTTP"}.
{"Idle connection","Неиспользуемое соединение"}.
@@ -152,6 +193,8 @@
{"Incorrect value of 'action' attribute","Некорректное значение атрибута 'action'"}.
{"Incorrect value of 'action' in data form","Некорректное значение 'action' в форме данных"}.
{"Incorrect value of 'path' in data form","Некорректное значение 'path' в форме данных"}.
{"Installed Modules:","Установленные модули:"}.
{"Install","Установить"}.
{"Insufficient privilege","Недостаточно прав"}.
{"Internal server error","Внутренняя ошибка сервера"}.
{"Invalid 'from' attribute in forwarded message","Некорректный атрибут 'from' в пересланном сообщении"}.
@@ -165,14 +208,24 @@
{"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 failed","Ошибка нормализации JID"}.
{"Joined MIX channels of ~ts","Присоединился к MIX каналам ~ts"}.
{"Joined MIX channels:","Присоединился к MIX каналам:"}.
{"joins the room","вошёл(а) в комнату"}.
{"July","июля"}.
{"June","июня"}.
{"Just created","Только что создано"}.
{"Last Activity","Последнее подключение"}.
{"Last login","Время последнего подключения"}.
{"Last message","Последнее сообщение"}.
{"Last month","За последний месяц"}.
{"Last year","За последний год"}.
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Младшие значимые биты хэша SHA-256 текста должны быть равны шестнадцатеричной метке"}.
{"leaves the room","вышел(а) из комнаты"}.
{"List users with hats","Список пользователей с шляпами"}.
{"Logged Out","Вышел"}.
{"Logging","Ведение журнала"}.
{"Make participants list public","Сделать список участников видимым всем"}.
{"Make room CAPTCHA protected","Сделать комнату защищённой капчей"}.
{"Make room members-only","Комната только для зарегистрированных участников"}.
@@ -183,21 +236,34 @@
{"Malformed username","Недопустимое имя пользователя"}.
{"MAM preference modification denied by service policy","Изменение настроек архива сообщений запрещено политикой службы"}.
{"March","марта"}.
{"Max # of items to persist, or `max` for no specific limit other than a server imposed maximum","Максимальное количество сохраняемых элементов или `max` для отсутствия ограничений, кроме установленных сервером ограничений"}.
{"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","Максимальное количество участников"}.
{"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 (значение «No Show»)"}.
{"Message body","Тело сообщения"}.
{"Message not found in forwarded payload","Сообщение не найдено в пересылаемом вложении"}.
{"Messages from strangers are rejected","Сообщения от незнакомцев запрещены"}.
{"Messages of type headline","Сообщения типа «заголовок»"}.
{"Messages of type normal","Сообщения типа «обычный»"}.
{"Middle Name","Отчество"}.
{"Minimum interval between voice requests (in seconds)","Минимальный интервал между запросами на право голоса"}.
{"Moderator privileges required","Требуются права модератора"}.
{"Moderators Only","Только модераторам"}.
{"Moderator","Модератор"}.
{"Module failed to handle the query","Ошибка модуля при обработке запроса"}.
{"Monday","Понедельник"}.
{"Multicast","Мультикаст"}.
{"Multiple <item/> elements are not allowed by RFC6121","Множественные элементы <item/> запрещены стандартом RFC6121"}.
{"Multi-User Chat","Конференция"}.
{"Name","Название"}.
{"Natural Language for Room Discussions","Естественный язык для обсуждений комнаты"}.
{"Natural-Language Room Name","Имя комнаты на естественном языке"}.
{"Neither 'jid' nor 'nick' attribute found","Не найден атрибут 'jid' или 'nick'"}.
{"Neither 'role' nor 'affiliation' attribute found","Не найден атрибут 'role' или 'affiliation'"}.
{"Never","Никогда"}.
@@ -233,6 +299,7 @@
{"No services available","Нет доступных сервисов"}.
{"No statistics found for this item","Не найдено статистики для этого элемента"}.
{"No 'to' attribute found in the invitation","Не найден атрибут 'to' в этом приглашении"}.
{"Nobody","Никто"}.
{"Node already exists","Узел уже существует"}.
{"Node ID","ID узла"}.
{"Node index not found","Индекс узла не найден"}.
@@ -240,6 +307,7 @@
{"Node ~p","Узел ~p"}.
{"Nodeprep has failed","Ошибка применения профиля Nodeprep"}.
{"Nodes","Узлы"}.
{"Node","Узел"}.
{"None","Нет"}.
{"Not allowed","Недопустимо"}.
{"Not Found","Не Найдено"}.
@@ -248,42 +316,59 @@
{"Notify subscribers when the node configuration changes","Уведомлять подписчиков об изменении конфигурации сборника"}.
{"Notify subscribers when the node is deleted","Уведомлять подписчиков об удалении сборника"}.
{"November","ноября"}.
{"Number of answers required","Количество требуемых ответов"}.
{"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","Разрешить участникам изменять тему"}.
{"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 <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 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","Только участники из белого списка могут подписываться и получать элементы"}.
{"Organization Name","Название организации"}.
{"Organization Unit","Отдел организации"}.
{"Other Modules Available:","Другие доступные модули:"}.
{"Outgoing s2s Connections","Исходящие s2s-соединения"}.
{"Owner privileges required","Требуются права владельца"}.
{"Packet relay is denied by service policy","Пересылка пакетов запрещена политикой службы"}.
{"Participant ID","ИД участника"}.
{"Participant","Участник"}.
{"Password Verification","Проверка пароля"}.
{"Password Verification:","Проверка пароля:"}.
{"Password","Пароль"}.
{"Password:","Пароль:"}.
{"Path to Dir","Путь к директории"}.
{"Path to File","Путь к файлу"}.
{"Period: ","Период"}.
{"Payload semantic type information","Информация о семантическом типе полезной нагрузки"}.
{"Period: ","Период: "}.
{"Persist items to storage","Сохранять публикации в хранилище"}.
{"Persistent","Постоянно"}.
{"Ping query is incorrect","Некорректный пинг-запрос"}.
{"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, wait for a while before sending new voice request","Пожалуйста, подождите перед тем как подать новый запрос на право голоса"}.
{"Pong","Понг"}.
{"Possessing 'ask' attribute is not allowed by RFC6121","Наявность атрибута 'ask' запрещено стандартом RFC6121"}.
{"Present real Jabber IDs to","Сделать реальные Jabber ID участников видимыми"}.
{"Previous session not found","Предыдущая сессия не найдена"}.
{"Previous session PID has been killed","Предыдущая сессия была убита"}.
@@ -291,6 +376,8 @@
{"Previous session PID is dead","Предыдущая сессия мертва"}.
{"Previous session timed out","Предыдущая сессия не отвечает"}.
{"private, ","приватная, "}.
{"Public","Публичная"}.
{"Publish model","Модель публикации"}.
{"Publish-Subscribe","Публикация-Подписка"}.
{"PubSub subscriber request","Запрос подписчика PubSub"}.
{"Purge all items when the relevant publisher goes offline","Очищать все записи автора публикации когда он отключается"}.
@@ -300,12 +387,20 @@
{"RAM and disc copy","ОЗУ и диск"}.
{"RAM copy","ОЗУ"}.
{"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 of new nodes only","Получать уведомления только о новых узлах"}.
{"Recipient is not in the conference room","Адресата нет в конференции"}.
{"Register an XMPP account","Зарегистрировать учётную запись XMPP"}.
{"Register","Зарегистрировать"}.
{"Remote copy","не хранится локально"}.
{"Remove a hat from a user","Снять шляпу с Пользователь"}.
{"Remove User","Удалить пользователя"}.
{"Replaced by new connection","Заменено новым соединением"}.
{"Request has timed out","Истекло время ожидания запроса"}.
{"Request is ignored","Запрос игнорируется"}.
{"Requested role","Запрошенная роль"}.
{"Resources","Ресурсы"}.
{"Restart Service","Перезапустить службу"}.
{"Restore Backup from File at ","Восстановление из резервной копии на "}.
@@ -313,6 +408,10 @@
{"Restore binary backup immediately:","Восстановить из бинарной резервной копии немедленно:"}.
{"Restore plain text backup immediately:","Восстановить из текстовой резервной копии немедленно:"}.
{"Restore","Восстановление из резервной копии"}.
{"Result","Результат"}.
{"Roles and Affiliations that May Retrieve Member List","Роли которые могут получать список участников"}.
{"Roles for which Presence is Broadcasted","Роли которые видят присутствие"}.
{"Roles that May Send Private Messages","Роли которым можно посылать личные сообщения"}.
{"Room Configuration","Конфигурация комнаты"}.
{"Room creation is denied by service policy","Cоздавать конференцию запрещено политикой службы"}.
{"Room description","Описание комнаты"}.
@@ -322,8 +421,12 @@
{"Roster groups allowed to subscribe","Группы списка контактов, которым разрешена подписка"}.
{"Roster size","Размер списка контактов"}.
{"Running Nodes","Работающие узлы"}.
{"~s invites you to the room ~s","~s приглашает вас в комнату ~s"}.
{"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","Разослать объявление всем подключённым пользователям"}.
@@ -331,17 +434,24 @@
{"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 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","Укажите механизм управления доступом"}.
{"Specify the event message type","Укажите тип сообщения о событии"}.
{"Specify the publisher model","Условия публикации"}.
{"Stanza id is not valid","Строфа неправильная"}.
{"Stanza ID","ИД строфы"}.
{"Statically specify a replyto of the node owner(s)","Статически указать кому отчечать (replyto) владельца(ев) узла"}.
{"Stopped Nodes","Остановленные узлы"}.
{"Store binary backup:","Сохранить бинарную резервную копию:"}.
{"Store plain text backup:","Сохранить текстовую резервную копию:"}.
@@ -350,32 +460,73 @@
{"Subject","Тема"}.
{"Submitted","Отправлено"}.
{"Subscriber Address","Адрес подписчика"}.
{"Subscribers may publish","Подписчики могут публиковать"}.
{"Subscription requests must be approved and only subscribers may retrieve items","Запросы на подписку должны быть одобрены, и только подписчики могут получать элементы"}.
{"Subscriptions are not allowed","Подписки недопустимы"}.
{"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 registered by another person","Этот псевдоним зарегистрирован кем-то другим"}.
{"The account already exists","Учётная запись уже существует"}.
{"The account was not unregistered","Учётная запись не была удалёна"}.
{"The body text of the last received message","Текст последнего полученного сообщения"}.
{"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 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 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 with an affiliation of publisher","JID тех, кто связан с издателем"}.
{"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 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 password contains unacceptable characters","Пароль содержит недопустимые символы"}.
{"The password is too weak","Слишком слабый пароль"}.
{"the password is","пароль:"}.
{"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 query is only allowed from local users","Запрос доступен только для локальных пользователей"}.
{"The query must not contain <item/> elements","Запрос не должен содержать элементов <item/>"}.
{"The room subject can be modified by participants","Тема комнаты может быть изменена участниками"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","Семантическая информация о типе данных в узле, обычно определяемая пространством имён нагрузки (если таковое имеется)"}.
{"The sender of the last received message","Отправитель последнего полученного сообщения"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","Строфа может содержать только один элемент <active/>, один элемент <default/> или один элемент <list/>"}.
{"There was an error creating the account: ","Ошибка при создании аккаунта:"}.
{"There was an error deleting the account: ","Ошибка при удалении аккаунта:"}.
{"The subscription identifier associated with the subscription request","Идентификатор подписки, связанный с запросом на подписку"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","URL XSL-преобразования, которое можно применить к полезным данным для создания соответствующего элемента тела сообщения."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","URL-адрес XSL-преобразования, которое можно применить к формату текста для генерации правильные формы данных, которые клиент может отобразить с помощью универсального механизма отображения Форм данных"}.
{"There was an error changing the password: ","Ошибка при изменении пароля: "}.
{"There was an error creating the account: ","Ошибка при создании учётной записи: "}.
{"There was an error deleting the account: ","Ошибка при удалении учётной записи: "}.
{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Регистр не имеет значения: \"маша\" и \"МАША\" будет считаться одним и тем же именем."}.
{"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-идентификатор) будет в виде: \"пользователь@сервер\". Пожалуйста, внимательно читайте инструкции для правильного заполнения полей."}.
{"This page allows to unregister an XMPP account in this XMPP server.","Здесь вы можете удалить учётную запись XMPP с этого сервера."}.
{"This room is not anonymous","Эта комната не анонимная"}.
{"This service can not process the address: ~s","Сервер не может обработать адрес: ~s"}.
{"Thursday","Четверг"}.
{"Time delay","По истечение"}.
{"Timed out waiting for stream resumption","Истекло время ожидания возобновления потока"}.
{"To register, visit ~s","Для регистрации посетите ~s"}.
{"To ~ts","Ко ~s"}.
{"Token TTL","Токен TTL"}.
{"Too many active bytestreams","Слишком много активных потоков данных"}.
{"Too many CAPTCHA requests","Слишком много запросов капчи"}.
@@ -387,24 +538,35 @@
{"Too many unacked stanzas","Слишком много неподтверждённых пакетов"}.
{"Too many users in this conference","Слишком много пользователей в этой конференции"}.
{"Traffic rate limit is exceeded","Превышен лимит скорости посылки информации"}.
{"~ts's MAM Archive","История сообщений ~ts"}.
{"~ts's Offline Messages Queue","Oчередь офлайновых сообщений ~ts"}.
{"Tuesday","Вторник"}.
{"Unable to generate a CAPTCHA","Не получилось создать капчу"}.
{"Unable to register route on existing local domain","Нельзя регистрировать маршруты на существующие локальные домены"}.
{"Unauthorized","Не авторизован"}.
{"Unexpected action","Неожиданное действие"}.
{"Unexpected error condition: ~p","Неожиданная ошибка: ~p"}.
{"Uninstall","Удалить"}.
{"Unregister an XMPP account","Удалить учётную запись XMPP"}.
{"Unregister","Удалить"}.
{"Unsupported <index/> element","Элемент <index/> не поддерживается"}.
{"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","Обновить характеристики"}.
{"Updating the vCard is not supported by the vCard storage backend","Обновление vCard не поддерживается сервером"}.
{"Upgrade","Обновить"}.
{"URL for Archived Discussion Logs","URL для архивных журналов обсуждений"}.
{"User already exists","Пользователь уже существует"}.
{"User JID","JID пользователя"}.
{"User (jid)","Пользователь (XMPP адрес)"}.
{"User Management","Управление пользователями"}.
{"User not allowed to perform an IQ set on another user's vCard.","Пользователю запрещено изменять vCard другого пользователя."}.
{"User removed","Пользователь удалён"}.
{"User session not found","Сессия пользователя не найдена"}.
{"User session terminated","Сессия пользователя завершена"}.
{"User ~ts","Пользователь ~ts"}.
{"Username:","Имя пользователя:"}.
{"Users are not allowed to register accounts so quickly","Пользователи не могут регистрировать учётные записи так быстро"}.
{"Users Last Activity","Статистика последнего подключения пользователей"}.
@@ -416,18 +578,41 @@
{"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","Посетителям запрещено изменять свои псевдонимы в этой комнате"}.
{"Visitors are not allowed to send messages to all occupants","Посетителям не разрешается посылать сообщения всем присутствующим"}.
{"Visitor","Посетитель"}.
{"Voice requests are disabled in this conference","Запросы на право голоса отключены в этой конференции"}.
{"Voice request","Запрос на право голоса"}.
{"Web client which allows to join the room anonymously","Веб-клиент, позволяющий анонимно присоединиться к комнате"}.
{"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 to allow subscriptions","Разрешить подписку"}.
{"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 to allow subscriptions","Разрешить ли подписку"}.
{"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","Недопустимые параметры веб-формы"}.
{"Wrong xmlns","Неправильный xmlns"}.
{"XMPP Account Registration","Регистрация учётной записи XMPP"}.
{"XMPP Domains","Домены XMPP"}.
{"XMPP Show Value of Away","XMPP: Отображение значения «Отошёл»"}.
{"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-адрес ассоциированного узла публикаций-подписок"}.
{"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 joined too many conferences","Вы присоединены к слишком большому количеству конференций"}.
{"You must fill in field \"Nickname\" in the form","Вы должны заполнить поле \"Псевдоним\" в форме"}.
@@ -437,4 +622,6 @@
{"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","Вам не разрешается создавать узлы"}.
+14 -9
View File
@@ -46,7 +46,9 @@
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","அல்லது இரண்டின் இருப்பு சந்தா உள்ள எவரும் உருப்படிகளை குழுசேர் மற்றும் மீட்டெடுக்கலாம்"}.
{"Anyone with Voice","குரல் உள்ள எவரும்"}.
{"Anyone","யாரும்"}.
{"API Commands","பநிஇ கட்டளைகள்"}.
{"April","ப-சித்திரை"}.
{"Arguments","வாதங்கள்"}.
{"Attribute 'channel' is required for this request","இந்த கோரிக்கைக்கு 'சேனல்' என்ற பண்புக்கூறு தேவை"}.
{"Attribute 'id' is mandatory for MIX messages","கலவை செய்திகளுக்கு 'ஐடி' கட்டாயமாகும்"}.
{"Attribute 'jid' is not allowed here","'சிட்' என்ற பண்புக்கூறு இங்கே அனுமதிக்கப்படவில்லை"}.
@@ -86,6 +88,7 @@
{"Choose whether to approve this entity's subscription.","இந்த நிறுவனத்தின் சந்தாவை அங்கீகரிக்க வேண்டுமா என்பதைத் தேர்வுசெய்க."}.
{"City","நகரம்"}.
{"Client acknowledged more stanzas than sent by server","சேவையகத்தால் அனுப்பப்பட்டதை விட கிளையன்ட் அதிக சரணத்தை ஒப்புக் கொண்டார்"}.
{"Clustering","கிளச்டரிங்"}.
{"Commands","கட்டளைகள்"}.
{"Conference room does not exist","மாநாட்டு அறை இல்லை"}.
{"Configuration of room ~s","அறையின் உள்ளமைவு ~s"}.
@@ -259,7 +262,7 @@
{"Module failed to handle the query","தொகுதி வினவலைக் கையாளத் தவறிவிட்டது"}.
{"Monday","திங்கள்"}.
{"Multicast","மல்டிகாச்ட்"}.
{"Multiple <item/> elements are not allowed by RFC6121","பல <உருப்படி/> கூறுகள் RFC6121 ஆல் அனுமதிக்கப்படாது"}.
{"Multiple <item/> elements are not allowed by RFC6121","பல <item/> கூறுகள் RFC6121 ஆல் அனுமதிக்கப்படாது"}.
{"Multi-User Chat","பல பயனர் அரட்டை"}.
{"Name","பெயர்"}.
{"Natural Language for Room Discussions","அறை விவாதங்களுக்கு இயற்கை மொழி"}.
@@ -281,7 +284,7 @@
{"No data form found","தரவு படிவம் எதுவும் கிடைக்கவில்லை"}.
{"No Data","தரவு இல்லை"}.
{"No features available","நற்பொருத்தங்கள் எதுவும் கிடைக்கவில்லை"}.
{"No <forwarded/> element found","இல்லை <முன்னோக்கி/> உறுப்பு காணப்பட்டது"}.
{"No <forwarded/> element found","இல்லை <forwarded/> உறுப்பு காணப்பட்டது"}.
{"No hook has processed this command","இந்த கட்டளையை எந்த ஊக்கும் செயலாக்கவில்லை"}.
{"No info about last activity found","கடைசி செயல்பாட்டைப் பற்றிய எந்த தகவலும் கிடைக்கவில்லை"}.
{"No 'item' element found","'உருப்படி' உறுப்பு இல்லை"}.
@@ -332,8 +335,8 @@
{"Online","ஆன்லைனில்"}.
{"Only collection node owners may associate leaf nodes with the collection","சேகரிப்பு முனை உரிமையாளர்கள் மட்டுமே இலை முனைகளை சேகரிப்புடன் தொடர்புபடுத்தலாம்"}.
{"Only deliver notifications to available users","கிடைக்கக்கூடிய பயனர்களுக்கு மட்டுமே அறிவிப்புகளை வழங்கவும்"}.
{"Only <enable/> or <disable/> tags are allowed","<anafice/> அல்லது <முடக்கு/> குறிச்சொற்கள் மட்டுமே அனுமதிக்கப்படுகின்றன"}.
{"Only <list/> element is allowed in this query","இந்த வினவலில் <பட்டியல்/> உறுப்பு மட்டுமே அனுமதிக்கப்படுகிறது"}.
{"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","இந்த அறையில் உள்ள விசயத்தை மாற்ற மதிப்பீட்டாளர்கள் மட்டுமே அனுமதிக்கப்படுகிறார்கள்"}.
@@ -408,6 +411,7 @@
{"Restore binary backup immediately:","பைனரி காப்புப்பிரதியை உடனடியாக மீட்டமைக்கவும்:"}.
{"Restore plain text backup immediately:","எளிய உரை காப்புப்பிரதியை உடனடியாக மீட்டெடுக்கவும்:"}.
{"Restore","மீட்டமை"}.
{"Result","விளைவு"}.
{"Roles and Affiliations that May Retrieve Member List","உறுப்பினர் பட்டியலை மீட்டெடுக்கக்கூடிய பாத்திரங்கள் மற்றும் இணைப்புகள்"}.
{"Roles for which Presence is Broadcasted","இருப்பு ஒளிபரப்பப்படும் பாத்திரங்கள்"}.
{"Roles that May Send Private Messages","தனிப்பட்ட செய்திகளை அனுப்பக்கூடிய பாத்திரங்கள்"}.
@@ -505,11 +509,11 @@
{"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","வினவலில் <பொருள்/> கூறுகள் இருக்கக்கூடாது"}.
{"The query must not contain <item/> elements","வினவலில் <item/>கூறுகள் இருக்கக் கூடாது"}.
{"The room subject can be modified by participants","அறை பொருள் பங்கேற்பாளர்களால் மாற்றப்படலாம்"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","முனையில் உள்ள தரவின் சொற்பொருள் வகை செய்தி, பொதுவாக பேலோடின் பெயர்வெளியால் குறிப்பிடப்படுகிறது (ஏதேனும் இருந்தால்)"}.
{"The sender of the last received message","கடைசியாக பெறப்பட்ட செய்தியை அனுப்பு"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","ச்டான்சாவில் ஒரே <செயலில்/> உறுப்பு, ஒரு <இயல்புநிலை/> உறுப்பு அல்லது ஒரு <பட்டியல்/> உறுப்பு மட்டுமே இருக்க வேண்டும்"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","ச்டான்சாவில் ஒரே <active/> உறுப்பு, ஒரு <default/>உறுப்பு அல்லது ஒரு <list/>உறுப்பு மட்டுமே இருக்க வேண்டும்"}.
{"The subscription identifier associated with the subscription request","சந்தா கோரிக்கையுடன் தொடர்புடைய சந்தா அடையாளங்காட்டி"}.
{"The URL of an XSL transformation which can be applied to payloads in order to generate an appropriate message body element.","பொருத்தமான செய்தி உடல் உறுப்பை உருவாக்குவதற்காக பேலோடுகளுக்கு பயன்படுத்தக்கூடிய எக்ச்எச்எல் மாற்றத்தின் முகவரி."}.
{"The URL of an XSL transformation which can be applied to the payload format in order to generate a valid Data Forms result that the client could display using a generic Data Forms rendering engine","செல்லுபடியாகும் தரவு படிவங்களை உருவாக்குவதற்காக பேலோட் வடிவத்தில் பயன்படுத்தக்கூடிய ஒரு எக்ச்எச்எல் உருமாற்றத்தின் முகவரி, கிளையன்ட் பொதுவான தரவு படிவங்கள் வழங்குதல் எஞ்சின் பயன்படுத்தி காண்பிக்க முடியும்"}.
@@ -530,8 +534,8 @@
{"Too many active bytestreams","பல செயலில் உள்ள பைட்டிரீம்கள்"}.
{"Too many CAPTCHA requests","பல கேப்ட்சா கோரிக்கைகள்"}.
{"Too many child elements","பல குழந்தை கூறுகள்"}.
{"Too many <item/> elements","பல <உருப்படி/> கூறுகள்"}.
{"Too many <list/> 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","இந்த ஐபி முகவரியிலிருந்து (~p) பல (~s) தோல்வியுற்ற அங்கீகாரங்கள் ~s. முகவரி UTC இல் தடைசெய்யப்படும்"}.
{"Too many receiver fields were specified","அதிகமான ரிசீவர் புலங்கள் குறிப்பிடப்பட்டன"}.
{"Too many unacked stanzas","பல அறியப்படாத சரணங்கள்"}.
@@ -548,7 +552,7 @@
{"Uninstall","நிறுவல் நீக்க"}.
{"Unregister an XMPP account","ஒரு எக்ச்எம்பிபி கணக்கை பதிவு செய்யவும்"}.
{"Unregister","பதிவு செய்யப்படாதது"}.
{"Unsupported <index/> element","ஆதரிக்கப்படாத <குறியீட்டு/> உறுப்பு"}.
{"Unsupported <index/> element","ஆதரிக்கப்படாத <index/> உறுப்பு"}.
{"Unsupported version","ஆதரிக்கப்படாத பதிப்பு"}.
{"Update message of the day (don't send)","அன்றைய செய்தியைப் புதுப்பிக்கவும் (அனுப்ப வேண்டாம்)"}.
{"Update message of the day on all hosts (don't send)","எல்லா ஓச்ட்களிலும் அன்றைய செய்தியைப் புதுப்பிக்கவும் (அனுப்ப வேண்டாம்)"}.
@@ -584,6 +588,7 @@
{"Visitor","பார்வையாளர்"}.
{"Voice requests are disabled in this conference","இந்த மாநாட்டில் குரல் கோரிக்கைகள் முடக்கப்பட்டுள்ளன"}.
{"Voice request","குரல் கோரிக்கை"}.
{"Web client which allows to join the room anonymously","அநாமதேயமாக அறையில் சேர அனுமதிக்கும் வலை கிளையண்ட்"}.
{"Wednesday","புதன்கிழமை"}.
{"When a new subscription is processed and whenever a subscriber comes online","புதிய சந்தா செயலாக்கப்படும் போது, சந்தாதாரர் ஆன்லைனில் வரும்போதெல்லாம்"}.
{"When a new subscription is processed","புதிய சந்தா செயலாக்கப்படும் போது"}.
+10 -10
View File
@@ -149,7 +149,7 @@
{"Failed to process option '~s'","Не вдалося обробити параметр \"~s\""}.
{"Family Name","Прізвище"}.
{"FAQ Entry","Запис в ЧаПи"}.
{"February","лютого"}.
{"February","Лютого"}.
{"File larger than ~w bytes","Файл більший, ніж ~w байт"}.
{"Fill in the form to search for any matching XMPP User","Заповніть форму для пошуку будь-якого відповідного користувача XMPP"}.
{"Friday","П'ятниця"}.
@@ -209,14 +209,14 @@
{"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","січня"}.
{"January","Січня"}.
{"JID normalization denied by service policy","Створювати конференцію заборонено політикою служби"}.
{"JID normalization failed","Помилка нормалізації JID"}.
{"Joined MIX channels of ~ts","Приєднався до каналів MIX ~ts"}.
{"Joined MIX channels:","Приєднався до каналів MIX:"}.
{"joins the room","увійшов(ла) в кімнату"}.
{"July","липня"}.
{"June","червня"}.
{"July","Липня"}.
{"June","Червня"}.
{"Just created","Щойно створено"}.
{"Last Activity","Останнє підключення"}.
{"Last login","Останнє підключення"}.
@@ -313,12 +313,12 @@
{"Node","Вузол"}.
{"None","Немає"}.
{"Not allowed","Не дозволяється"}.
{"Not Found","не знайдено"}.
{"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","Повідомляти абонентів про видалення збірника"}.
{"November","листопада"}.
{"November","Листопада"}.
{"Number of answers required","Кількість необхідних відповідей"}.
{"Number of occupants","Кількість присутніх"}.
{"Number of Offline Messages","Кількість автономних повідомлень"}.
@@ -397,7 +397,7 @@
{"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","Замінено новим з'єднанням"}.
@@ -435,7 +435,7 @@
{"Send announcement to all online users","Надіслати сповіщення всім підключеним користувачам"}.
{"Send announcement to all users on all hosts","Надіслати сповіщення до усіх користувачів на усіх хостах"}.
{"Send announcement to all users","Надіслати сповіщення всім користувачам"}.
{"September","вересня"}.
{"September","Вересня"}.
{"Server:","Сервер:"}.
{"Service list retrieval timed out","Час очікування отримання списку послуг минув"}.
{"Session state copying timed out","Час очікування копіювання стану сеансу минув"}.
@@ -551,7 +551,7 @@
{"Unexpected error condition: ~p","Умова несподіваної помилки: ~p"}.
{"Uninstall","Видалити"}.
{"Unregister an XMPP account","Видалити обліковий запис XMPP"}.
{"Unregister","Видалити"}.
{"Unregister","Скасувати реєстрацію"}.
{"Unsupported <index/> element","Непідтримуваний елемент <index/>"}.
{"Unsupported version","Непідтримувана версія"}.
{"Update message of the day (don't send)","Оновити повідомлення дня (не надсилати)"}.
@@ -582,7 +582,7 @@
{"Value 'set' of 'type' attribute is not allowed","Значення 'set' атрибута 'type' не допускається"}.
{"vCard User Search","Пошук користувачів по vCard"}.
{"View joined MIX channels","Перегляд приєднаних каналів MIX"}.
{"Virtual Hosts","віртуальні хости"}.
{"Virtual Hosts","Віртуальні хости"}.
{"Visitors are not allowed to change their nicknames in this room","Відвідувачам не дозволяється змінювати псевдонім в цій кімнаті"}.
{"Visitors are not allowed to send messages to all occupants","Відвідувачам не дозволяється надсилати повідомлення всім присутнім"}.
{"Visitor","Відвідувач"}.
Vendored
BIN
View File
Binary file not shown.
+20 -18
View File
@@ -26,8 +26,8 @@
{if_version_below, "24",
{base64url, "~> 1.0", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}}
}},
{cache_tab, "~> 1.0.31", {git, "https://github.com/processone/cache_tab", {tag, "1.0.31"}}},
{eimp, "~> 1.0.24", {git, "https://github.com/processone/eimp", {tag, "1.0.24"}}},
{cache_tab, "~> 1.0.33", {git, "https://github.com/processone/cache_tab", {tag, "1.0.33"}}},
{eimp, "~> 1.0.26", {git, "https://github.com/processone/eimp", {tag, "1.0.26"}}},
{if_var_true, pam,
{epam, "~> 1.0.14", {git, "https://github.com/processone/epam", {tag, "1.0.14"}}}},
{if_var_true, redis,
@@ -41,12 +41,12 @@
{eredis, "~> 1.7.1", {git, "https://github.com/Nordix/eredis/", {tag, "v1.7.1"}}}
}}},
{if_var_true, sip,
{esip, "~> 1.0.57", {git, "https://github.com/processone/esip", {tag, "1.0.57"}}}},
{esip, "~> 1.0.59", {git, "https://github.com/processone/esip", {tag, "1.0.59"}}}},
{if_var_true, zlib,
{ezlib, "~> 1.0.13", {git, "https://github.com/processone/ezlib", {tag, "1.0.13"}}}},
{fast_tls, "~> 1.1.22", {git, "https://github.com/processone/fast_tls", {tag, "1.1.22"}}},
{fast_xml, "~> 1.1.55", {git, "https://github.com/processone/fast_xml", {tag, "1.1.55"}}},
{fast_yaml, "~> 1.0.37", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.37"}}},
{ezlib, "~> 1.0.15", {git, "https://github.com/processone/ezlib", {tag, "1.0.15"}}}},
{fast_tls, "~> 1.1.25", {git, "https://github.com/processone/fast_tls", {tag, "1.1.25"}}},
{fast_xml, "~> 1.1.57", {git, "https://github.com/processone/fast_xml", {tag, "1.1.57"}}},
{fast_yaml, "~> 1.0.39", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.39"}}},
{idna, "~> 6.0", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}},
{if_version_below, "27",
{jiffy, "~> 1.1.1", {git, "https://github.com/davisp/jiffy", {tag, "1.1.1"}}}
@@ -63,22 +63,22 @@
{luerl, "1.0.0", {git, "https://github.com/rvirding/luerl", {tag, "1.0"}}},
{luerl, "~> 1.2.0", {git, "https://github.com/rvirding/luerl", {tag, "1.2"}}}
}},
{mqtree, "~> 1.0.17", {git, "https://github.com/processone/mqtree", {tag, "1.0.17"}}},
{p1_acme, "~> 1.0.25", {git, "https://github.com/processone/p1_acme", {tag, "1.0.25"}}},
{mqtree, "~> 1.0.19", {git, "https://github.com/processone/mqtree", {tag, "1.0.19"}}},
{p1_acme, "~> 1.0.29", {git, "https://github.com/processone/p1_acme", {tag, "1.0.29"}}},
{if_var_true, mysql,
{p1_mysql, "~> 1.0.26", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.26"}}}},
{p1_oauth2, "~> 0.6.14", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.14"}}},
{if_var_true, pgsql,
{p1_pgsql, "~> 1.1.32", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.32"}}}},
{p1_utils, "~> 1.0.27", {git, "https://github.com/processone/p1_utils", {tag, "1.0.27"}}},
{p1_pgsql, "~> 1.1.36", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.36"}}}},
{p1_utils, "~> 1.0.28", {git, "https://github.com/processone/p1_utils", {tag, "1.0.28"}}},
{pkix, "~> 1.0.10", {git, "https://github.com/processone/pkix", {tag, "1.0.10"}}},
{if_var_true, sqlite,
{sqlite3, "~> 1.1.15", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.15"}}}},
{stringprep, "~> 1.0.31", {git, "https://github.com/processone/stringprep", {tag, "1.0.31"}}},
{stringprep, "~> 1.0.33", {git, "https://github.com/processone/stringprep", {tag, "1.0.33"}}},
{if_var_true, stun,
{stun, "~> 1.2.17", {git, "https://github.com/processone/stun", {tag, "1.2.17"}}}},
{xmpp, "~> 1.10.0", {git, "https://github.com/processone/xmpp", {tag, "1.10.0"}}},
{yconf, "~> 1.0.18", {git, "https://github.com/processone/yconf", {tag, "1.0.18"}}}
{stun, "~> 1.2.21", {git, "https://github.com/processone/stun", {tag, "1.2.21"}}}},
{xmpp, "~> 1.11.2", {git, "https://github.com/processone/xmpp", {tag, "1.11.2"}}},
{yconf, "~> 1.0.22", {git, "https://github.com/processone/yconf", {tag, "1.0.22"}}}
]}.
{gitonly_deps, [ejabberd_po]}.
@@ -126,7 +126,6 @@
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
{if_version_above, "20", {d, 'DEPRECATED_GET_STACKTRACE'}},
{if_version_above, "20", {d, 'HAVE_ERL_ERROR'}},
{if_version_above, "20", {d, 'HAVE_URI_STRING'}},
{if_version_below, "21", {d, 'USE_OLD_HTTP_URI'}},
@@ -140,11 +139,12 @@
{if_version_below, "25", {d, 'OTP_BELOW_25'}},
{if_version_below, "26", {d, 'OTP_BELOW_26'}},
{if_version_below, "27", {d, 'OTP_BELOW_27'}},
{if_version_below, "27", {feature, maybe_expr, enable}},
{if_version_below, "28", {d, 'OTP_BELOW_28'}},
{if_var_false, debug, no_debug_info},
{if_var_true, debug, debug_info},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, new_sql_schema, {d, 'NEW_SQL_SCHEMA'}},
{if_var_true, multihost_sql_schema, {d, 'MULTIHOST_SQL_SCHEMA'}},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATEWAY_WORKAROUND'}},
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
@@ -165,7 +165,7 @@
}]}}.
{if_rebar3, {project_plugins, [configure_deps,
{if_var_true, tools, rebar3_format},
{if_var_true, tools, rebar3_lint}
{if_var_true, tools, {rebar3_lint, "4.1.1"}}
]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts, override_deps_versions2, override_opts, configure_deps
@@ -255,7 +255,9 @@
{cover_enabled, true}.
{cover_export_enabled, true}.
{cover_excl_mods, [eldap_filter_yecc]}.
{coveralls_coverdata, "_build/test/cover/ct.coverdata"}.
{coveralls_parallel, "true"}.
{coveralls_service_name, "github"}.
%%%
+48 -48
View File
@@ -1,86 +1,86 @@
{"1.2.0",
[{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},1},
{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.31">>},0},
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.24">>},0},
{<<"cache_tab">>,{pkg,<<"cache_tab">>,<<"1.0.33">>},0},
{<<"eimp">>,{pkg,<<"eimp">>,<<"1.0.26">>},0},
{<<"epam">>,{pkg,<<"epam">>,<<"1.0.14">>},0},
{<<"eredis">>,{pkg,<<"eredis">>,<<"1.7.1">>},0},
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.57">>},0},
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.13">>},0},
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.22">>},0},
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.55">>},0},
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.37">>},0},
{<<"esip">>,{pkg,<<"esip">>,<<"1.0.59">>},0},
{<<"ezlib">>,{pkg,<<"ezlib">>,<<"1.0.15">>},0},
{<<"fast_tls">>,{pkg,<<"fast_tls">>,<<"1.1.25">>},0},
{<<"fast_xml">>,{pkg,<<"fast_xml">>,<<"1.1.57">>},0},
{<<"fast_yaml">>,{pkg,<<"fast_yaml">>,<<"1.0.39">>},0},
{<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},0},
{<<"jiffy">>,{pkg,<<"jiffy">>,<<"1.1.2">>},1},
{<<"jose">>,{pkg,<<"jose">>,<<"1.11.10">>},0},
{<<"luerl">>,{pkg,<<"luerl">>,<<"1.2.3">>},0},
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.17">>},0},
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.25">>},0},
{<<"mqtree">>,{pkg,<<"mqtree">>,<<"1.0.19">>},0},
{<<"p1_acme">>,{pkg,<<"p1_acme">>,<<"1.0.29">>},0},
{<<"p1_mysql">>,{pkg,<<"p1_mysql">>,<<"1.0.26">>},0},
{<<"p1_oauth2">>,{pkg,<<"p1_oauth2">>,<<"0.6.14">>},0},
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.32">>},0},
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.27">>},0},
{<<"p1_pgsql">>,{pkg,<<"p1_pgsql">>,<<"1.1.36">>},0},
{<<"p1_utils">>,{pkg,<<"p1_utils">>,<<"1.0.28">>},0},
{<<"pkix">>,{pkg,<<"pkix">>,<<"1.0.10">>},0},
{<<"sqlite3">>,{pkg,<<"sqlite3">>,<<"1.1.15">>},0},
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.31">>},0},
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.17">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.10.0">>},0},
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.18">>},0}]}.
{<<"stringprep">>,{pkg,<<"stringprep">>,<<"1.0.33">>},0},
{<<"stun">>,{pkg,<<"stun">>,<<"1.2.21">>},0},
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.1">>},1},
{<<"xmpp">>,{pkg,<<"xmpp">>,<<"1.11.2">>},0},
{<<"yconf">>,{pkg,<<"yconf">>,<<"1.0.22">>},0}]}.
[
{pkg_hash,[
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
{<<"cache_tab">>, <<"E4097B50A6F373AB1E0A5F01BAB0BEF6626771A4CD6C93404ED6D54810E11FBC">>},
{<<"eimp">>, <<"853DB317BA394D479D2F1181E0B0135A3CD3C2C7EB48FF6CFB035A28DD354B14">>},
{<<"cache_tab">>, <<"E2542AFB34F17EE3CA19D2B0F546A074922C2B99FB6B2ACFB38160D7D0336EC3">>},
{<<"eimp">>, <<"C0B05F32E35629C4D9BCFB832FF879A92B0F92B19844BC7835E0A45635F2899A">>},
{<<"epam">>, <<"AA0B85D27F4EF3A756AE995179DF952A0721237E83C6B79D644347B75016681A">>},
{<<"eredis">>, <<"39E31AA02ADCD651C657F39AAFD4D31A9B2F63C6C700DC9CECE98D4BC3C897AB">>},
{<<"esip">>, <<"4B14E4832D08B9FFC10D855B5D10B3083232B1D53DEB4C046679496CE85569C4">>},
{<<"ezlib">>, <<"3C7F62862850A241159C10B218ECF580BCE54D0890601B65144DACC2633BE2B0">>},
{<<"fast_tls">>, <<"44356B256AFAD4399C2FC5059A3066669DAFD8BD4E4E796C9C1CF8910DDD265E">>},
{<<"fast_xml">>, <<"ACE020F2521F2A484AC8467D2822AF85534A346E2AAE03FFCBC34F29318BEFAF">>},
{<<"fast_yaml">>, <<"F71D472FBF787CCD161B914D1EB486116A0F4F2E835337A378FBD31B59D2E74B">>},
{<<"esip">>, <<"EB202F8C62928193588091DFEDBC545FE3274C34ECD209961F86DCB6C9EBCE88">>},
{<<"ezlib">>, <<"D74F5DF191784744726A5B1AE9062522C606334F11086363385EB3B772D91357">>},
{<<"fast_tls">>, <<"DA8ED6F05A2452121B087158B17234749F36704C1F2B74DC51DB99A1E27ED5E8">>},
{<<"fast_xml">>, <<"31EFC0F9BCEDA92069704F7A25830407DA5DC3DAD1272B810D6F2E13E73CC11A">>},
{<<"fast_yaml">>, <<"2E71168091949BAB0E5F583B340A99072B4D22D93EB86624E7850A12B1517BE4">>},
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
{<<"jiffy">>, <<"A9B6C9A7EC268E7CF493D028F0A4C9144F59CCB878B1AFE42841597800840A1B">>},
{<<"jose">>, <<"A903F5227417BD2A08C8A00A0CBCC458118BE84480955E8D251297A425723F83">>},
{<<"luerl">>, <<"DF25F41944E57A7C4D9EF09D238BC3E850276C46039CFC12B8BB42ECCF36FCB1">>},
{<<"mqtree">>, <<"82F54B8F2D22B4445DB1D6CCCB7FE9EAD049D61410C29E32475F3CEB3EE62A89">>},
{<<"p1_acme">>, <<"DB91F0D6C193CD1D5C0B0FA3939A898DBF56A6075DB4347CDE26E802715DE50C">>},
{<<"mqtree">>, <<"D769C25F898810725FC7DB0DBFFE5F72098647048B1BE2E6D772F1C2F31D8476">>},
{<<"p1_acme">>, <<"86372C34DE9CE7E498842828F761060C6B7726001E2853FF118893F6DD192E24">>},
{<<"p1_mysql">>, <<"574D07C9936C53B1EC3556DB3CF064CC14A6C39039835B3D940471BFA5AC8E2B">>},
{<<"p1_oauth2">>, <<"1C5F82535574DE87E2059695AC4B91F8F9AEBACBC1C80287DAE6F02552D47AEA">>},
{<<"p1_pgsql">>, <<"3F95D7E3413FC8F0BE80ABB4BE1A0D7F67066A36905085CD5A423145598B0CB0">>},
{<<"p1_utils">>, <<"F468D84C6FFA6E4B12A6160826DCF2D015527189D57865568A78B49C5ED972A1">>},
{<<"p1_pgsql">>, <<"4BC0535F2F9A2355674DFDC9E1CFC18A722325FED588977FFF1C88C5915AE3EB">>},
{<<"p1_utils">>, <<"9A7088A98D788B4C4880FD3C82D0C135650DB13F2E4EF7E10DB179791BC94D59">>},
{<<"pkix">>, <<"D3BFADF7B7CFE2A3636F1B256C9CCE5F646A07CE31E57EE527668502850765A0">>},
{<<"sqlite3">>, <<"E819DEFD280145C328457D7AF897D2E45E8E5270E18812EE30B607C99CDD21AF">>},
{<<"stringprep">>, <<"FA1688C156DD271722AA18C423A4163E710D2F4F475AD0BC220910DF669B53AF">>},
{<<"stun">>, <<"C54614A592812EA125A2E6827AAC5A438571B591616426EC1419BA9B48252F54">>},
{<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>},
{<<"xmpp">>, <<"68A6DFF8DB8987C4592B2D5DD71D3F947B4EBD15209C9ACACA5909A642670630">>},
{<<"yconf">>, <<"E565EDC8AABB8164C3BEBC86969095D296AD315DCBB46AF65DCCBC6C71EAE0F6">>}]},
{<<"stringprep">>, <<"22F42866B4F6F3C238EA2B9CB6241791184DDEDBAB55E94A025511F46325F3CA">>},
{<<"stun">>, <<"735855314AD22CB7816B88597D2F5CA22E24AA5E4D6010A0EF3AFFB33CEED6A5">>},
{<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>},
{<<"xmpp">>, <<"6EF43A6E5FB71506AF7ECCD05BBB6CCCB58EB1856C539613E76FD9A5C4E936BA">>},
{<<"yconf">>, <<"52A435F9B60AB1E13950DFE3F7131ECDD8B3D1CA72C44BF66FC74B4571027124">>}]},
{pkg_hash_ext,[
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
{<<"cache_tab">>, <<"8582B60A4A09B247EF86355BA9E07FCE9E11EDC0345A775C9171F971C72B6351">>},
{<<"eimp">>, <<"7D61432EB8A45659C0BE475F44E75EEB651743AA64A1DE8ADF785CDAD81961AD">>},
{<<"cache_tab">>, <<"4258009EB050B22AABE0C848E230BBA58401A6895C58C2FF74DFB635E3C35900">>},
{<<"eimp">>, <<"D96D4E8572B9DFC40F271E47F0CB1D8849373BC98A21223268781765ED52044C">>},
{<<"epam">>, <<"2F3449E72885A72A6C2A843F561ADD0FC2F70D7A21F61456930A547473D4D989">>},
{<<"eredis">>, <<"7C2B54C566FED55FEEF3341CA79B0100A6348FD3F162184B7ED5118D258C3CC1">>},
{<<"esip">>, <<"19C357E1817B1E04792EF359BF900400F3E6D0E5ADE929FD72F88EA9B44AF2ED">>},
{<<"ezlib">>, <<"9EE62AB3F8ED55A0FD11A9569FCB8E458683F95575417272192B069F092ABFBB">>},
{<<"fast_tls">>, <<"E65779AEFB7AB15C4755230FEF8077E687D20CC5A3984A5974F9F657E8E2485B">>},
{<<"fast_xml">>, <<"83F3E23A780ED5F567CDEC73953F06C95B838D709DBFA86B59A98A8D23C99F85">>},
{<<"fast_yaml">>, <<"8DE868721BF7E2172414F7D3148EDE0F3C922B496455CD625DD5C4429515A769">>},
{<<"esip">>, <<"0BDF2E3C349DC0B144F173150329E675C6A51AC473D7A0B2E362245FAAD3FBE6">>},
{<<"ezlib">>, <<"DD14BA6C12521AF5CFE6923E73E3D545F4A0897DC66BFAB5287FBB7AE3962EAB">>},
{<<"fast_tls">>, <<"59E183B5740E670E02B8AA6BE673B5E7779E5FE5BFCC679FE2D4993D1949A821">>},
{<<"fast_xml">>, <<"EEC34E90ADACAFE467D5DDAB635A014DED73B98B4061554B2D1972173D929C39">>},
{<<"fast_yaml">>, <<"24C7B9AB9E2B9269D64E45F4A2A1280966ADB17D31E63365CFD3EE277FB0A78D">>},
{<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>},
{<<"jiffy">>, <<"BB61BC42A720BBD33CB09A410E48BB79A61012C74CB8B3E75F26D988485CF381">>},
{<<"jose">>, <<"0D6CD36FF8BA174DB29148FC112B5842186B68A90CE9FC2B3EC3AFE76593E614">>},
{<<"luerl">>, <<"1B4B9D0CA5D7D280D1D2787A6A5EE9F5A212641B62BFF91556BAA53805DF3AED">>},
{<<"mqtree">>, <<"5FE8B7CF8FBC4783D0FCEB94654AC2BBF3242A58CD0397D249DED8AE021BE2A3">>},
{<<"p1_acme">>, <<"A7B55B47495DDB4F98A15E65451EC3AD43F4637B955C74CD695D98E6A645D08C">>},
{<<"mqtree">>, <<"C81065715C49A1882812F80A5AE2D842E80DD3F2D130530DF35990248BF8CE3C">>},
{<<"p1_acme">>, <<"08FD38F7FBE2DC28A237AA1B38B306B73455695CC8881A3DDD6A11B7C51F7BC7">>},
{<<"p1_mysql">>, <<"EA138083F2C54719B9CF549DBF5802A288B0019EA3E5449B354C74CC03FAFDEC">>},
{<<"p1_oauth2">>, <<"1FD3AC474E43722D9D5A87C6DF8D36F698ED87AF7BB81CBBB66361451D99AE8F">>},
{<<"p1_pgsql">>, <<"268B01E8F4EB75C211A31495A25C2815C549AECCE2F0DF1A161C6E0A2CDE061E">>},
{<<"p1_utils">>, <<"F1AF942B0A62BCFA0D59FBE30679BE4FFEB5E241A0C49ED5F094DB2F5B80F5E0">>},
{<<"p1_pgsql">>, <<"82BCA8B895C84F4600EB8D609A32CB5FDD72A7F5BD938DFB29179E08C643FD09">>},
{<<"p1_utils">>, <<"C49BD44BC4A40AD996691AF826DD7E0AA56D4D0CD730817190A1F84D1A7F0033">>},
{<<"pkix">>, <<"E02164F83094CB124C41B1AB28988A615D54B9ADC38575F00F19A597A3AC5D0E">>},
{<<"sqlite3">>, <<"3C0BA4E13322C2AD49DE4E2DDD28311366ADDE54BEAE8DBA9D9E3888F69D2857">>},
{<<"stringprep">>, <<"E9699C88E8DB16B3A41F0E45AC6874A4DA81A6E4854A77D76EDE6D09B08E3530">>},
{<<"stun">>, <<"6B318244C21E8524A9AAE3AC9A05CD8234EE994C1C2C815DE68D306086AD768D">>},
{<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>},
{<<"xmpp">>, <<"CEEAE43B8FE97649D8F8546B3F7F2B38ECFC931C0CDD5C7445FFB3F80FCB7D85">>},
{<<"yconf">>, <<"FA950EC6503F92D6417FB8CC1D982403F041697E8E1BBF4D4588FB919B9562EA">>}]}
{<<"stringprep">>, <<"96F8B30BC50887F605B33B46BCA1D248C19A879319B8C482790E3B4DA5DA98C0">>},
{<<"stun">>, <<"3D7FE8EFB9D05B240A6AA9A6BF8B8B7BFF2D802895D170443C588987DC1E12D9">>},
{<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>},
{<<"xmpp">>, <<"BB681644E15E3EFC0008AB3A717944D67CF611A4B7E344382AA6367447BD52D2">>},
{<<"yconf">>, <<"ACA83457CEABE70756484B5C87BA7B1955F511D499168687EAEAA7C300E857F1">>}]}
].
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -18,7 +18,7 @@
CREATE TABLE users (
username varchar(191) NOT NULL,
type smallint NOT NULL,,
type smallint NOT NULL,
password text NOT NULL,
serverkey varchar(128) NOT NULL DEFAULT '',
salt varchar(128) NOT NULL DEFAULT '',
+1 -1
View File
@@ -20,7 +20,7 @@
-- ALTER TABLE users ADD COLUMN server_host text NOT NULL DEFAULT '<HOST>';
-- ALTER TABLE users DROP CONSTRAINT users_pkey;
-- ALTER TABLE users ADD PRIMARY KEY (server_host, username);
-- ALTER TABLE users ADD PRIMARY KEY (server_host, username, "type");
-- ALTER TABLE users ALTER COLUMN server_host DROP DEFAULT;
-- ALTER TABLE last ADD COLUMN server_host text NOT NULL DEFAULT '<HOST>';
+1 -1
View File
@@ -18,7 +18,7 @@
CREATE TABLE users (
username text NOT NULL,
"type" smallint NOT NULL
"type" smallint NOT NULL,
"password" text NOT NULL,
serverkey text NOT NULL DEFAULT '',
salt text NOT NULL DEFAULT '',
+2
View File
@@ -482,6 +482,8 @@ domain() ->
non_empty(binary()),
fun(Val) ->
try jid:tolower(jid:decode(Val)) of
{<<"">>, <<"xn--", _/binary>> = Domain, <<"">>} ->
unicode:characters_to_binary(idna:decode(binary_to_list(Domain)), utf8);
{<<"">>, Domain, <<"">>} -> Domain;
_ -> fail({bad_domain, Val})
catch _:{bad_jid, _} ->
-1
View File
@@ -42,7 +42,6 @@
-protocol({xep, 388, '0.4.0', '24.02', "complete", ""}).
-protocol({xep, 440, '0.4.0', '24.02', "complete", ""}).
-protocol({xep, 474, '0.4.0', '24.02', "complete", "0.4.0 since 25.03"}).
-protocol({xep, 485, '0.2.0', '24.02', "complete", "mod_pubsub_serverinfo in ejabberd-contrib.git"}).
-export([start/0, stop/0, halt/0, start_app/1, start_app/2,
get_pid_file/0, check_apps/0, module_name/1, is_loaded/0]).
+32 -7
View File
@@ -34,6 +34,7 @@
reopen_log/0, rotate_log/0,
set_loglevel/1,
evacuate_kindly/2,
restart_kindly/2,
stop_kindly/2, send_service_message_all_mucs/2,
registered_vhosts/0,
reload_config/0,
@@ -181,7 +182,20 @@ get_commands_spec() ->
args_example = [60, <<"Server will stop now.">>],
args = [{delay, integer}, {announcement, string}],
result = {res, rescode}},
#ejabberd_commands{name = stop_kindly, tags = [server],
#ejabberd_commands{name = restart_kindly, tags = [server, async],
desc = "Restart kindly the server",
longdesc = "Inform users and rooms, wait, and restart the server.\n"
"Provide the delay in seconds, and the "
"announcement quoted, for example: \n"
"`ejabberdctl restart_kindly 60 "
"\\\"The server will stop in one minute.\\\"`",
note = "added in 25.10",
module = ?MODULE, function = restart_kindly,
args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
args_example = [60, <<"Server will restart now.">>],
args = [{delay, integer}, {announcement, string}],
result = {res, rescode}},
#ejabberd_commands{name = stop_kindly, tags = [server, async],
desc = "Stop kindly the server (informing users)",
longdesc = "Inform users and rooms, wait, and stop the server.\n"
"Provide the delay in seconds, and the "
@@ -274,7 +288,9 @@ get_commands_spec() ->
`ejabberdctl` (or some `CTL_ON_` container
environment variables) to run more commands
afterwards, you may want to precede them with
the _`started`_ command to ensure the
the `started`
_`../../admin/guide/managing.md#ejabberdctl-commands|ejabberdctl command`_
to ensure the
clustering process has completed before
proceeding. For example: `join_cluster
ejabberd@main` > `started` > `list_cluster`.",
@@ -704,6 +720,9 @@ set_loglevel(LogLevel) ->
evacuate_kindly(DelaySeconds, AnnouncementTextString) ->
perform_kindly(DelaySeconds, AnnouncementTextString, evacuate).
restart_kindly(DelaySeconds, AnnouncementTextString) ->
perform_kindly(DelaySeconds, AnnouncementTextString, restart).
stop_kindly(DelaySeconds, AnnouncementTextString) ->
perform_kindly(DelaySeconds, AnnouncementTextString, stop).
@@ -721,15 +740,19 @@ perform_kindly(DelaySeconds, AnnouncementTextString, Action) ->
ejabberd_admin,
send_service_message_all_mucs,
[Subject, AnnouncementText]},
{WaitingDesc, timer, sleep, [DelaySeconds * 1000]},
{"Stopping ejabberd", application, stop, [ejabberd]}],
{WaitingDesc, timer, sleep, [DelaySeconds * 1000]}
],
SpecificSteps =
case Action of
evacuate ->
[{"Starting ejabberd", application, start, [ejabberd]},
[{"Stopping ejabberd", application, stop, [ejabberd]},
{"Starting ejabberd", application, start, [ejabberd]},
{"Stopping ejabberd port listeners", ejabberd_listener, stop_listeners, []}];
restart ->
[{"Restarting Erlang node", init, restart, []}];
stop ->
[{"Stopping Mnesia", mnesia, stop, []}, {"Stopping Erlang node", init, stop, []}]
[
{"Stopping Erlang node", init, stop, []}]
end,
Steps = PreSteps ++ SpecificSteps,
NumberLast = length(Steps),
@@ -737,7 +760,9 @@ perform_kindly(DelaySeconds, AnnouncementTextString, Action) ->
lists:foldl(fun({Desc, Mod, Func, Args}, NumberThis) ->
SecondsDiff =
calendar:datetime_to_gregorian_seconds({date(), time()}) - TimestampStart,
io:format("[~p/~p ~ps] ~ts... ", [NumberThis, NumberLast, SecondsDiff, Desc]),
io:format("~s[~p/~p ~ps]~s ~ts...~s ",
[?CLEAD ++ ?CINFO, NumberThis, NumberLast, SecondsDiff,
?CMID ++ ?CINFO, Desc, ?CCLEAN]),
Result = (catch apply(Mod, Func, Args)),
io:format("~p~n", [Result]),
NumberThis + 1
+7 -1
View File
@@ -32,7 +32,7 @@
-export([start/2, prep_stop/1, stop/1]).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
%%%
%%% Application API
@@ -44,6 +44,7 @@ start(normal, _Args) ->
ejabberd_logger:start(),
write_pid_file(),
start_included_apps(),
misc:warn_unset_home(),
start_elixir_application(),
setup_if_elixir_conf_used(),
case ejabberd_config:load() of
@@ -108,6 +109,7 @@ prep_stop(State) ->
ejabberd_service:stop(),
ejabberd_s2s:stop(),
ejabberd_system_monitor:stop(),
gen_mod:prep_stop(),
gen_mod:stop(),
State.
@@ -176,6 +178,10 @@ file_queue_init() ->
Err -> throw({?MODULE, Err})
end.
%%%
%%% Elixir
%%%
-ifdef(ELIXIR_ENABLED).
is_using_elixir_config() ->
Config = ejabberd_config:path(),
+5 -5
View File
@@ -237,6 +237,7 @@ check_password(User, AuthzId, Server, Password, Digest, DigestGen) ->
case check_password_with_authmodule(
User, AuthzId, Server, Password, Digest, DigestGen) of
{true, _AuthModule} -> true;
{false, _ErrorAtom, _Reason} -> false;
false -> false
end.
@@ -423,9 +424,8 @@ count_users(Server, Opts) ->
-spec get_password(binary(), binary()) -> false | [password()].
get_password(User, Server) ->
case get_password_with_authmodule(User, Server) of
{Passwords, _} -> Passwords
end.
{Passwords, _} = get_password_with_authmodule(User, Server),
Passwords.
-spec get_password_s(binary(), binary()) -> password().
get_password_s(User, Server) ->
@@ -756,7 +756,7 @@ db_set_password(User, Server, PlainPassword, Passwords, Mod) ->
end
end,
case Ret of
{ok, _} -> ok;
{ok, _} -> ejabberd_hooks:run(set_password, Server, [User, Server]);
{error, _} = Err -> Err
end.
@@ -815,7 +815,7 @@ db_user_exists(User, Server, Mod) ->
end,
case Val of
{ok, _} ->
{true, Mod /= ejabberd_auth_anonymous} ;
{true, Mod /= ejabberd_auth_anonymous};
not_found ->
{false, Mod /= ejabberd_auth_anonymous};
error ->
+1 -7
View File
@@ -151,13 +151,7 @@ get_users(Server, []) ->
Users = mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Server}], ['$1']}]),
{_, Res} = lists:foldl(
fun({U, S, _}, {{U2, S2}, _} = Acc) when U == U2 andalso S == S2 ->
Acc;
({U, S, _}, {_, Res}) ->
{{U, S}, [{U, S} | Res]}
end, {{none, none}, []}, Users),
Res;
misc:lists_uniq([{U, S} || {U, S, _} <- Users]);
get_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
+13 -16
View File
@@ -258,13 +258,6 @@ drop_password_type(LServer, Hash) ->
?SQL("delete from users"
" where type=%(Type)d and %(LServer)H")).
scram_hash_encode(Hash, StoreKey) ->
case Hash of
sha -> StoreKey;
sha256 -> <<"sha256:", StoreKey/binary>>;
sha512 -> <<"sha512:", StoreKey/binary>>
end.
set_password_scram_t(LUser, LServer, Hash,
StoredKey, ServerKey, Salt, IterationCount) ->
Type = hash_to_num(Hash),
@@ -406,40 +399,44 @@ which_users_exists(LServer, LUsers) ->
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
fun(Host, #passwd{us = {LUser, LServer, plain}, password = Password})
when LServer == Host,
is_binary(Password) ->
[?SQL("delete from users where username=%(LUser)s and %(LServer)H;"),
[?SQL("delete from users where username=%(LUser)s and type=1 and %(LServer)H;"),
?SQL_INSERT(
"users",
["username=%(LUser)s",
"server_host=%(LServer)s",
"type=1",
"password=%(Password)s"])];
(Host, {passwd, {LUser, LServer},
{scram, StoredKey1, ServerKey, Salt, IterationCount}})
(Host, {passwd, {LUser, LServer, _},
{scram, StoredKey, ServerKey, Salt, IterationCount}})
when LServer == Host ->
Hash = sha,
StoredKey = scram_hash_encode(Hash, StoredKey1),
[?SQL("delete from users where username=%(LUser)s and %(LServer)H;"),
Type = hash_to_num(Hash),
[?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
?SQL_INSERT(
"users",
["username=%(LUser)s",
"server_host=%(LServer)s",
"type=%(Type)d",
"password=%(StoredKey)s",
"serverkey=%(ServerKey)s",
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"])];
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
(Host, #passwd{us = {LUser, LServer, _}, password = #scram{} = Scram})
when LServer == Host ->
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),
StoredKey = Scram#scram.storedkey,
ServerKey = Scram#scram.serverkey,
Salt = Scram#scram.salt,
IterationCount = Scram#scram.iterationcount,
[?SQL("delete from users where username=%(LUser)s and %(LServer)H;"),
Type = hash_to_num(Scram#scram.hash),
[?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
?SQL_INSERT(
"users",
["username=%(LUser)s",
"server_host=%(LServer)s",
"type=%(Type)d",
"password=%(StoredKey)s",
"serverkey=%(ServerKey)s",
"salt=%(Salt)s",
+28 -17
View File
@@ -416,8 +416,8 @@ unauthenticated_stream_features(#{lserver := LServer}) ->
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]).
inline_stream_features(#{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_inline_features, LServer, {[], [], []}, [LServer, State]).
sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) ->
Type = ejabberd_auth:store_type(LServer),
@@ -440,21 +440,32 @@ sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = St
end,
%% I re-created it from cyrsasl ets magic, but I think it's wrong
%% TODO: need to check before 18.09 release
lists:filter(
fun(<<"ANONYMOUS">>) ->
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
(<<"DIGEST-MD5">>) -> Digest;
(<<"SCRAM-SHA-1">>) -> ShaAv;
(<<"SCRAM-SHA-1-PLUS">>) -> ShaAv andalso Encrypted;
(<<"SCRAM-SHA-256">>) -> Sha256Av;
(<<"SCRAM-SHA-256-PLUS">>) -> Sha256Av andalso Encrypted;
(<<"SCRAM-SHA-512">>) -> Sha512Av;
(<<"SCRAM-SHA-512-PLUS">>) -> Sha512Av andalso Encrypted;
(<<"PLAIN">>) -> true;
(<<"X-OAUTH2">>) -> [ejabberd_auth_anonymous] /= ejabberd_auth:auth_modules(LServer);
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
(_) -> false
end, Mechs -- Mechs1).
Mechs2 = lists:filter(
fun(<<"ANONYMOUS">>) ->
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
(<<"DIGEST-MD5">>) -> Digest;
(<<"SCRAM-SHA-1">>) -> ShaAv;
(<<"SCRAM-SHA-1-PLUS">>) -> ShaAv andalso Encrypted;
(<<"SCRAM-SHA-256">>) -> Sha256Av;
(<<"SCRAM-SHA-256-PLUS">>) -> Sha256Av andalso Encrypted;
(<<"SCRAM-SHA-512">>) -> Sha512Av;
(<<"SCRAM-SHA-512-PLUS">>) -> Sha512Av andalso Encrypted;
(<<"PLAIN">>) -> true;
(<<"X-OAUTH2">>) -> [ejabberd_auth_anonymous] /= ejabberd_auth:auth_modules(LServer);
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);
(_) -> false
end, Mechs -- Mechs1),
case ejabberd_option:auth_password_types_hidden_in_sasl1() of
[] -> Mechs2;
List ->
Mechs3 = lists:foldl(
fun(plain, Acc) -> Acc -- [<<"PLAIN">>];
(scram_sha1, Acc) -> Acc -- [<<"SCRAM-SHA-1">>, <<"SCRAM-SHA-1-PLUS">>];
(scram_sha256, Acc) -> Acc -- [<<"SCRAM-SHA-256">>, <<"SCRAM-SHA-256-PLUS">>];
(scram_sha512, Acc) -> Acc -- [<<"SCRAM-SHA-512">>, <<"SCRAM-SHA-512-PLUS">>]
end, Mechs2, List),
{Mechs3, Mechs2}
end.
sasl_options(#{lserver := LServer}) ->
case ejabberd_option:disable_sasl_scram_downgrade_protection(LServer) of
+13 -1
View File
@@ -97,6 +97,8 @@ perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?STR(Str)];
perl_gen({Name, binary}, Str, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?STR(Str)];
perl_gen({Name, binary_or_list}, Str, Indent, HTMLOutput) ->
perl_gen({Name, {list, {Name, binary}}}, Str, Indent, HTMLOutput);
perl_gen({Name, atom}, Atom, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?STR_A(Atom)];
perl_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
@@ -128,6 +130,8 @@ java_gen({Name, string}, Str, _Indent, HTMLOutput) ->
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
java_gen({Name, binary}, Str, _Indent, HTMLOutput) ->
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR(Str), ?OP_L(");")];
java_gen({Name, binary_or_list}, Str, Indent, HTMLOutput) ->
java_gen({Name, {list, {Name, binary}}}, Str, Indent, HTMLOutput);
java_gen({Name, atom}, Atom, _Indent, HTMLOutput) ->
[?ID_L("put"), ?OP_L("("), ?STR_A(Name), ?OP_L(", "), ?STR_A(Atom), ?OP_L(");")];
java_gen({Name, {tuple, Fields}}, Tuple, Indent, HTMLOutput) ->
@@ -178,6 +182,8 @@ xml_gen({Name, binary}, Str, Indent, HTMLOutput) ->
[?XML_L(name, Indent, 1, ?ID_A(Name)),
?XML(value, Indent, 1,
[?XML_L(string, Indent, 2, ?ID(Str))])])];
xml_gen({Name, binary_or_list}, Str, Indent, HTMLOutput) ->
xml_gen({Name, {list, {Name, binary}}}, Str, Indent, HTMLOutput);
xml_gen({Name, atom}, Atom, Indent, HTMLOutput) ->
[?XML(member, Indent,
[?XML_L(name, Indent, 1, ?ID_A(Name)),
@@ -215,6 +221,8 @@ json_gen({_Name, string}, Str, _Indent, HTMLOutput) ->
[?STR(Str)];
json_gen({_Name, binary}, Str, _Indent, HTMLOutput) ->
[?STR(Str)];
json_gen({Name, binary_or_list}, Str, Indent, HTMLOutput) ->
json_gen({Name, {list, {Name, binary}}}, Str, Indent, HTMLOutput);
json_gen({_Name, atom}, Atom, _Indent, HTMLOutput) ->
[?STR_A(Atom)];
json_gen({_Name, rescode}, Val, _Indent, HTMLOutput) ->
@@ -270,6 +278,8 @@ generate_example_input({_Name, string}, {LastStr, LastNum}) ->
{string:chars(LastStr+1, 5), {LastStr+1, LastNum}};
generate_example_input({_Name, binary}, {LastStr, LastNum}) ->
{iolist_to_binary(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
generate_example_input({_Name, binary_or_list}, {LastStr, LastNum}) ->
{[iolist_to_binary(string:chars(LastStr+1, 5))], {LastStr+1, LastNum}};
generate_example_input({_Name, atom}, {LastStr, LastNum}) ->
{list_to_atom(string:chars(LastStr+1, 5)), {LastStr+1, LastNum}};
generate_example_input({_Name, rescode}, {LastStr, LastNum}) ->
@@ -347,6 +357,8 @@ format_type({Name, Type}) ->
io_lib:format("~ts::~ts", [Name, format_type(Type)]);
format_type(binary) ->
"string";
format_type(binary_or_list) ->
"string | [string]";
format_type(atom) ->
"string";
format_type(Type) ->
@@ -398,7 +410,7 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
TagsText = ?RAW(string:join(["_`"++atom_to_list(Tag)++"`_" || Tag <- Tags], ", ")),
IsDefinerMod = case Definer of
unknown -> false;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
_ -> lists:member(gen_mod, lists:flatten(proplists:get_all_values(behaviour, Definer:module_info(attributes))))
end,
ModuleText = case IsDefinerMod of
true ->
+79 -32
View File
@@ -39,6 +39,7 @@
-export([callback_modules/1]).
-export([set_option/2]).
-export([get_defined_keywords/1, get_predefined_keywords/1, replace_keywords/2, replace_keywords/3]).
-export([resolve_host_alias/1]).
%% Deprecated functions
-export([get_option/2]).
@@ -51,7 +52,7 @@
{get_lang, 1}]).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-type option() :: atom() | {atom(), global | binary()}.
-type error_reason() :: {merge_conflict, atom(), binary()} |
@@ -168,14 +169,14 @@ get_option({O, Host} = Opt) ->
T -> T
end,
try ets:lookup_element(Tab, Opt, 2)
catch ?EX_RULE(error, badarg, St) when Host /= global ->
StackTrace = ?EX_STACK(St),
Val = get_option({O, global}),
?DEBUG("Option '~ts' is not defined for virtual host '~ts'. "
"This is a bug, please report it with the following "
"stacktrace included:~n** ~ts",
[O, Host, misc:format_exception(2, error, badarg, StackTrace)]),
Val
catch
error:badarg:StackTrace when Host /= global ->
Val = get_option({O, global}),
?DEBUG("Option '~ts' is not defined for virtual host '~ts'. "
"This is a bug, please report it with the following "
"stacktrace included:~n** ~ts",
[O, Host, misc:format_exception(2, error, badarg, StackTrace)]),
Val
end.
-spec set_option(option(), term()) -> ok.
@@ -263,30 +264,31 @@ version() ->
-spec default_db(binary() | global, module()) -> atom().
default_db(Host, Module) ->
default_db(default_db, Host, Module, mnesia).
default_db(default_db, db_type, Host, Module, mnesia).
-spec default_db(binary() | global, module(), atom()) -> atom().
default_db(Host, Module, Default) ->
default_db(default_db, Host, Module, Default).
default_db(default_db, db_type, Host, Module, Default).
-spec default_ram_db(binary() | global, module()) -> atom().
default_ram_db(Host, Module) ->
default_db(default_ram_db, Host, Module, mnesia).
default_db(default_ram_db, ram_db_type, Host, Module, mnesia).
-spec default_ram_db(binary() | global, module(), atom()) -> atom().
default_ram_db(Host, Module, Default) ->
default_db(default_ram_db, Host, Module, Default).
default_db(default_ram_db, ram_db_type, Host, Module, Default).
-spec default_db(default_db | default_ram_db, binary() | global, module(), atom()) -> atom().
default_db(Opt, Host, Mod, Default) ->
-spec default_db(default_db | default_ram_db, db_type | ram_db_type, binary() | global, module(), atom()) -> atom().
default_db(Opt, ModOpt, Host, Mod, Default) ->
Type = get_option({Opt, Host}),
DBMod = list_to_atom(atom_to_list(Mod) ++ "_" ++ atom_to_list(Type)),
case code:ensure_loaded(DBMod) of
{module, _} -> Type;
{error, _} ->
?WARNING_MSG("Module ~ts doesn't support database '~ts' "
"defined in option '~ts', using "
"'~ts' as fallback", [Mod, Type, Opt, Default]),
"defined in toplevel option '~ts': will use the value "
"set in ~ts option '~ts', or '~ts' as fallback",
[Mod, Type, Opt, Mod, ModOpt, Default]),
Default
end.
@@ -340,7 +342,12 @@ may_hide_data(Data) ->
-spec env_binary_to_list(atom(), atom()) -> {ok, any()} | undefined.
env_binary_to_list(Application, Parameter) ->
%% Application need to be loaded to allow setting parameters
application:load(Application),
case proplists:is_defined(Application, application:loaded_applications()) of
true ->
ok;
false ->
application:load(Application)
end,
case application:get_env(Application, Parameter) of
{ok, Val} when is_binary(Val) ->
BVal = binary_to_list(Val),
@@ -501,15 +508,62 @@ get_predefined_keywords(Host) ->
global ->
[];
_ ->
[{<<"HOST">>, Host}]
[{<<"HOST">>, Host}, {<<"HOST_URL_ENCODE">>, misc:url_encode(Host)}]
end,
{ok, [[Home]]} = init:get_argument(home),
Home = misc:get_home(),
ConfigDirPath =
iolist_to_binary(filename:dirname(
ejabberd_config:path())),
LogDirPath =
iolist_to_binary(filename:dirname(
ejabberd_logger:get_log_path())),
HostList
++ [{<<"HOME">>, list_to_binary(Home)},
{<<"CONFIG_PATH">>, ConfigDirPath},
{<<"LOG_PATH">>, LogDirPath},
{<<"SEMVER">>, ejabberd_option:version()},
{<<"VERSION">>,
misc:semver_to_xxyy(
ejabberd_option:version())}].
resolve_host_alias(Host) ->
case lists:member(Host, ejabberd_option:hosts()) of
true ->
Host;
false ->
resolve_host_alias2(Host)
end.
resolve_host_alias2(Host) ->
Result =
lists:filter(fun({Alias1, _Vhost}) -> is_glob_match(Host, Alias1) end,
ejabberd_option:hosts_alias()),
case Result of
[{_, Vhost} | _] when is_binary(Vhost) ->
?DEBUG("(~p) Alias host '~s' resolved into vhost '~s'", [self(), Host, Vhost]),
Vhost;
[] ->
?DEBUG("(~p) Request sent to host '~s', which isn't a vhost or an alias",
[self(), Host]),
Host
end.
%% Copied from ejabberd-2.0.0/src/acl.erl
is_regexp_match(String, RegExp) ->
case ejabberd_regexp:run(String, RegExp) of
nomatch ->
false;
match ->
true;
{error, ErrDesc} ->
io:format("Wrong regexp ~p in ACL: ~p", [RegExp, ErrDesc]),
false
end.
is_glob_match(String, <<"!", Glob/binary>>) ->
not is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob));
is_glob_match(String, Glob) ->
is_regexp_match(String, ejabberd_regexp:sh_to_awk(Glob)).
%% @format-end
%%%===================================================================
@@ -578,15 +632,7 @@ callback_modules(external) ->
end
end, beams(external));
callback_modules(all) ->
lists_uniq(callback_modules(local) ++ callback_modules(external)).
-ifdef(OTP_BELOW_25).
lists_uniq(List) ->
lists:usort(List).
-else.
lists_uniq(List) ->
lists:uniq(List).
-endif.
misc:lists_uniq(callback_modules(local) ++ callback_modules(external)).
-spec validators(module(), [atom()], [any()]) -> econf:validators().
validators(Mod, Disallowed, DK) ->
@@ -656,7 +702,7 @@ get_additional_macros() ->
parse_macro_string(MacroString) ->
[NameString, ValueString] = string:split(MacroString, "="),
{ok, [ValueDecoded]} = fast_yaml:decode(ValueString, [plain_as_atom]),
{ok, [ValueDecoded]} = fast_yaml:decode(ValueString),
{list_to_atom(NameString), ValueDecoded}.
read_yaml_files(Files, Opts) ->
@@ -756,8 +802,9 @@ load_file(File) ->
Err ->
abort(Err)
end
catch ?EX_RULE(Class, Reason, St) ->
{error, {exception, Class, Reason, ?EX_STACK(St)}}
catch
Class:Reason:Stack ->
{error, {exception, Class, Reason, Stack}}
end.
-spec commit() -> ok.
+5
View File
@@ -230,6 +230,11 @@ filter(_Host, captcha_host, _, _) ->
filter(_Host, route_subdomains, _, _) ->
warn_removed_option(route_subdomains, s2s_access),
false;
filter(_Host, auth_password_types_hidden_in_scram1, Val, _) ->
{true, {auth_password_types_hidden_in_sasl1, Val}};
filter(_Host, new_sql_schema, Val, _) ->
warn_replaced_option(new_sql_schema, sql_schema_multihost),
{true, {sql_schema_multihost, Val}};
filter(Host, modules, ModOpts, State) ->
NoDialbackHosts = maps:get(remove_s2s_dialback, State, []),
ModOpts1 = lists:filter(
+22 -10
View File
@@ -39,7 +39,7 @@
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-define(DEFAULT_VERSION, 1000000).
@@ -331,11 +331,10 @@ try_call_command(Args, Auth, AccessCommands, Version) ->
?STATUS_ERROR};
throw:Error ->
{io_lib:format("~p", [Error]), ?STATUS_ERROR};
?EX_RULE(A, Why, Stack) ->
StackTrace = ?EX_STACK(Stack),
{io_lib:format("Unhandled exception occurred executing the command:~n** ~ts",
[misc:format_exception(2, A, Why, StackTrace)]),
?STATUS_ERROR}
A:Why:StackTrace ->
{io_lib:format("Unhandled exception occurred executing the command:~n** ~ts",
[misc:format_exception(2, A, Why, StackTrace)]),
?STATUS_ERROR}
end.
-spec call_command(Args::[string()],
@@ -390,6 +389,8 @@ format_arg(Arg, integer) ->
format_arg2(Arg, "~d");
format_arg(Arg, binary) ->
unicode:characters_to_binary(Arg, utf8);
format_arg(Arg, binary_or_list) ->
[unicode:characters_to_binary(Arg, utf8)];
format_arg("", string) ->
"";
format_arg(Arg, string) ->
@@ -1000,12 +1001,12 @@ format_usage_ctype1({Name, Type, Description}, Indentation, ShCode) ->
format_usage_ctype(Type, _Indentation)
when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary)
or (Type==rescode) or (Type==restuple) ->
or (Type==rescode) or (Type==restuple) or (Type==binary_or_list) ->
io_lib:format("~p", [Type]);
format_usage_ctype({Name, Type}, _Indentation)
when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary)
or (Type==rescode) or (Type==restuple)
or (Type==rescode) or (Type==restuple) or (Type==binary_or_list)
or (Type==any) ->
io_lib:format("~p::~p", [Name, Type]);
@@ -1148,6 +1149,17 @@ get_commands_spec() ->
desc = "Get list of commands, or help of a command (only ejabberdctl)",
longdesc = "This command is exclusive for the ejabberdctl command-line script, "
"don't attempt to execute it using any other API frontend."},
#ejabberd_commands{name = mnesia_change, tags = [ejabberdctl, mnesia],
desc = "Change the erlang node name in the mnesia database (only ejabberdctl)",
longdesc = "This command internally calls the _`mnesia_change_nodename`_ API. "
"This is a special command that starts and stops ejabberd several times: "
"do not attempt to run this command when ejabberd is running. "
"This command is exclusive for the ejabberdctl command-line script, "
"don't attempt to execute it using any other API frontend.",
note = "added in 25.08",
args = [{old_node_name, string}],
args_desc = ["Old erlang node name"],
args_example = ["ejabberd@oldmachine"]},
#ejabberd_commands{name = mnesia_info_ctl, tags = [ejabberdctl, mnesia],
desc = "Show information of Mnesia system (only ejabberdctl)",
note = "renamed in 24.02",
@@ -1158,9 +1170,9 @@ get_commands_spec() ->
longdesc = "This command is exclusive for the ejabberdctl command-line script, "
"don't attempt to execute it using any other API frontend.",
note = "added in 24.02",
args = [{db_type, string}, {db_version, string}, {new_schema, string}],
args = [{db_type, string}, {db_version, string}, {multihost_schema, string}],
args_desc = ["Database type: pgsql | mysql | sqlite",
"Your database version: 16.1, 8.2.0...",
"Use new schema: 0, false, 1 or true"],
"Use multihost schema: 0, false, 1 or true"],
args_example = ["pgsql", "16.1", "true"]}
].
+34 -3
View File
@@ -24,6 +24,7 @@
%% API
-export([man/0, man/1, have_a2x/0]).
-include("ejabberd_commands.hrl").
-include("translate.hrl").
%%%===================================================================
@@ -46,7 +47,8 @@ man(Lang) ->
DocOpts = maps:get(opts, Map, []),
Example = maps:get(example, Map, []),
Note = maps:get(note, Map, []),
{[{M, Descr, DocOpts, #{example => Example, note => Note}}|Mods], SubMods};
Apitags = get_module_apitags(M),
{[{M, Descr, DocOpts, #{example => Example, note => Note, apitags => Apitags}}|Mods], SubMods};
#{opts := DocOpts} ->
{ParentMod, Backend} = strip_backend_suffix(M),
{Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)};
@@ -113,7 +115,8 @@ man(Lang) ->
format_versions(Lang, Example) ++ [io_lib:nl()] ++
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++
format_example(0, Lang, Example)
format_example(0, Lang, Example) ++ [io_lib:nl()] ++
format_apitags(Lang, Example)
end, lists:keysort(1, ModDoc1)),
ListenOptions =
[io_lib:nl(),
@@ -190,6 +193,34 @@ format_versions(_Lang, #{note := Note}) when Note /= [] ->
format_versions(_, _) ->
[].
%% @format-begin
get_module_apitags(M) ->
AllCommands = ejabberd_commands:get_commands_definition(),
Tags = [C#ejabberd_commands.tags || C <- AllCommands, C#ejabberd_commands.module == M],
TagsClean =
lists:sort(
misc:lists_uniq(
lists:flatten(Tags))),
TagsStrings = [atom_to_list(C) || C <- TagsClean],
TagFiltering =
fun ("internal") ->
false;
([$v | Rest]) ->
{error, no_integer} == string:to_integer(Rest);
(_) ->
true
end,
TagsFiltered = lists:filter(TagFiltering, TagsStrings),
TagsUrls =
[["_`../../developer/ejabberd-api/admin-tags.md#", C, "|", C, "`_"] || C <- TagsFiltered],
lists:join(", ", TagsUrls).
format_apitags(_Lang, #{apitags := TagsString}) when TagsString /= "" ->
["**API Tags:** ", TagsString];
format_apitags(_, _) ->
[].
%% @format-end
format_desc(Lang, #{desc := Desc}) ->
tr_multi(Lang, Desc).
@@ -406,7 +437,7 @@ run_a2x(Cwd, AsciiDocFile) ->
{error, "a2x was not found: do you have 'asciidoc' installed?"};
{true, Path} ->
Cmd = lists:flatten(
io_lib:format("~ts -f manpage ~ts -D ~ts",
io_lib:format("~ts --no-xmllint -f manpage ~ts -D ~ts",
[Path, AsciiDocFile, Cwd])),
case os:cmd(Cmd) of
"" -> ok;
+33 -32
View File
@@ -60,7 +60,7 @@
).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state, {}).
-type subscriber() :: {Module :: atom(), Function :: atom(), InitArg :: any()}.
@@ -455,17 +455,19 @@ safe_apply(Hook, Module, Function, Args) ->
true ->
apply(Module, Function, Args)
end
catch ?EX_RULE(E, R, St) when E /= exit; R /= normal ->
Stack = ?EX_STACK(St),
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++
string:join(
["** ~ts"|
["** Arg " ++ integer_to_list(I) ++ " = ~p"
|| I <- lists:seq(1, length(Args))]],
"~n"),
[Hook, Module, Function, length(Args),
misc:format_exception(2, E, R, Stack)|Args]),
'EXIT'
catch
E:R:Stack when E /= exit; R /= normal ->
?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" ++
string:join(
["** ~ts" | [ "** Arg " ++ integer_to_list(I) ++ " = ~p"
|| I <- lists:seq(1, length(Args)) ]],
"~n"),
[Hook,
Module,
Function,
length(Args),
misc:format_exception(2, E, R, Stack) | Args]),
'EXIT'
end.
-spec call_subscriber_list([subscriber()], binary() | global, atom(), {atom(), atom(), integer(), list()} | list(), subscriber_event(), [subscriber()]) -> any().
@@ -480,18 +482,20 @@ call_subscriber_list([{Mod, Func, InitArg} | SubscriberList], Host, Hook, Callba
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)
catch
E:R:Stack when E /= exit; R /= normal ->
?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.
%%%----------------------------------------------------------------------
@@ -709,13 +713,11 @@ run_event_handlers(TracingOpts, Hook, Host, Event, EventArgs, RunType) ->
_ ->
ok
catch
?EX_RULE(E, R, St) ->
Stack = ?EX_STACK(St),
?ERROR_MSG(
E:R:Stack ->
?ERROR_MSG(
"(~0p|~ts|~0p) Tracing event '~0p' handler exception(~0p): ~0p: ~0p",
[Hook, Host, erlang:self(), EventHandler, E, R, Stack]
),
ok
[Hook, Host, erlang:self(), EventHandler, E, R, Stack]),
ok
end
end,
EventHandlerList
@@ -885,8 +887,7 @@ tracing_output(#{output_function := OutputF}, Text, Args) ->
_ ->
ok
catch
?EX_RULE(E, R, St) ->
Stack = ?EX_STACK(St),
E:R:Stack ->
?ERROR_MSG("Tracing output function exception(~0p): ~0p: ~0p", [E, R, Stack]),
ok
end;
+9 -30
View File
@@ -39,7 +39,7 @@
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_stacktrace.hrl").
-include_lib("kernel/include/file.hrl").
-record(state, {sockmod,
@@ -66,7 +66,6 @@
request_headers = [],
end_of_request = false,
options = [],
default_host,
custom_headers,
trail = <<>>,
allow_unencrypted_sasl2,
@@ -170,9 +169,8 @@ send_file(State, Fd, Size, FileName) ->
try
case State#state.sockmod of
gen_tcp ->
case file:sendfile(Fd, State#state.socket, 0, Size, []) of
{ok, _} -> ok
end;
{ok, _} = file:sendfile(Fd, State#state.socket, 0, Size, []),
ok;
_ ->
case file:read(Fd, ?SEND_BUF) of
{ok, Data} ->
@@ -275,7 +273,7 @@ process_header(State, Data) ->
request_headers = add_header(Name, Langs, State)};
{ok, {http_header, _, 'Host' = Name, _, Value}} ->
{Host, Port, TP} = get_transfer_protocol(State#state.addr_re, SockMod, Value),
State#state{request_host = Host,
State#state{request_host = ejabberd_config:resolve_host_alias(Host),
request_port = Port,
request_tp = TP,
request_headers = add_header(Name, Value, State)};
@@ -301,7 +299,6 @@ process_header(State, Data) ->
#state{sockmod = SockMod, socket = Socket,
trail = State3#state.trail,
options = State#state.options,
default_host = State#state.default_host,
custom_headers = State#state.custom_headers,
request_handlers = State#state.request_handlers,
addr_re = State#state.addr_re};
@@ -309,7 +306,6 @@ process_header(State, Data) ->
#state{end_of_request = true,
trail = State3#state.trail,
options = State#state.options,
default_host = State#state.default_host,
custom_headers = State#state.custom_headers,
request_handlers = State#state.request_handlers,
addr_re = State#state.addr_re}
@@ -317,7 +313,6 @@ process_header(State, Data) ->
_ ->
#state{end_of_request = true,
options = State#state.options,
default_host = State#state.default_host,
custom_headers = State#state.custom_headers,
request_handlers = State#state.request_handlers,
addr_re = State#state.addr_re}
@@ -377,11 +372,11 @@ process(Handlers, Request) ->
try
HandlerModule:process(LocalPath, Request)
catch
?EX_RULE(Class, Reason, Stack) ->
Class:Reason:Stack ->
?ERROR_MSG(
"HTTP handler crashed: ~s",
[misc:format_exception(2, Class, Reason, ?EX_STACK(Stack))]),
erlang:raise(Class, Reason, ?EX_STACK(Stack))
"HTTP handler crashed: ~s",
[misc:format_exception(2, Class, Reason, Stack)]),
erlang:raise(Class, Reason, Stack)
end
end,
ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]),
@@ -723,7 +718,7 @@ file_format_error(Reason) ->
url_decode_q_split_normalize(Path) ->
{NPath, Query} = url_decode_q_split(Path),
LPath = normalize_path([NPE
|| NPE <- str:tokens(path_decode(NPath), <<"/">>)]),
|| NPE <- str:tokens(misc:uri_decode(NPath), <<"/">>)]),
{LPath, Query}.
% Code below is taken (with some modifications) from the yaws webserver, which
@@ -751,19 +746,6 @@ url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 ->
url_decode_q_split(<<>>, Ack) ->
{path_norm_reverse(Ack), <<>>}.
%% @doc Decode a part of the URL and return string()
path_decode(Path) -> path_decode(Path, <<>>).
path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
Hex = list_to_integer([Hi, Lo], 16),
if Hex == 0 -> exit(badurl);
true -> ok
end,
path_decode(Tail, <<Acc/binary, Hex>>);
path_decode(<<H, T/binary>>, Acc) when H /= 0 ->
path_decode(T, <<Acc/binary, H>>);
path_decode(<<>>, Acc) -> Acc.
path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T);
path_norm_reverse(T) -> start_dir(0, <<"">>, T).
@@ -927,8 +909,6 @@ listen_opt_type(request_handlers) ->
econf:binary(),
fun(Path) -> str:tokens(Path, <<"/">>) end),
econf:beam([[{socket_handoff, 3}, {process, 2}]]));
listen_opt_type(default_host) ->
econf:domain();
listen_opt_type(custom_headers) ->
econf:map(
econf:binary(),
@@ -944,5 +924,4 @@ listen_options() ->
{allow_unencrypted_sasl2, false},
{request_handlers, []},
{tag, <<>>},
{default_host, undefined},
{custom_headers, []}].
+2 -2
View File
@@ -117,7 +117,7 @@ socket_handoff(LocalPath, Request, Opts) ->
%%% Internal
init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
init([{#ws{ip = IP, http_opts = HOpts, headers = Headers}, _} = WS]) ->
SOpts = lists:filtermap(fun({stream_management, _}) -> true;
({max_ack_queue, _}) -> true;
({ack_timeout, _}) -> true;
@@ -128,7 +128,7 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
({access, _}) -> true;
(_) -> false
end, HOpts),
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
Opts = ejabberd_c2s_config:get_c2s_limits() ++ [{http, [{headers, Headers}]}] ++ SOpts,
PingInterval = ejabberd_option:websocket_ping_interval(),
WSTimeout = ejabberd_option:websocket_timeout(),
Socket = {http_ws, self(), IP},
+6 -6
View File
@@ -36,7 +36,7 @@
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state, {expire = infinity :: timeout()}).
-type state() :: #state{}.
@@ -174,11 +174,11 @@ calc_checksum(Data) ->
-spec callback(atom() | pid(), #iq{} | timeout, term()) -> any().
callback(undefined, IQRes, Fun) ->
try Fun(IQRes)
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to process iq response:~n~ts~n** ~ts",
[xmpp:pp(IQRes),
misc:format_exception(2, Class, Reason, StackTrace)])
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to process iq response:~n~ts~n** ~ts",
[xmpp:pp(IQRes),
misc:format_exception(2, Class, Reason, StackTrace)])
end;
callback(Proc, IQRes, Ctx) ->
try
+70 -39
View File
@@ -113,12 +113,12 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
{Port, SockOpts}
end,
ExtraOpts2 = lists:keydelete(send_timeout, 1, ExtraOpts),
case gen_udp:open(Port2, [binary,
case {gen_udp:open(Port2, [binary,
{active, false},
{reuseaddr, true} |
ExtraOpts2]) of
{ok, Socket} ->
set_definitive_udsocket(Port, Opts),
ExtraOpts2]),
set_definitive_udsocket(Port, Opts)} of
{{ok, Socket}, ok} ->
misc:set_proc_label({?MODULE, udp, Port}),
case inet:sockname(Socket) of
{ok, {Addr, Port1}} ->
@@ -139,18 +139,18 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
{error, _} ->
ok
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
{error, Reason} ->
return_socket_error(Reason, EndPoint, Module)
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
{{error, Reason}, _} ->
return_socket_error(Reason, EndPoint, Module);
{_, {error, Reason} } ->
return_socket_error(Reason, EndPoint, Module)
end;
init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
case listen_tcp(Port, SockOpts) of
{ok, ListenSocket} ->
set_definitive_udsocket(Port, Opts),
case {listen_tcp(Port, SockOpts),
set_definitive_udsocket(Port, Opts)} of
{{ok, ListenSocket}, ok} ->
case inet:sockname(ListenSocket) of
{ok, {Addr, Port1}} ->
proc_lib:init_ack({ok, self()}),
@@ -174,13 +174,13 @@ init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
{error, _} ->
ok
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
{error, Reason} ->
return_socket_error(Reason, EndPoint, Module)
end;
{error, Reason} = Err ->
report_socket_error(Reason, EndPoint, Module),
proc_lib:init_ack(Err)
{{error, Reason}, _} ->
return_socket_error(Reason, EndPoint, Module);
{_, {error, Reason}} ->
return_socket_error(Reason, EndPoint, Module)
end.
-spec listen_tcp(inet:port_number(), [gen_tcp:option()]) ->
@@ -216,23 +216,39 @@ listen_tcp(Port, SockOpts) ->
setup_provisional_udsocket_dir(DefinitivePath) ->
ProvisionalPath = get_provisional_udsocket_path(DefinitivePath),
?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
?INFO_MSG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
[ProvisionalPath, DefinitivePath]),
ProvisionalPath.
ProvisionalPathAbsolute = relative_socket_to_mnesia(ProvisionalPath),
create_base_dir(ProvisionalPathAbsolute),
ProvisionalPathAbsolute.
get_provisional_udsocket_path(Path) ->
PathBase64 = misc:term_to_base64(Path),
PathBuild = filename:join(os:getenv("HOME"), PathBase64),
%% Shorthen the path, a long path produces a crash when opening the socket.
binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}).
ReproducibleSecret = binary:part(crypto:hash(sha, misc:atom_to_binary(erlang:get_cookie())), 1, 8),
PathBase64 = misc:term_to_base64({ReproducibleSecret, Path}),
PathBuild = filename:join(misc:get_home(), PathBase64),
DestPath = filename:join(filename:dirname(Path), PathBase64),
case {byte_size(DestPath) > 107, byte_size(PathBuild) > 107} of
{false, _} ->
DestPath;
{true, false} ->
?INFO_MSG("The provisional Unix Domain Socket path ~ts is longer than 107, let's use home directory instead which is ~p", [DestPath, byte_size(PathBuild)]),
PathBuild;
{true, true} ->
?ERROR_MSG("The Unix Domain Socket path ~ts is too long, "
"and I cannot create the provisional file safely. "
"Please configure a shorter path and try again.", [Path]),
throw({error_socket_path_too_long, Path})
end.
get_definitive_udsocket_path(<<"unix", _>> = Unix) ->
Unix;
get_definitive_udsocket_path(ProvisionalPath) ->
PathBase64 = filename:basename(ProvisionalPath),
{term, Path} = misc:base64_to_term(PathBase64),
{term, {_, Path}} = misc:base64_to_term(PathBase64),
relative_socket_to_mnesia(Path).
-spec set_definitive_udsocket(integer() | binary(), opts()) -> ok | {error, file:posix() | badarg}.
set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
Prov = get_provisional_udsocket_path(Path),
Usd = maps:get(unix_socket, Opts),
@@ -263,16 +279,19 @@ set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
end
end,
FinalPath = relative_socket_to_mnesia(Path),
FinalPathDir = filename:dirname(FinalPath),
case file:make_dir(FinalPathDir) of
create_base_dir(FinalPath),
file:rename(Prov, FinalPath);
set_definitive_udsocket(Port, _Opts) when is_integer(Port) ->
ok.
create_base_dir(Path) ->
Dirname = filename:dirname(Path),
case file:make_dir(Dirname) of
ok ->
file:change_mode(FinalPathDir, 8#00700);
file:change_mode(Dirname, 8#00700);
_ ->
ok
end,
file:rename(Prov, FinalPath);
set_definitive_udsocket(_Port, _Opts) ->
ok.
end.
relative_socket_to_mnesia(Path1) ->
case filename:pathtype(Path1) of
@@ -297,13 +316,15 @@ maybe_delete_udsocket_file(_Port) ->
split_opts(Transport, Opts) ->
maps:fold(
fun(Opt, Val, {ModOpts, SockOpts}) ->
case OptVal = {Opt, Val} of
case {Opt, Val} of
{ip, _} ->
{ModOpts, [OptVal|SockOpts]};
{ModOpts, [{Opt, Val} | SockOpts]};
{backlog, _} when Transport == tcp ->
{ModOpts, [OptVal|SockOpts]};
{ModOpts, [{Opt, Val} | SockOpts]};
{backlog, _} ->
{ModOpts, SockOpts};
{send_timeout, _} ->
{ModOpts, [{Opt, Val} | SockOpts]};
_ ->
{ModOpts#{Opt => Val}, SockOpts}
end
@@ -584,10 +605,20 @@ config_reloaded() ->
end
end, New).
-spec report_socket_error(inet:posix(), endpoint(), module()) -> ok.
report_socket_error(Reason, EndPoint, Module) ->
-spec return_socket_error(inet:posix(), endpoint(), module()) -> no_return().
return_socket_error(Reason, EndPoint, Module) ->
?ERROR_MSG("Failed to open socket at ~ts for ~ts: ~ts",
[format_endpoint(EndPoint), Module, format_error(Reason)]).
[format_endpoint(EndPoint), Module, format_error(Reason)]),
return_init_error(Reason).
-ifdef(OTP_BELOW_26).
return_init_error(Reason) ->
proc_lib:init_ack({error, Reason}).
-else.
-spec return_init_error(inet:posix()) -> no_return().
return_init_error(Reason) ->
proc_lib:init_fail({error, Reason}, {exit, normal}).
-endif.
-spec format_error(inet:posix() | atom()) -> string().
format_error(Reason) ->
+1 -1
View File
@@ -48,7 +48,7 @@
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_stacktrace.hrl").
-include("translate.hrl").
-record(state, {}).
+9 -5
View File
@@ -47,6 +47,8 @@
-export_type([loglevel/0]).
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
@@ -383,19 +385,21 @@ console_template() ->
false ->
[time, " [", level, "] " | msg()]
end.
msg() ->
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
msg, io_lib:nl()].
-else.
console_template() ->
[time, " [", level, "] " | msg()].
[time, " ", ?CLEAD, ?CDEFAULT, clevel, "[", level, "] ", ?CMID, ?CDEFAULT, ctext | msg()].
msg() ->
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
msg, ?CCLEAN, io_lib:nl()].
-endif.
file_template() ->
[time, " [", level, "] ", pid,
{mfa, ["@", mfa, {line, [":", line], []}], []}, " " | msg()].
msg() ->
[{logger_formatter, [[logger_formatter, title], ":", io_lib:nl()], []},
msg, io_lib:nl()].
-spec reopen_log() -> ok.
reopen_log() ->
ok.
+14 -12
View File
@@ -42,7 +42,7 @@
-define(NEED_RESET, [local_content, type]).
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state, {tables = #{} :: tables(),
schema = [] :: [{atom(), custom_schema()}]}).
@@ -80,10 +80,11 @@ init([]) ->
Schema = read_schema_file(),
{ok, #state{schema = Schema}};
false ->
?CRITICAL_MSG("Node name mismatch: I'm [~ts], "
"the database is owned by ~p", [MyNode, DbNodes]),
?CRITICAL_MSG("Erlang node name mismatch: I'm running in node [~ts], "
"but the mnesia database is owned by ~p", [MyNode, DbNodes]),
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
"or change node name in Mnesia", []),
"or change node name in Mnesia by running: "
"ejabberdctl mnesia_change ~ts", [hd(DbNodes)]),
{stop, node_name_mismatch}
end.
@@ -376,14 +377,15 @@ do_transform(OldAttrs, Attrs, Old) ->
transform_fun(Module, Name) ->
fun(Obj) ->
try Module:transform(Obj)
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to transform Mnesia table ~ts:~n"
"** Record: ~p~n"
"** ~ts",
[Name, Obj,
misc:format_exception(2, Class, Reason, StackTrace)]),
erlang:raise(Class, Reason, StackTrace)
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to transform Mnesia table ~ts:~n"
"** Record: ~p~n"
"** ~ts",
[Name,
Obj,
misc:format_exception(2, Class, Reason, StackTrace)]),
erlang:raise(Class, Reason, StackTrace)
end
end.
+50 -5
View File
@@ -18,6 +18,7 @@
-export([auth_method/0, auth_method/1]).
-export([auth_opts/0, auth_opts/1]).
-export([auth_password_format/0, auth_password_format/1]).
-export([auth_password_types_hidden_in_sasl1/0, auth_password_types_hidden_in_sasl1/1]).
-export([auth_scram_hash/0, auth_scram_hash/1]).
-export([auth_stored_password_types/0, auth_stored_password_types/1]).
-export([auth_use_cache/0, auth_use_cache/1]).
@@ -55,6 +56,7 @@
-export([hide_sensitive_log_data/0, hide_sensitive_log_data/1]).
-export([host_config/0]).
-export([hosts/0]).
-export([hosts_alias/0]).
-export([include_config_file/0, include_config_file/1]).
-export([install_contrib_modules/0]).
-export([jwt_auth_only_rule/0, jwt_auth_only_rule/1]).
@@ -87,7 +89,6 @@
-export([modules/0, modules/1]).
-export([negotiation_timeout/0]).
-export([net_ticktime/0]).
-export([new_sql_schema/0]).
-export([oauth_access/0, oauth_access/1]).
-export([oauth_cache_life_time/0]).
-export([oauth_cache_missed/0]).
@@ -119,6 +120,10 @@
-export([redis_server/0]).
-export([registration_timeout/0]).
-export([resource_conflict/0, resource_conflict/1]).
-export([rest_proxy/0, rest_proxy/1]).
-export([rest_proxy_password/0, rest_proxy_password/1]).
-export([rest_proxy_port/0, rest_proxy_port/1]).
-export([rest_proxy_username/0, rest_proxy_username/1]).
-export([router_cache_life_time/0]).
-export([router_cache_missed/0]).
-export([router_cache_size/0]).
@@ -156,6 +161,7 @@
-export([sql_prepared_statements/0, sql_prepared_statements/1]).
-export([sql_query_timeout/0, sql_query_timeout/1]).
-export([sql_queue_type/0, sql_queue_type/1]).
-export([sql_schema_multihost/0]).
-export([sql_server/0, sql_server/1]).
-export([sql_ssl/0, sql_ssl/1]).
-export([sql_ssl_cafile/0, sql_ssl_cafile/1]).
@@ -258,6 +264,13 @@ auth_password_format() ->
auth_password_format(Host) ->
ejabberd_config:get_option({auth_password_format, Host}).
-spec auth_password_types_hidden_in_sasl1() -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
auth_password_types_hidden_in_sasl1() ->
auth_password_types_hidden_in_sasl1(global).
-spec auth_password_types_hidden_in_sasl1(global | binary()) -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
auth_password_types_hidden_in_sasl1(Host) ->
ejabberd_config:get_option({auth_password_types_hidden_in_sasl1, Host}).
-spec auth_scram_hash() -> 'sha' | 'sha256' | 'sha512'.
auth_scram_hash() ->
auth_scram_hash(global).
@@ -475,6 +488,10 @@ host_config() ->
hosts() ->
ejabberd_config:get_option({hosts, global}).
-spec hosts_alias() -> [{binary(),binary()}].
hosts_alias() ->
ejabberd_config:get_option({hosts_alias, global}).
-spec include_config_file() -> any().
include_config_file() ->
include_config_file(global).
@@ -669,10 +686,6 @@ negotiation_timeout() ->
net_ticktime() ->
ejabberd_config:get_option({net_ticktime, global}).
-spec new_sql_schema() -> boolean().
new_sql_schema() ->
ejabberd_config:get_option({new_sql_schema, global}).
-spec oauth_access() -> 'none' | acl:acl().
oauth_access() ->
oauth_access(global).
@@ -833,6 +846,34 @@ resource_conflict() ->
resource_conflict(Host) ->
ejabberd_config:get_option({resource_conflict, Host}).
-spec rest_proxy() -> binary().
rest_proxy() ->
rest_proxy(global).
-spec rest_proxy(global | binary()) -> binary().
rest_proxy(Host) ->
ejabberd_config:get_option({rest_proxy, Host}).
-spec rest_proxy_password() -> string().
rest_proxy_password() ->
rest_proxy_password(global).
-spec rest_proxy_password(global | binary()) -> string().
rest_proxy_password(Host) ->
ejabberd_config:get_option({rest_proxy_password, Host}).
-spec rest_proxy_port() -> char().
rest_proxy_port() ->
rest_proxy_port(global).
-spec rest_proxy_port(global | binary()) -> char().
rest_proxy_port(Host) ->
ejabberd_config:get_option({rest_proxy_port, Host}).
-spec rest_proxy_username() -> string().
rest_proxy_username() ->
rest_proxy_username(global).
-spec rest_proxy_username(global | binary()) -> string().
rest_proxy_username(Host) ->
ejabberd_config:get_option({rest_proxy_username, Host}).
-spec router_cache_life_time() -> 'infinity' | pos_integer().
router_cache_life_time() ->
ejabberd_config:get_option({router_cache_life_time, global}).
@@ -1059,6 +1100,10 @@ sql_queue_type() ->
sql_queue_type(Host) ->
ejabberd_config:get_option({sql_queue_type, Host}).
-spec sql_schema_multihost() -> boolean().
sql_schema_multihost() ->
ejabberd_config:get_option({sql_schema_multihost, global}).
-spec sql_server() -> binary().
sql_server() ->
sql_server(global).
+30 -6
View File
@@ -21,10 +21,10 @@
-export([opt_type/1, options/0, globals/0, doc/0]).
-ifdef(NEW_SQL_SCHEMA).
-define(USE_NEW_SQL_SCHEMA_DEFAULT, true).
-ifdef(MULTIHOST_SQL_SCHEMA).
-define(USE_MULTIHOST_SQL_SCHEMA_DEFAULT, true).
-else.
-define(USE_NEW_SQL_SCHEMA_DEFAULT, false).
-define(USE_MULTIHOST_SQL_SCHEMA_DEFAULT, false).
-endif.
-include_lib("kernel/include/inet.hrl").
@@ -79,6 +79,8 @@ opt_type(auth_opts) ->
end;
opt_type(auth_stored_password_types) ->
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
opt_type(auth_password_types_hidden_in_sasl1) ->
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
opt_type(auth_password_format) ->
econf:enum([plain, scram]);
opt_type(auth_scram_hash) ->
@@ -182,6 +184,13 @@ opt_type(host_config) ->
[unique]));
opt_type(hosts) ->
econf:non_empty(econf:list(econf:domain(), [unique]));
opt_type(hosts_alias) ->
econf:and_then(
econf:map(econf:domain(), econf:domain(), [unique]),
econf:map(
econf:domain(),
econf:enum(ejabberd_config:get_option(hosts)),
[unique]));
opt_type(include_config_file) ->
econf:any();
opt_type(install_contrib_modules) ->
@@ -257,7 +266,7 @@ opt_type(negotiation_timeout) ->
econf:timeout(second);
opt_type(net_ticktime) ->
econf:timeout(second);
opt_type(new_sql_schema) ->
opt_type(sql_schema_multihost) ->
econf:bool();
opt_type(update_sql_schema) ->
econf:bool();
@@ -333,6 +342,14 @@ opt_type(registration_timeout) ->
econf:timeout(second, infinity);
opt_type(resource_conflict) ->
econf:enum([setresource, closeold, closenew, acceptnew]);
opt_type(rest_proxy) ->
econf:domain();
opt_type(rest_proxy_port) ->
econf:port();
opt_type(rest_proxy_username) ->
econf:string();
opt_type(rest_proxy_password) ->
econf:string();
opt_type(router_cache_life_time) ->
econf:timeout(second, infinity);
opt_type(router_cache_missed) ->
@@ -549,6 +566,7 @@ options() ->
{auth_password_format, plain},
{auth_scram_hash, sha},
{auth_stored_password_types, []},
{auth_password_types_hidden_in_sasl1, []},
{auth_external_user_exists_check, true},
{auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
@@ -579,6 +597,7 @@ options() ->
{extauth_program, undefined},
{fqdn, fun fqdn/1},
{hide_sensitive_log_data, false},
{hosts_alias, []},
{host_config, []},
{include_config_file, []},
{language, <<"en">>},
@@ -613,7 +632,7 @@ options() ->
{modules, []},
{negotiation_timeout, timer:seconds(120)},
{net_ticktime, timer:seconds(60)},
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
{sql_schema_multihost, ?USE_MULTIHOST_SQL_SCHEMA_DEFAULT},
{update_sql_schema, true},
{update_sql_schema_timeout, timer:minutes(5)},
{oauth_access, none},
@@ -652,6 +671,10 @@ options() ->
{redis_server, "localhost"},
{registration_timeout, timer:seconds(600)},
{resource_conflict, acceptnew},
{rest_proxy, <<>>},
{rest_proxy_port, 0},
{rest_proxy_username, ""},
{rest_proxy_password, ""},
{router_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
{router_cache_missed,
@@ -754,6 +777,7 @@ globals() ->
ext_api_path_oauth,
fqdn,
hosts,
hosts_alias,
host_config,
install_contrib_modules,
listen,
@@ -765,7 +789,6 @@ globals() ->
log_modules_fully,
negotiation_timeout,
net_ticktime,
new_sql_schema,
update_sql_schema,
node_start,
oauth_cache_life_time,
@@ -798,6 +821,7 @@ globals() ->
sm_cache_life_time,
sm_cache_missed,
sm_cache_size,
sql_schema_multihost,
trusted_proxies,
validate_stream,
version,
+71 -10
View File
@@ -109,14 +109,20 @@ doc() ->
desc =>
?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'.")}},
"Some components can be configured with specific toplevel options "
"like _`oauth_db_type`_. "
"Many modules can be configured with specific module options, "
"usually named `db_type`. "
"The default value is 'mnesia'.")}},
{default_ram_db,
#{value => "mnesia | redis | sql",
desc =>
?T("Default volatile (in-memory) storage for ejabberd. "
"Modules and other components (e.g. session management) "
"may have its own value. The default value is 'mnesia'.")}},
"Some components can be configured with specific toplevel options "
"like _`router_db_type`_ and _`sm_db_type`_. "
"Some modules can be configured with specific module options, "
"usually named `ram_db_type`. "
"The default value is 'mnesia'.")}},
{queue_type,
#{value => "ram | file",
desc =>
@@ -392,6 +398,17 @@ doc() ->
"SASL PLAIN and SASL SCRAM-SHA-1/256/512(-PLUS). The SCRAM variant "
"depends on the _`auth_scram_hash`_ option."), "",
?T("The default value is 'plain'."), ""]}},
{auth_password_types_hidden_in_sasl1,
#{value => "[plain | scram_sha1 | scram_sha256 | scram_sha512]",
note => "added in 25.07",
desc =>
?T("List of password types that should not be offered in SASL1 authenticatication. "
"Because SASL1, unlike SASL2, can't have list of available mechanisms tailored to "
"individual user, it's possible that offered mechanisms will not be compatible "
"with stored password, especially if new password type was added recently. "
"This option allows disabling offering some mechanisms in SASL1, to a time until new "
"password type will be available for all users.")}},
{auth_scram_hash,
#{value => "sha | sha256 | sha512",
desc =>
@@ -712,6 +729,23 @@ doc() ->
" domain.tld:",
" auth_method:",
" - ldap"]}},
{hosts_alias,
#{value => "{Alias: Host}",
desc =>
?T("Define aliases for existing vhosts managed by ejabberd. "
"An alias may be a regexp expression. "
"This option is only consulted by the 'ejabberd_http' listener."),
note => "added in 25.07",
example =>
["hosts:",
" - domain.tld",
" - example.org",
"",
"hosts_alias:",
" xmpp.domain.tld: domain.tld",
" jabber.domain.tld: domain.tld",
" mytest.net: example.org",
" \"exa*\": example.org"]}},
{include_config_file,
#{value => "[Filename, ...\\] | {Filename: Options}",
desc =>
@@ -958,23 +992,30 @@ doc() ->
"bugs. Usually leaving default value of this is option is best, "
"tweak it only if you know what you are doing. "
"The default value is '1 minute'.")}},
{new_sql_schema,
{sql_schema_multihost,
#{value => "true | false",
note => "renamed in 25.10",
desc =>
{?T("Whether to use the "
"_`database.md#default-and-new-schemas|new SQL schema`_. "
"_`database.md#default-and-new-schemas|multihost SQL schema`_. "
"All schemas are located "
"at <https://github.com/processone/ejabberd/tree/~s/sql>. "
"There are two schemas available. The default legacy schema "
"There are two schemas available. The legacy 'singlehost' schema "
"stores one XMPP domain into one ejabberd database. "
"The 'new' schema can handle several XMPP domains in a "
"single ejabberd database. Using this 'new' schema is best when "
"The 'multihost' schema can handle several XMPP domains in a "
"single ejabberd database. The 'multihost' schema is preferable when "
"serving several XMPP domains and/or changing domains from "
"time to time. This avoid need to manage several databases and "
"handle complex configuration changes. The default depends on "
"configuration flag '--enable-new-sql-schema' which is set "
"configuration flag '--enable-sql-schema-multihost' which is set "
"at compile time."),
[binary:part(ejabberd_config:version(), {0,5})]}}},
{new_sql_schema,
#{value => "true | false",
note => "obsoleted in 25.10",
desc =>
?T("This option was renamed to _`sql_schema_multihost`_ in ejabberd 25.10. "
"Please update your configuration to use the new option name")}},
{update_sql_schema,
#{value => "true | false",
note => "updated in 24.06",
@@ -1194,6 +1235,26 @@ doc() ->
"uses old Jabber Non-SASL authentication (XEP-0078), "
"then this option is not respected, and the action performed "
"is 'closeold'.")}},
{rest_proxy,
#{value => "Host",
note => "added in 25.07",
desc => ?T("Address of a HTTP Connect proxy used by modules issuing rest calls "
"(like ejabberd_oauth_rest)")}},
{rest_proxy_port,
#{value => "1..65535",
note => "added in 25.07",
desc => ?T("Port of a HTTP Connect proxy used by modules issuing rest calls "
"(like ejabberd_oauth_rest)")}},
{rest_proxy_username,
#{value => "string()",
note => "added in 25.07",
desc => ?T("Username used to authenticate to HTTP Connect proxy used by modules issuing rest calls "
"(like ejabberd_oauth_rest)")}},
{rest_proxy_password,
#{value => "string()",
note => "added in 25.07",
desc => ?T("Password used to authenticate to HTTP Connect proxy used by modules issuing rest calls "
"(like ejabberd_oauth_rest)")}},
{router_cache_life_time,
#{value => "timeout()",
desc =>
+5 -4
View File
@@ -48,7 +48,7 @@
-define(CALL_TIMEOUT, 60*1000). %% 60 seconds
-include("logger.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state, {connection :: pid() | undefined,
num :: pos_integer(),
@@ -114,9 +114,10 @@ multi(F) ->
{error, _} = Err -> Err;
Result -> get_result(Result)
end
catch ?EX_RULE(E, R, St) ->
erlang:erase(?TR_STACK),
erlang:raise(E, R, ?EX_STACK(St))
catch
E:R:St ->
erlang:erase(?TR_STACK),
erlang:raise(E, R, St)
end;
_ ->
erlang:error(nested_transaction)
+7 -6
View File
@@ -70,7 +70,8 @@
-include("logger.hrl").
-include("ejabberd_router.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_stacktrace.hrl").
-callback init() -> any().
-callback register_route(binary(), binary(), local_hint(),
@@ -90,11 +91,11 @@ start_link() ->
-spec route(stanza()) -> ok.
route(Packet) ->
try do_route(Packet)
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
end.
-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
+11 -10
View File
@@ -32,7 +32,8 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("ejabberd_router.hrl").
-include("ejabberd_stacktrace.hrl").
%%%===================================================================
%%% API
@@ -141,13 +142,13 @@ row_to_route(Domain, {ServerHost, NodeS, PidS, LocalHintS} = Row) ->
local_hint = dec_local_hint(LocalHintS)}]
catch _:{bad_node, _} ->
[];
?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to decode row from 'route' table:~n"
"** Row = ~p~n"
"** Domain = ~ts~n"
"** ~ts",
[Row, Domain,
misc:format_exception(2, Class, Reason, StackTrace)]),
[]
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to decode row from 'route' table:~n"
"** Row = ~p~n"
"** Domain = ~ts~n"
"** ~ts",
[Row,
Domain,
misc:format_exception(2, Class, Reason, StackTrace)]),
[]
end.
+6 -6
View File
@@ -53,7 +53,7 @@
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_commands.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd_stacktrace.hrl").
-include("translate.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
@@ -249,11 +249,11 @@ handle_info({mnesia_system_event, {mnesia_up, Node}}, State) ->
{noreply, State};
handle_info({route, Packet}, State) ->
try route(Packet)
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
end,
{noreply, State};
handle_info({'DOWN', _Ref, process, Pid, _Reason}, State) ->
+1 -1
View File
@@ -138,7 +138,7 @@ process_closed(#{server := LServer} = State, Reason) ->
%%% xmpp_stream_in callbacks
%%%===================================================================
tls_options(#{tls_options := TLSOpts, lserver := LServer, server_host := ServerHost}) ->
ejabberd_s2s:tls_options(LServer, ServerHost, TLSOpts).
[override_cert_purpose | ejabberd_s2s:tls_options(LServer, ServerHost, TLSOpts)].
tls_required(#{server_host := ServerHost}) ->
ejabberd_s2s:tls_required(ServerHost).
+1
View File
@@ -21,6 +21,7 @@
-export([start_link/0, new/1, update/2, match/3, get_max_rate/1]).
-export([reload_from_config/0]).
-export([read_shaper_rules/2]).
-export([validator/1, shaper_rules_validator/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+12 -9
View File
@@ -92,7 +92,7 @@
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
-include("ejabberd_stacktrace.hrl").
-include("translate.hrl").
-callback init() -> ok | {error, any()}.
@@ -131,13 +131,14 @@ stop() ->
%% @doc route arbitrary term to c2s process(es)
route(To, Term) ->
try do_route(To, Term), ok
catch ?EX_RULE(E, R, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route term to ~ts:~n"
"** Term = ~p~n"
"** ~ts",
[jid:encode(To), Term,
misc:format_exception(2, E, R, StackTrace)])
catch
E:R:StackTrace ->
?ERROR_MSG("Failed to route term to ~ts:~n"
"** Term = ~p~n"
"** ~ts",
[jid:encode(To),
Term,
misc:format_exception(2, E, R, StackTrace)])
end.
-spec route(stanza()) -> ok.
@@ -481,8 +482,10 @@ c2s_handle_info(#{lang := Lang, bind2_session_id := {Tag, _}} = State,
{stop, ejabberd_c2s:send(State1, Err)};
c2s_handle_info(State, {replaced_with_bind_tag, _}) ->
State;
c2s_handle_info(#{lang := Lang} = State, kick) ->
c2s_handle_info(#{lang := Lang, jid := JID} = State, kick) ->
Err = xmpp:serr_policy_violation(?T("has been kicked"), Lang),
ejabberd_hooks:run(sm_kick_user, JID#jid.lserver,
[JID#jid.luser, JID#jid.lserver]),
{stop, ejabberd_c2s:send(State, Err)};
c2s_handle_info(#{lang := Lang} = State, {exit, Reason}) ->
Err = xmpp:serr_conflict(Reason, Lang),
+1 -1
View File
@@ -109,7 +109,7 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
delete_session(#session{usr = {_, LServer, _}, sid = {Now, Pid}}) ->
TS = now_to_timestamp(Now),
PidS = list_to_binary(erlang:pid_to_list(Pid)),
PidS = misc:encode_pid(Pid),
case ejabberd_sql:sql_query(
LServer,
?SQL("delete from sm where usec=%(TS)d and pid=%(PidS)s")) of
+27 -22
View File
@@ -41,6 +41,7 @@
abort/1,
restart/1,
use_new_schema/0,
use_multihost_schema/0,
sql_query_to_iolist/1,
sql_query_to_iolist/2,
escape/1,
@@ -70,6 +71,8 @@
-export([connecting/2, connecting/3,
session_established/2, session_established/3]).
-deprecated({use_new_schema, 0}).
-ifdef(OTP_BELOW_28).
-ifdef(OTP_BELOW_26).
%% OTP 25 or lower
@@ -89,7 +92,7 @@
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state,
{db_ref :: undefined | db_ref_pid() | odbc_connection_reference(),
@@ -353,8 +356,11 @@ sqlite_file(Host) ->
binary_to_list(File)
end.
use_multihost_schema() ->
ejabberd_option:sql_schema_multihost().
use_new_schema() ->
ejabberd_option:new_sql_schema().
use_multihost_schema().
-spec get_worker(binary()) -> atom().
get_worker(Host) ->
@@ -616,19 +622,20 @@ outer_transaction(F, NRestarts, _Reason) ->
{atomic, Res}
end
catch
?EX_RULE(throw, {aborted, Reason}, _) when NRestarts > 0 ->
maybe_restart_transaction(F, NRestarts, Reason, true);
?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 ->
StackTrace = ?EX_STACK(Stack),
?ERROR_MSG("SQL transaction restarts exceeded~n** "
"Restarts: ~p~n** Last abort reason: "
"~p~n** Stacktrace: ~p~n** When State "
"== ~p",
[?MAX_TRANSACTION_RESTARTS, Reason,
StackTrace, get(?STATE_KEY)]),
maybe_restart_transaction(F, NRestarts, Reason, true);
?EX_RULE(_, Reason, _) ->
maybe_restart_transaction(F, 0, Reason, true)
throw:{aborted, Reason}:_ when NRestarts > 0 ->
maybe_restart_transaction(F, NRestarts, Reason, true);
throw:{aborted, Reason}:StackTrace when NRestarts =:= 0 ->
?ERROR_MSG("SQL transaction restarts exceeded~n** "
"Restarts: ~p~n** Last abort reason: "
"~p~n** Stacktrace: ~p~n** When State "
"== ~p",
[?MAX_TRANSACTION_RESTARTS,
Reason,
StackTrace,
get(?STATE_KEY)]),
maybe_restart_transaction(F, NRestarts, Reason, true);
_:Reason:_ ->
maybe_restart_transaction(F, 0, Reason, true)
end
end.
@@ -742,10 +749,9 @@ sql_query_internal(#sql_query{} = Query) ->
{error, <<"terminated unexpectedly">>};
exit:{shutdown, _} ->
{error, <<"shutdown">>};
?EX_RULE(Class, Reason, Stack) ->
StackTrace = ?EX_STACK(Stack),
Class:Reason:StackTrace ->
?ERROR_MSG("Internal error while processing SQL query:~n** ~ts",
[misc:format_exception(2, Class, Reason, StackTrace)]),
[misc:format_exception(2, Class, Reason, StackTrace)]),
{error, <<"internal error">>}
end,
check_error(Res, Query);
@@ -935,12 +941,11 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) ->
try
[(SQLQuery#sql_query.format_res)(Row)]
catch
?EX_RULE(Class, Reason, Stack) ->
StackTrace = ?EX_STACK(Stack),
Class:Reason:StackTrace ->
?ERROR_MSG("Error while processing SQL query result:~n"
"** Row: ~p~n** ~ts",
[Row,
misc:format_exception(2, Class, Reason, StackTrace)]),
misc:format_exception(2, Class, Reason, StackTrace)]),
[]
end
end, Rows),
@@ -1376,7 +1381,7 @@ write_file_if_new(File, Payload) ->
tmp_dir() ->
case os:type() of
{win32, _} -> filename:join([os:getenv("HOME"), "conf"]);
{win32, _} -> filename:join([misc:get_home(), "conf"]);
_ -> filename:join(["/tmp", "ejabberd"])
end.
+6 -6
View File
@@ -40,7 +40,7 @@
res_pos = 0,
server_host_used = false,
used_vars = [],
use_new_schema,
use_multihost_schema,
need_timestamp_pass = false,
need_array_pass = false,
has_list = false}).
@@ -245,13 +245,13 @@ transform_insert(Form, TableArg, FieldsArg) ->
parse(S, Loc, UseNewSchema) ->
parse1(S, [],
#state{loc = Loc,
use_new_schema = UseNewSchema}).
use_multihost_schema = UseNewSchema}).
parse(S, ParamPos, Loc, UseNewSchema) ->
parse1(S, [],
#state{loc = Loc,
param_pos = ParamPos,
use_new_schema = UseNewSchema}).
use_multihost_schema = UseNewSchema}).
parse1([], Acc, State) ->
State1 = append_string(lists:reverse(Acc), State),
@@ -300,7 +300,7 @@ parse1([$%, $( | S], Acc, State) ->
State3 =
State2#state{server_host_used = {true, Name},
used_vars = [Name | State2#state.used_vars]},
case State#state.use_new_schema of
case State#state.use_multihost_schema of
true ->
Convert =
erl_syntax:application(
@@ -469,7 +469,7 @@ make_sql_query(State) ->
make_sql_query(State, unknown).
make_sql_query(State, Type) ->
Hash = erlang:phash2(State#state{loc = undefined, use_new_schema = true}),
Hash = erlang:phash2(State#state{loc = undefined, use_multihost_schema = true}),
SHash = <<"Q", (integer_to_binary(Hash))/binary>>,
Query = pack_query(State#state.'query'),
Flags = case State#state.has_list of true -> 1; _ -> 0 end,
@@ -938,7 +938,7 @@ make_schema_check(New, Old) ->
erl_syntax:case_expr(
erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(use_new_schema),
erl_syntax:atom(use_multihost_schema),
[]),
[erl_syntax:clause(
[erl_syntax:abstract(true)],
+10 -10
View File
@@ -49,7 +49,7 @@ start(Host) ->
#sql_schema_info{
db_type = DBType,
db_version = DBVersion,
new_schema = ejabberd_sql:use_new_schema()}
multihost_schema = ejabberd_sql:use_multihost_schema()}
end),
Table = filter_table_sh(SchemaInfo, schema_table()),
Res = create_table(Host, SchemaInfo, Table),
@@ -268,7 +268,7 @@ table_exists(Host, Table) ->
end).
filter_table_sh(SchemaInfo, Table) ->
case {SchemaInfo#sql_schema_info.new_schema, Table#sql_table.name} of
case {SchemaInfo#sql_schema_info.multihost_schema, Table#sql_table.name} of
{true, _} ->
Table;
{_, <<"route">>} ->
@@ -407,7 +407,7 @@ get_current_version(Host, Module, Schemas) ->
sqlite_table_copy_t(SchemaInfo, Table) ->
TableName = Table#sql_table.name,
NewTableName = <<"new_", TableName/binary>>,
NewTableName = <<"multihost_", TableName/binary>>,
NewTable = Table#sql_table{name = NewTableName},
create_table_t(SchemaInfo, NewTable),
Columns = lists:join(<<",">>,
@@ -777,7 +777,7 @@ should_update_schema(Host) ->
end,
case ejabberd_option:update_sql_schema() andalso SupportedDB of
true ->
case ejabberd_sql:use_new_schema() of
case ejabberd_sql:use_multihost_schema() of
true ->
lists:member(sql, ejabberd_option:auth_method(Host));
false ->
@@ -850,7 +850,7 @@ update_schema(Host, Module, RawSchemas) ->
#sql_schema_info{
db_type = DBType,
db_version = DBVersion,
new_schema = ejabberd_sql:use_new_schema()}
multihost_schema = ejabberd_sql:use_multihost_schema()}
end),
Schemas = preprocess_schemas(SchemaInfo, RawSchemas),
Version = get_current_version(Host, Module, Schemas),
@@ -954,7 +954,7 @@ do_update_schema(Host, Module, SchemaInfo, Schema) ->
end;
({create_index, TableName, Columns1}) ->
Columns =
case ejabberd_sql:use_new_schema() of
case ejabberd_sql:use_multihost_schema() of
true ->
Columns1;
false ->
@@ -1005,7 +1005,7 @@ do_update_schema(Host, Module, SchemaInfo, Schema) ->
end;
({update_primary_key, TableName, Columns1}) ->
Columns =
case ejabberd_sql:use_new_schema() of
case ejabberd_sql:use_multihost_schema() of
true ->
Columns1;
false ->
@@ -1071,7 +1071,7 @@ do_update_schema(Host, Module, SchemaInfo, Schema) ->
end;
({drop_index, TableName, Columns1}) ->
Columns =
case ejabberd_sql:use_new_schema() of
case ejabberd_sql:use_multihost_schema() of
true ->
Columns1;
false ->
@@ -1160,7 +1160,7 @@ print_schema(SDBType, SDBVersion, SNewSchema) ->
"false" -> false;
"true" -> true;
_ ->
io:format("new_schema must be one of the following: "
io:format("multihost_schema must be one of the following: "
"'0', '1', 'false', 'true'~n"),
error
end,
@@ -1172,7 +1172,7 @@ print_schema(SDBType, SDBVersion, SNewSchema) ->
#sql_schema_info{
db_type = DBType,
db_version = DBVersion,
new_schema = NewSchema},
multihost_schema = NewSchema},
Mods = ejabberd_config:beams(all),
lists:foreach(
fun(Mod) ->
+1 -1
View File
@@ -196,7 +196,7 @@ check_sqlite_db(Host) ->
create_sqlite_tables(DB) ->
SqlDir = misc:sql_dir(),
Filename = case ejabberd_sql:use_new_schema() of
Filename = case ejabberd_sql:use_multihost_schema() of
true -> "lite.new.sql";
false -> "lite.sql"
end,
+24 -7
View File
@@ -1660,9 +1660,19 @@ make_login_items(#request{us = {Username, Host}} = R, Level) ->
_ ->
UserEl
end,
MenuPost =
case ejabberd_hooks:run_fold(webadmin_menu_system_post, [], [R]) of
[] ->
[];
PostElements ->
[{xmlel,
<<"div">>,
[{<<"id">>, <<"navitemlogin">>}],
[?XE(<<"ul">>, PostElements)]}]
end,
[{xmlel,
<<"li">>,
[],
[{<<"id">>, <<"navitemlogin-start">>}],
[{xmlel,
<<"div">>,
[{<<"id">>, <<"navitemlogin">>}],
@@ -1673,10 +1683,12 @@ make_login_items(#request{us = {Username, Host}} = R, Level) ->
R,
[{<<"sentence">>, misc:atom_to_binary(node())}],
[{only, value},
{result_links, [{sentence, node, Level, <<"">>}]}])]),
?LI([?C(unicode:characters_to_binary("📤")),
?AC(<<(binary:copy(<<"../">>, Level))/binary, "logout/">>,
<<"Logout">>)])])]}]}].
{result_links, [{sentence, node, Level, <<"">>}]}])])]
++ ejabberd_hooks:run_fold(webadmin_menu_system_inside, [], [R])
++ [?LI([?C(unicode:characters_to_binary("📤")),
?AC(<<(binary:copy(<<"../">>, Level))/binary, "logout/">>,
<<"Logout">>)])])]}]
++ MenuPost}].
%%%==================================
@@ -1925,7 +1937,9 @@ lists_zipwith3(Combine, [E1 | List1], [E2 | List2], [], DefX, DefY, DefZ, Res) -
E123 = Combine(E1, E2, DefZ),
lists_zipwith3(Combine, List1, List2, [], DefX, DefY, DefZ, [E123 | Res]).
-else.
-endif.
-ifndef(OTP_BELOW_26).
lists_zipwith3(Combine, List1, List2, List3, How) ->
lists:zipwith3(Combine, List1, List2, List3, How).
@@ -2183,7 +2197,10 @@ make_command_result_element(_ArgumentsUsed,
|| {ElementName, _ElementFormat} <- TupleElements])]),
?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XC(<<"td">>, format_result(V, {ElementName, ElementFormat}))
[?XE(<<"td">>,
[?XAC(<<"span">>,
[{<<"style">>, <<"white-space: pre-wrap;">>}],
format_result(V, {ElementName, ElementFormat}))])
|| {V, {ElementName, ElementFormat}}
<- lists:zip(tuple_to_list(Values), TupleElements)])])]);
make_command_result_element(ArgumentsUsed,
+46 -6
View File
@@ -49,6 +49,7 @@
-include("logger.hrl").
-include("translate.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include_lib("stdlib/include/zip.hrl").
-define(REPOS, "git@github.com:processone/ejabberd-contrib.git").
@@ -148,7 +149,8 @@ get_commands_spec() ->
#ejabberd_commands{name = module_upgrade,
tags = [modules],
desc = "Upgrade the running code of an installed module",
longdesc = "In practice, this uninstalls and installs the module",
longdesc = "In practice, this uninstalls, cleans the compiled files, and installs the module",
note = "improved in 25.07",
module = ?MODULE, function = upgrade,
args_desc = ["Module name"],
args_example = [<<"mod_rest">>],
@@ -241,6 +243,7 @@ install(Package, Config) when is_binary(Package) ->
ok ->
code:add_pathsz([module_ebin_dir(Module)|module_deps_dirs(Module)]),
ejabberd_config_reload(Config),
maybe_print_module_status(Module),
copy_commit_json(Package, Attrs),
ModuleRuntime = get_runtime_module_name(Module),
case erlang:function_exported(ModuleRuntime, post_install, 0) of
@@ -261,6 +264,14 @@ ejabberd_config_reload(Config) when is_list(Config) ->
ejabberd_config_reload(undefined) ->
ejabberd_config:reload().
maybe_print_module_status(Module) ->
case get_module_status_el(Module) of
[_, {xmlcdata, String}] ->
io:format("~ts~n", [String]);
_ ->
ok
end.
uninstall(Module) when is_atom(Module) ->
uninstall(misc:atom_to_binary(Module));
uninstall(Package) when is_binary(Package) ->
@@ -289,8 +300,19 @@ upgrade(Module) when is_atom(Module) ->
upgrade(misc:atom_to_binary(Module));
upgrade(Package) when is_binary(Package) ->
uninstall(Package),
clean(Package),
install(Package).
clean(Package) ->
Spec = [S || {Mod, S} <- available(), misc:atom_to_binary(Mod)==Package],
case Spec of
[] ->
{error, not_available};
[Attrs] ->
Path = proplists:get_value(path, Attrs),
[delete_path(SubPath) || SubPath <- filelib:wildcard(Path++"/{deps,ebin}")]
end.
add_sources(Path) when is_list(Path) ->
add_sources(iolist_to_binary(module_name(Path)), Path).
add_sources(_, "") ->
@@ -371,8 +393,6 @@ geturl(Url) ->
{error, Reason}
end.
getenv(Env) ->
getenv(Env, "").
getenv(Env, Default) ->
case os:getenv(Env) of
false -> Default;
@@ -389,6 +409,23 @@ extract(tar, {ok, _, Body}, DestDir) ->
extract(_, {error, Reason}, _) ->
{error, Reason};
extract(zip, Zip, DestDir) ->
{ok, DirList} = zip:list_dir(Zip),
Offending =
lists:filter(fun (#zip_comment{}) ->
false;
(#zip_file{name = Filename}) ->
absolute == filename:pathtype(Filename)
end,
DirList),
case Offending of
[] ->
extract(zip_verified, Zip, DestDir);
_ ->
Filenames = [F#zip_file.name || F <- Offending],
?ERROR_MSG("The zip file includes absolute file paths:~n ~p", [Filenames]),
{error, {zip_absolute_path, Filenames}}
end;
extract(zip_verified, Zip, DestDir) ->
case zip:extract(Zip, [{cwd, DestDir}]) of
{ok, _} -> ok;
Error -> Error
@@ -453,7 +490,7 @@ delete_path(Path, Package) ->
delete_path(filename:join(filename:dirname(Path), Package)).
modules_dir() ->
DefaultDir = filename:join(getenv("HOME"), ".ejabberd-modules"),
DefaultDir = filename:join(misc:get_home(), ".ejabberd-modules"),
getenv("CONTRIB_MODULES_PATH", DefaultDir).
sources_dir() ->
@@ -543,7 +580,7 @@ check_sources(Module) ->
true -> Acc;
false -> [{missing, Name}|Acc]
end
end, HaveSrc, [{is_file, "README.txt"},
end, HaveSrc, [{is_file, "README.md"},
{is_file, "COPYING"},
{is_file, SpecFile}]),
SpecCheck = case consult(SpecFile) of
@@ -641,10 +678,13 @@ maybe_define_lager_macro() ->
end.
compile_options() ->
[verbose, report_errors, report_warnings, debug_info, ?ALL_DEFS]
[verbose, report_errors, report_warnings, debug_info, ?ALL_DEFS,
{feature, maybe_expr, enable}]
++ maybe_define_lager_macro()
++ [{i, filename:join(app_dir(App), "include")}
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
++ [{i, filename:join(app_dir(App), "include")}
|| App <- [p1_xml, p1_xmpp]] % paths used in Debian packages
++ [{i, filename:join(mod_dir(Mod), "include")}
|| Mod <- installed()].
+9 -9
View File
@@ -37,7 +37,7 @@
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("translate.hrl").
-include("ejabberd_stacktrace.hrl").
-type component() :: ejabberd_sm | ejabberd_local.
@@ -111,14 +111,14 @@ process_iq(_Host, Module, Function, IQ) ->
ejabberd_router:route(ResIQ);
ignore ->
ok
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to process iq:~n~ts~n** ~ts",
[xmpp:pp(IQ),
misc:format_exception(2, Class, Reason, StackTrace)]),
Txt = ?T("Module failed to handle the query"),
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
ejabberd_router:route_error(IQ, Err)
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to process iq:~n~ts~n** ~ts",
[xmpp:pp(IQ),
misc:format_exception(2, Class, Reason, StackTrace)]),
Txt = ?T("Module failed to handle the query"),
Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang),
ejabberd_router:route_error(IQ, Err)
end.
-spec process_iq(module(), atom(), iq()) -> ignore | iq().
+70 -24
View File
@@ -27,7 +27,7 @@
-author('alexey@process-one.net').
-export([init/1, start_link/0, start_child/3, start_child/4,
stop_child/1, stop_child/2, stop/0, config_reloaded/0]).
stop_child/1, stop_child/2, prep_stop/0, stop/0, config_reloaded/0]).
-export([start_module/2, stop_module/2, stop_module_keep_config/2,
get_opt/2, set_opt/3, get_opt_hosts/1, is_equal_opt/3,
get_module_opt/3, get_module_opts/2, get_module_opt_hosts/2,
@@ -44,7 +44,7 @@
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("ejabberd_stacktrace.hrl").
-include("ejabberd_commands.hrl").
-record(ejabberd_module,
@@ -76,6 +76,7 @@
-callback start(binary(), opts()) ->
ok | {ok, pid()} |
{ok, [registration()]} | {error, term()}.
-callback prep_stop(binary()) -> any().
-callback stop(binary()) -> any().
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback mod_opt_type(atom()) -> econf:validator().
@@ -86,7 +87,7 @@
example => [string()] | [{binary(), [string()]}]}.
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
-optional_callbacks([mod_opt_type/1, reload/3]).
-optional_callbacks([mod_opt_type/1, reload/3, prep_stop/1]).
-export_type([opts/0]).
-export_type([db_type/0]).
@@ -114,6 +115,10 @@ init([]) ->
{read_concurrency, true}]),
{ok, {{one_for_one, 10, 1}, []}}.
-spec prep_stop() -> ok.
prep_stop() ->
prep_stop_modules().
-spec stop() -> ok.
stop() ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 60),
@@ -182,16 +187,20 @@ start_module(Host, Module, Opts, Order) ->
ets:delete(ejabberd_modules, {Module, Host}),
erlang:error({bad_return, Module, Err})
end
catch ?EX_RULE(Class, Reason, Stack) ->
StackTrace = ?EX_STACK(Stack),
ets:delete(ejabberd_modules, {Module, Host}),
ErrorText = format_module_error(
Module, start, 2,
Opts, Class, Reason,
StackTrace),
?CRITICAL_MSG(ErrorText, []),
maybe_halt_ejabberd(),
erlang:raise(Class, Reason, StackTrace)
catch
Class:Reason:StackTrace ->
ets:delete(ejabberd_modules, {Module, Host}),
ErrorText = format_module_error(
Module,
start,
2,
Opts,
Class,
Reason,
StackTrace),
?CRITICAL_MSG(ErrorText, []),
maybe_halt_ejabberd(),
erlang:raise(Class, Reason, StackTrace)
end.
-spec reload_modules(binary()) -> ok.
@@ -241,14 +250,18 @@ reload_module(Host, Module, NewOpts, OldOpts, Order) ->
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
Err -> erlang:error({bad_return, Module, Err})
end
catch ?EX_RULE(Class, Reason, Stack) ->
StackTrace = ?EX_STACK(Stack),
ErrorText = format_module_error(
Module, reload, 3,
NewOpts, Class, Reason,
StackTrace),
catch
Class:Reason:StackTrace ->
ErrorText = format_module_error(
Module,
reload,
3,
NewOpts,
Class,
Reason,
StackTrace),
?CRITICAL_MSG(ErrorText, []),
erlang:raise(Class, Reason, StackTrace)
erlang:raise(Class, Reason, StackTrace)
end;
false ->
?WARNING_MSG("Module ~ts doesn't support reloading "
@@ -301,6 +314,21 @@ is_app_running(AppName) ->
lists:keymember(AppName, 1,
application:which_applications(Timeout)).
-spec prep_stop_modules() -> ok.
prep_stop_modules() ->
lists:foreach(
fun(Host) ->
prep_stop_modules(Host)
end, ejabberd_option:hosts()).
-spec prep_stop_modules(binary()) -> ok.
prep_stop_modules(Host) ->
Modules = lists:reverse(loaded_modules_with_opts(Host)),
lists:foreach(
fun({Module, _Args}) ->
prep_stop_module_keep_config(Host, Module)
end, Modules).
-spec stop_modules() -> ok.
stop_modules() ->
lists:foreach(
@@ -320,6 +348,23 @@ stop_modules(Host) ->
stop_module(Host, Module) ->
stop_module_keep_config(Host, Module).
-spec prep_stop_module_keep_config(binary(), atom()) -> error | ok.
prep_stop_module_keep_config(Host, Module) ->
?DEBUG("Preparing to stop ~ts at ~ts", [Module, Host]),
try Module:prep_stop(Host) of
_ ->
ok
catch
error:undef:_St ->
ok;
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to prepare stop module ~ts at ~ts:~n** ~ts",
[Module,
Host,
misc:format_exception(2, Class, Reason, StackTrace)]),
error
end.
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
stop_module_keep_config(Host, Module) ->
?DEBUG("Stopping ~ts at ~ts", [Module, Host]),
@@ -335,12 +380,13 @@ stop_module_keep_config(Host, Module) ->
_ ->
ets:delete(ejabberd_modules, {Module, Host}),
ok
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
catch
Class:Reason:StackTrace ->
?ERROR_MSG("Failed to stop module ~ts at ~ts:~n** ~ts",
[Module, Host,
[Module,
Host,
misc:format_exception(2, Class, Reason, StackTrace)]),
error
error
end.
-spec add_registrations(binary(), module(), [registration()]) -> ok.
+69 -44
View File
@@ -36,16 +36,19 @@
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
compile_exprs/2, join_atoms/2, try_read_file/1, get_descr/2,
get_home/0, warn_unset_home/0,
css_dir/0, img_dir/0, js_dir/0, msgs_dir/0, sql_dir/0, lua_dir/0,
read_css/1, read_img/1, read_js/1, read_lua/1,
intersection/2, format_val/1, cancel_timer/1, unique_timestamp/0,
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_with_kv_lists/1,
uri_decode/1,
json_encode/1, json_decode/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]).
semver_to_xxyy/1, logical_processors/0, get_mucsub_event_type/1,
lists_uniq/1]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -70,45 +73,11 @@
-type distance_cache() :: #{{string(), string()} => non_neg_integer()}.
-spec uri_parse(binary()|string()) -> {ok, string(), string(), string(), number(), string(), string()} | {error, term()}.
-ifdef(USE_OLD_HTTP_URI).
uri_parse(URL) when is_binary(URL) ->
uri_parse(binary_to_list(URL));
uri_parse(URL) ->
uri_parse(URL, []).
yconf:parse_uri(URL).
uri_parse(URL, Protocols) when is_binary(URL) ->
uri_parse(binary_to_list(URL), Protocols);
uri_parse(URL, Protocols) ->
case http_uri:parse(URL, [{scheme_defaults, Protocols}]) of
{ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
{ok, atom_to_list(Scheme), UserInfo, Host, Port, Path, Query};
{error, _} = E ->
E
end.
-else.
uri_parse(URL) when is_binary(URL) ->
uri_parse(binary_to_list(URL));
uri_parse(URL) ->
uri_parse(URL, [{http, 80}, {https, 443}]).
uri_parse(URL, Protocols) when is_binary(URL) ->
uri_parse(binary_to_list(URL), Protocols);
uri_parse(URL, Protocols) ->
case uri_string:parse(URL) of
#{scheme := Scheme, host := Host, port := Port, path := Path} = M1 ->
{ok, Scheme, maps:get(userinfo, M1, ""), Host, Port, Path, maps:get(query, M1, "")};
#{scheme := Scheme, host := Host, path := Path} = M2 ->
case lists:keyfind(list_to_atom(Scheme), 1, Protocols) of
{_, Port} ->
{ok, Scheme, maps:get(userinfo, M2, ""), Host, Port, Path, maps:get(query, M2, "")};
_ ->
{error, unknown_protocol}
end;
{error, Atom, _} ->
{error, Atom}
end.
-endif.
yconf:parse_uri(URL, Protocols).
-ifdef(OTP_BELOW_25).
-ifdef(OTP_BELOW_24).
@@ -123,6 +92,26 @@ uri_quote(Data) ->
uri_string:quote(Data).
-endif.
%% @doc Decode a part of the URL and return string()
%% -spec url_decode(binary()) -> bitstring().
-ifdef(OTP_BELOW_24).
uri_decode(Path) -> uri_decode(Path, <<>>).
uri_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) ->
Hex = list_to_integer([Hi, Lo], 16),
if Hex == 0 -> exit(badurl);
true -> ok
end,
uri_decode(Tail, <<Acc/binary, Hex>>);
uri_decode(<<H, T/binary>>, Acc) when H /= 0 ->
uri_decode(T, <<Acc/binary, H>>);
uri_decode(<<>>, Acc) -> Acc.
-else.
uri_decode(Path) ->
uri_string:percent_decode(Path).
-endif.
-ifdef(USE_OLD_CRYPTO_HMAC).
crypto_hmac(Type, Key, Data) -> crypto:hmac(Type, Key, Data).
crypto_hmac(Type, Key, Data, MacL) -> crypto:hmac(Type, Key, Data, MacL).
@@ -132,22 +121,21 @@ 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) ->
json_encode({[]}) ->
%% Jiffy was able to handle this case, but Json library does not
<<"{}">>;
json_encode(Term) ->
iolist_to_binary(json:encode(Term,
fun({Val}, Encoder) when is_list(Val) ->
json:encode_key_value_list(Val, Encoder);
(Val, Encoder) ->
json:encode_value(Val, Encoder)
json:encode_value(Val, Encoder)
end)).
json_encode(Term) ->
iolist_to_binary(json:encode(Term)).
json_decode(Bin) ->
json:decode(Bin).
-endif.
@@ -472,6 +460,25 @@ get_descr(Lang, Text) ->
Copyright = ejabberd_config:get_copyright(),
<<Desc/binary, $\n, Copyright/binary>>.
-spec get_home() -> string().
get_home() ->
case init:get_argument(home) of
{ok, [[Home]]} ->
Home;
error ->
mnesia:system_info(directory)
end.
warn_unset_home() ->
case init:get_argument(home) of
{ok, [[_Home]]} ->
ok;
error ->
?INFO_MSG("The 'HOME' environment variable is not set, "
"ejabberd will use as HOME the Mnesia directory: ~s.",
[mnesia:system_info(directory)])
end.
-spec intersection(list(), list()) -> list().
intersection(L1, L2) ->
lists:filter(
@@ -801,3 +808,21 @@ set_proc_label(_Label) ->
set_proc_label(Label) ->
proc_lib:set_label(Label).
-endif.
-ifdef(OTP_BELOW_25).
-spec lists_uniq([term()]) -> [term()].
lists_uniq(List) ->
lists_uniq_int(List, #{}).
lists_uniq_int([El | Rest], Existing) ->
case maps:is_key(El, Existing) of
true -> lists_uniq_int(Rest, Existing);
_ -> [El | lists_uniq_int(Rest, Existing#{El => true})]
end;
lists_uniq_int([], _) ->
[].
-else.
-spec lists_uniq([term()]) -> [term()].
lists_uniq(List) ->
lists:uniq(List).
-endif.
+4 -2
View File
@@ -252,9 +252,11 @@ mod_options(_Host) ->
mod_doc() ->
#{desc =>
?T("This module implements https://xmpp.org/extensions/xep-0050.html"
[?T("def:ad-hoc command"), "",
?T(": Command that can be executed by an XMPP client using XEP-0050."), "",
?T("This module implements https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. It's an auxiliary module and is "
"only needed by some of the other modules."),
"only needed by some of the other modules.")],
opts =>
[{report_commands_node,
#{value => "true | false",
+42 -13
View File
@@ -90,7 +90,7 @@ depends(_Host, _Opts) ->
mod_doc() ->
#{desc =>
?T("Execute https://docs.ejabberd.im/developer/ejabberd-api/[API Commands] "
?T("Execute (def:API commands) "
"in a XMPP client using "
"https://xmpp.org/extensions/xep-0050.html[XEP-0050: Ad-Hoc Commands]. "
"This module requires _`mod_adhoc`_ (to execute the commands), "
@@ -102,7 +102,7 @@ mod_doc() ->
desc =>
?T("What API version to use. "
"If setting an ejabberd version, it will use the latest API "
"version that was available in that ejabberd version. "
"version that was available in that (def:c2s) ejabberd version. "
"For example, setting '\"24.06\"' in this option implies '2'. "
"The default value is the latest version.")}}],
example =>
@@ -452,7 +452,17 @@ set_form_api_command(From, Host, CommandNameBin, XData, _Lang) ->
Instructions = get_instructions(Def),
%% Arguments
FieldsArgs1 = [Field || Field <- XData#xdata.fields, Field#xdata_field.type /= fixed],
FieldsArgs0 = [Field || Field <- XData#xdata.fields, Field#xdata_field.type /= fixed],
FieldsArgs1 =
lists:map(fun(Arg) ->
case Arg#xdata_field.values of
[_] ->
Arg;
_ ->
Arg#xdata_field{type = 'text-multi'}
end
end,
FieldsArgs0),
{Node, FieldsArgs} =
case lists:keytake(<<"mod_adhoc_api_target_node">>, #xdata_field.var, FieldsArgs1) of
@@ -474,13 +484,14 @@ set_form_api_command(From, Host, CommandNameBin, XData, _Lang) ->
Arguments = api_extract_fields(FieldsArgs, Def#ejabberd_commands.args),
ApiVersion = mod_adhoc_api_opt:default_version(Host),
CallResult =
ejabberd_cluster:call(Node,
mod_http_api,
handle,
[binary_to_existing_atom(CommandNameBin, latin1),
get_caller_info(From),
Arguments,
ApiVersion]),
execute(Def,
[Node,
mod_http_api,
handle,
[binary_to_existing_atom(CommandNameBin, latin1),
get_caller_info(From),
Arguments,
ApiVersion]]),
%% Command result
FieldsResult2 =
@@ -515,6 +526,15 @@ set_form_api_command(From, Host, CommandNameBin, XData, _Lang) ->
instructions = Instructions,
fields = FieldsArgsWithHeads ++ FieldsResultWithHeads}}.
execute(Def, Arguments) ->
case lists:member(async, Def#ejabberd_commands.tags) of
true ->
spawn(ejabberd_cluster, call, Arguments),
{200, 0};
false ->
apply(ejabberd_cluster, call, Arguments)
end.
api_extract_fields(Fields, ArgsDef) ->
lists:map(fun(#xdata_field{values = Values, var = ANameBin}) ->
ArgDef = proplists:get_value(binary_to_existing_atom(ANameBin, latin1), ArgsDef),
@@ -608,15 +628,22 @@ build_fields(NameTypes, Descs, Examples, Policy, Replacements, Required) ->
end,
build_fields2(NameTypes2, Descs2, Examples2, Replacements, Required).
build_fields2([{_ArgName, {list, _ArgNameType}}] = NameTypes,
build_fields2([{_ArgName, {list, ArgNameType}}] = NameTypes,
Descs,
Examples,
_Replacements,
Required) ->
ArgNameType2 =
case ArgNameType of
{jid, _} ->
'jid-multi';
{_, _} ->
'text-multi'
end,
Args = lists_zip3_pad(NameTypes, Descs, Examples),
lists:map(fun({{AName, AType}, ADesc, AExample}) ->
ANameBin = list_to_binary(atom_to_list(AName)),
#xdata_field{type = 'text-multi',
#xdata_field{type = ArgNameType2,
label = ANameBin,
desc = list_to_binary(ADesc),
values = encode(AExample, AType),
@@ -664,7 +691,9 @@ lists_zip3_pad([A | As], Nil, Nil, Xs) when (Nil == none) or (Nil == []) ->
lists_zip3_pad([], Nil, Nil, Xs) when (Nil == none) or (Nil == []) ->
lists:reverse(Xs).
-else.
-endif.
-ifndef(OTP_BELOW_26).
lists_zip3_pad(As, Bs, Cs) ->
lists:zip3(As, Bs, Cs, {pad, {error_missing_args_def, "", ""}}).
+77 -75
View File
@@ -51,6 +51,7 @@
% Accounts
set_password/3, check_password_hash/4, delete_old_users/1,
delete_old_users_vhost/2, check_password/3,
list_banned/1, count_banned/1,
ban_account/3, ban_account_v2/3, get_ban_details/2, unban_account/2,
% vCard
@@ -255,6 +256,28 @@ get_commands_spec() ->
result = {res, rescode},
result_example = ok},
#ejabberd_commands{name = list_banned, tags = [accounts],
desc = "List banned accounts",
longdesc = "The HOST argument can be `all` to query all vhosts.",
note = "added in 25.10",
module = ?MODULE, function = list_banned,
args = [{host, binary}],
args_example = [<<"myserver.com">>],
args_desc = ["Server name"],
result = {banned, {list, {jid, string}}},
result_desc = "The list of accounts that are banned",
result_example = ["attacker@example.com", "user3@example.com"]},
#ejabberd_commands{name = count_banned, tags = [accounts],
desc = "Count number of banned accounts",
longdesc = "The HOST argument can be `all` to query all vhosts.",
note = "added in 25.10",
module = ?MODULE, function = count_banned,
args = [{host, binary}],
args_example = [<<"myserver.com">>],
args_desc = ["Server name"],
result_example = 6,
result_desc = "Number of banned accounts",
result = {banned, integer}},
#ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account: kick sessions and set random password",
longdesc = "This simply sets a random password.",
@@ -268,14 +291,14 @@ get_commands_spec() ->
#ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account",
longdesc = "This command kicks the account sessions, "
"sets a random password, and stores ban details in the "
"account private storage. "
"stores ban details in the account private storage, "
"which blocks login to the account. "
"This command requires _`mod_private`_ to be enabled. "
"Check also _`get_ban_details`_ API "
"and _`unban_account`_ API.",
module = ?MODULE, function = ban_account_v2,
version = 2,
note = "improved in 24.06",
note = "improved in 25.08",
args = [{user, binary}, {host, binary}, {reason, binary}],
args_example = [<<"attacker">>, <<"myserver.com">>, <<"Spaming other users">>],
args_desc = ["User name to ban", "Server name",
@@ -290,7 +313,7 @@ get_commands_spec() ->
note = "added in 24.06",
args = [{user, binary}, {host, binary}],
args_example = [<<"attacker">>, <<"myserver.com">>],
args_desc = ["User name to unban", "Server name"],
args_desc = ["Name of a user to check ban information", "Server name"],
result = {ban_details, {list,
{detail, {tuple, [{name, string},
{value, string}
@@ -301,7 +324,7 @@ get_commands_spec() ->
{"lastdate", "2024-04-22T08:39:12Z"},
{"lastreason", "Connection reset by peer"}]},
#ejabberd_commands{name = unban_account, tags = [accounts],
desc = "Revert the ban from an account: set back the old password",
desc = "Remove the ban from an account",
longdesc = "Check _`ban_account`_ API.",
module = ?MODULE, function = unban_account,
version = 2,
@@ -612,6 +635,7 @@ get_commands_spec() ->
result = {res, rescode}},
#ejabberd_commands{name = add_rosteritem, tags = [roster],
desc = "Add an item to a user's roster (supports ODBC)",
longdesc = "The client will receive a `jabber:iq:roster` IQ notifying them of the added entry.",
module = ?MODULE, function = add_rosteritem,
version = 1,
note = "updated in 24.02",
@@ -630,6 +654,7 @@ get_commands_spec() ->
%%{"", "will add mike@server.com to peter@localhost roster"},
#ejabberd_commands{name = delete_rosteritem, tags = [roster],
desc = "Delete an item from a user's roster (supports ODBC)",
longdesc = "The client will receive a `jabber:iq:roster` IQ notifying them of the removed entry.",
module = ?MODULE, function = delete_rosteritem,
args = [{localuser, binary}, {localhost, binary},
{user, binary}, {host, binary}],
@@ -1151,6 +1176,18 @@ delete_or_not(LUser, LServer, TimeStamp_oldest) ->
%%
%% Ban account v0
-define(NS_BANNED, <<"jabber:ejabberd:banned">>).
list_banned(<<"all">>) ->
lists:flatten([list_banned(Host) || Host <- ejabberd_option:hosts()]);
list_banned(Host) ->
[jid:encode(Jid) || Jid <- mod_private:get_users_with_data(Host, ?NS_BANNED)].
count_banned(<<"all">>) ->
lists:sum([count_banned(Host) || Host <- ejabberd_option:hosts()]);
count_banned(Host) ->
mod_private:count_users_with_data(Host, ?NS_BANNED).
ban_account(User, Host, ReasonText) ->
Reason = prepare_reason(ReasonText),
kick_sessions(User, Host, Reason),
@@ -1158,6 +1195,7 @@ ban_account(User, Host, ReasonText) ->
ok.
kick_sessions(User, Server, Reason) ->
ejabberd_hooks:run(sm_kick_user, Server, [User, Server]),
lists:map(
fun(Resource) ->
kick_this_session(User, Server, Resource, Reason)
@@ -1189,27 +1227,29 @@ prepare_reason(Reason) when is_binary(Reason) ->
%% Ban account v2
ban_account_v2(User, Host, ReasonText) ->
case gen_mod:is_loaded(Host, mod_private) of
false ->
IsPrivateEnabled = gen_mod:is_loaded(Host, mod_private),
Exists = ejabberd_auth:user_exists(User, Host),
IsBanned = is_banned(User, Host),
case {IsPrivateEnabled, Exists, IsBanned} of
{true, true, false} ->
ban_account_v2_b(User, Host, ReasonText);
{false, _, _} ->
mod_private_is_required_but_disabled;
true ->
case is_banned(User, Host) of
true ->
account_was_already_banned;
false ->
ban_account_v2_b(User, Host, ReasonText)
end
{_, false, _} ->
account_does_not_exist;
{_, _, true} ->
account_was_already_banned;
{_, _, _} ->
other_error
end.
ban_account_v2_b(User, Host, ReasonText) ->
Reason = prepare_reason(ReasonText),
Pass = ejabberd_auth:get_password_s(User, Host),
Last = get_last(User, Host),
BanDate = xmpp_util:encode_timestamp(erlang:timestamp()),
Hash = get_hash_value(User, Host),
BanPrivateXml = build_ban_xmlel(Reason, Pass, Last, BanDate, Hash),
BanPrivateXml = build_ban_xmlel(Reason, Last, BanDate, Hash),
ok = private_set2(User, Host, BanPrivateXml),
ok = set_random_password_v2(User, Host),
kick_sessions(User, Host, Reason),
ok.
@@ -1217,41 +1257,21 @@ get_hash_value(User, Host) ->
Cookie = misc:atom_to_binary(erlang:get_cookie()),
misc:term_to_base64(crypto:hash(sha256, <<User/binary, Host/binary, Cookie/binary>>)).
set_random_password_v2(User, Server) ->
NewPass = p1_rand:get_string(),
ok = ejabberd_auth:set_password(User, Server, NewPass).
build_ban_xmlel(Reason, Pass, {LastDate, LastReason}, BanDate, Hash) ->
PassEls = build_pass_els(Pass),
build_ban_xmlel(Reason, {LastDate, LastReason}, BanDate, Hash) ->
#xmlel{name = <<"banned">>,
attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}],
attrs = [{<<"xmlns">>, ?NS_BANNED}],
children = [#xmlel{name = <<"reason">>, attrs = [], children = [{xmlcdata, Reason}]},
#xmlel{name = <<"password">>, attrs = [], children = PassEls},
#xmlel{name = <<"lastdate">>, attrs = [], children = [{xmlcdata, LastDate}]},
#xmlel{name = <<"lastreason">>, attrs = [], children = [{xmlcdata, LastReason}]},
#xmlel{name = <<"bandate">>, attrs = [], children = [{xmlcdata, BanDate}]},
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, Hash}]}
]}.
build_pass_els(Pass) when is_binary(Pass) ->
[{xmlcdata, Pass}];
build_pass_els(#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
hash = Hash,
iterationcount = IterationCount}) ->
[#xmlel{name = <<"storedkey">>, attrs = [], children = [{xmlcdata, StoredKey}]},
#xmlel{name = <<"serverkey">>, attrs = [], children = [{xmlcdata, ServerKey}]},
#xmlel{name = <<"salt">>, attrs = [], children = [{xmlcdata, Salt}]},
#xmlel{name = <<"hash">>, attrs = [], children = [{xmlcdata, misc:atom_to_binary(Hash)}]},
#xmlel{name = <<"iterationcount">>, attrs = [], children = [{xmlcdata, integer_to_binary(IterationCount)}]}
].
%%
%% Get ban details
get_ban_details(User, Host) ->
case private_get2(User, Host, <<"banned">>, <<"jabber:ejabberd:banned">>) of
case private_get2(User, Host, <<"banned">>, ?NS_BANNED) of
[El] ->
get_ban_details(User, Host, El);
[] ->
@@ -1286,45 +1306,25 @@ is_banned(User, Host) ->
%% Unban account
unban_account(User, Host) ->
case gen_mod:is_loaded(Host, mod_private) of
false ->
IsPrivateEnabled = gen_mod:is_loaded(Host, mod_private),
Exists = ejabberd_auth:user_exists(User, Host),
IsBanned = is_banned(User, Host),
case {IsPrivateEnabled, Exists, IsBanned} of
{true, true, true} ->
unban_account2(User, Host);
{false, _, _} ->
mod_private_is_required_but_disabled;
true ->
case is_banned(User, Host) of
false ->
account_was_not_banned;
true ->
unban_account2(User, Host)
end
{_, false, _} ->
account_does_not_exist;
{_, _, false} ->
account_was_not_banned;
{_, _, _} ->
other_error
end.
unban_account2(User, Host) ->
OldPass = get_oldpass(User, Host),
ok = ejabberd_auth:set_password(User, Host, OldPass),
UnBanPrivateXml = build_unban_xmlel(),
private_set2(User, Host, UnBanPrivateXml).
get_oldpass(User, Host) ->
[El] = private_get2(User, Host, <<"banned">>, <<"jabber:ejabberd:banned">>),
Pass = fxml:get_subtag(El, <<"password">>),
get_pass(Pass).
get_pass(#xmlel{children = [{xmlcdata, Pass}]}) ->
Pass;
get_pass(#xmlel{children = ScramEls} = Pass) when is_list(ScramEls) ->
StoredKey = fxml:get_subtag_cdata(Pass, <<"storedkey">>),
ServerKey = fxml:get_subtag_cdata(Pass, <<"serverkey">>),
Salt = fxml:get_subtag_cdata(Pass, <<"salt">>),
Hash = fxml:get_subtag_cdata(Pass, <<"hash">>),
IterationCount = fxml:get_subtag_cdata(Pass, <<"iterationcount">>),
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
hash = binary_to_existing_atom(Hash, latin1),
iterationcount = binary_to_integer(IterationCount)}.
build_unban_xmlel() ->
#xmlel{name = <<"banned">>, attrs = [{<<"xmlns">>, <<"jabber:ejabberd:banned">>}]}.
mod_private:del_data(jid:nodeprep(User), jid:nameprep(Host), ?NS_BANNED),
ok.
%%%
%%% Sessions
@@ -1390,6 +1390,8 @@ get_status_list(Host, Status_required) ->
Fstatus = case Status_required of
<<"all">> ->
fun(_, _) -> true end;
StatusList when is_list(StatusList) ->
fun(A, B) -> lists:member(A, B) end;
_ ->
fun(A, B) -> A == B end
end,
+41 -40
View File
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%%% File : mod_admin_update_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Convert SQL DB to the new format
%%% Purpose : Convert the SQL database from singlehost to multihost
%%% Created : 9 Aug 2017 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@@ -65,7 +65,7 @@ depends(_Host, _Opts) ->
get_commands_spec() ->
[#ejabberd_commands{name = update_sql, tags = [sql],
desc = "Convert MS SQL, MySQL or PostgreSQL DB to the new format",
desc = "Convert SQL database from singlehost to multihost (MS SQL, MySQL, PostgreSQL)",
note = "improved in 23.04",
module = ?MODULE, function = update_sql,
args = [],
@@ -119,18 +119,18 @@ update_sql(Host) ->
end.
check_config() ->
case ejabberd_sql:use_new_schema() of
case ejabberd_sql:use_multihost_schema() of
true -> ok;
false ->
ejabberd_config:set_option(new_sql_schema, true),
io:format('~nNOTE: you must add "new_sql_schema: true" to ejabberd.yml before next restart~n~n', [])
ejabberd_config:set_option(sql_schema_multihost, true),
io:format('~nNOTE: you must add "sql_schema_multihost: true" to ejabberd.yml before next restart~n~n', [])
end.
update_tables(State) ->
case add_sh_column(State, "users") of
true ->
drop_pkey(State, "users"),
add_pkey(State, "users", ["server_host", "username"]),
add_pkey(State, "users", ["server_host", "username", "type"]),
drop_sh_default(State, "users");
false ->
ok
@@ -150,8 +150,8 @@ update_tables(State) ->
drop_index(State, "rosterusers", "i_rosteru_user_jid"),
drop_index(State, "rosterusers", "i_rosteru_username"),
drop_index(State, "rosterusers", "i_rosteru_jid"),
create_unique_index(State, "rosterusers", "i_rosteru_sh_user_jid", ["server_host", "username", "jid"]),
create_index(State, "rosterusers", "i_rosteru_sh_jid", ["server_host", "jid"]),
create_unique_index(State, "rosterusers", "i_rosteru_server_host_user_jid", ["server_host", "username", "jid"]),
create_index(State, "rosterusers", "i_rosteru_server_host_jid", ["server_host", "jid"]),
drop_sh_default(State, "rosterusers");
false ->
ok
@@ -160,7 +160,7 @@ update_tables(State) ->
case add_sh_column(State, "rostergroups") of
true ->
drop_index(State, "rostergroups", "pk_rosterg_user_jid"),
create_index(State, "rostergroups", "i_rosterg_sh_user_jid", ["server_host", "username", "jid"]),
create_index(State, "rostergroups", "i_rosterg_server_host_user_jid", ["server_host", "username", "jid"]),
drop_sh_default(State, "rostergroups");
false ->
ok
@@ -169,7 +169,7 @@ update_tables(State) ->
case add_sh_column(State, "sr_group") of
true ->
drop_index(State, "sr_group", "i_sr_group_name"),
create_unique_index(State, "sr_group", "i_sr_group_sh_name", ["server_host", "name"]),
create_unique_index(State, "sr_group", "i_sr_group_server_host_name", ["server_host", "name"]),
drop_sh_default(State, "sr_group");
false ->
ok
@@ -180,8 +180,8 @@ update_tables(State) ->
drop_index(State, "sr_user", "i_sr_user_jid_grp"),
drop_index(State, "sr_user", "i_sr_user_jid"),
drop_index(State, "sr_user", "i_sr_user_grp"),
create_unique_index(State, "sr_user", "i_sr_user_sh_jid_grp", ["server_host", "jid", "grp"]),
create_index(State, "sr_user", "i_sr_user_sh_grp", ["server_host", "grp"]),
create_unique_index(State, "sr_user", "i_sr_user_server_host_jid_grp", ["server_host", "jid", "grp"]),
create_index(State, "sr_user", "i_sr_user_server_host_grp", ["server_host", "grp"]),
drop_sh_default(State, "sr_user");
false ->
ok
@@ -190,7 +190,7 @@ update_tables(State) ->
case add_sh_column(State, "spool") of
true ->
drop_index(State, "spool", "i_despool"),
create_index(State, "spool", "i_spool_sh_username", ["server_host", "username"]),
create_index(State, "spool", "i_spool_server_host_username", ["server_host", "username"]),
drop_sh_default(State, "spool");
false ->
ok
@@ -205,10 +205,10 @@ update_tables(State) ->
drop_index(State, "archive", "i_bare_peer"),
drop_index(State, "archive", "i_username_peer"),
drop_index(State, "archive", "i_username_bare_peer"),
create_index(State, "archive", "i_archive_sh_username_timestamp", ["server_host", "username", "timestamp"]),
create_index(State, "archive", "i_archive_sh_timestamp", ["server_host", "timestamp"]),
create_index(State, "archive", "i_archive_sh_username_peer", ["server_host", "username", "peer"]),
create_index(State, "archive", "i_archive_sh_username_bare_peer", ["server_host", "username", "bare_peer"]),
create_index(State, "archive", "i_archive_server_host_username_timestamp", ["server_host", "username", "timestamp"]),
create_index(State, "archive", "i_archive_server_host_timestamp", ["server_host", "timestamp"]),
create_index(State, "archive", "i_archive_server_host_username_peer", ["server_host", "username", "peer"]),
create_index(State, "archive", "i_archive_server_host_username_bare_peer", ["server_host", "username", "bare_peer"]),
drop_sh_default(State, "archive");
false ->
ok
@@ -247,17 +247,17 @@ update_tables(State) ->
drop_index(State, "vcard_search", "i_vcard_search_lorgname"),
drop_index(State, "vcard_search", "i_vcard_search_lorgunit"),
add_pkey(State, "vcard_search", ["server_host", "lusername"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lfn", ["server_host", "lfn"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lfamily", ["server_host", "lfamily"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lgiven", ["server_host", "lgiven"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lmiddle", ["server_host", "lmiddle"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lnickname", ["server_host", "lnickname"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lbday", ["server_host", "lbday"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lctry", ["server_host", "lctry"]),
create_index(State, "vcard_search", "i_vcard_search_sh_llocality", ["server_host", "llocality"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lemail", ["server_host", "lemail"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lorgname", ["server_host", "lorgname"]),
create_index(State, "vcard_search", "i_vcard_search_sh_lorgunit", ["server_host", "lorgunit"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lfn", ["server_host", "lfn"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lfamily", ["server_host", "lfamily"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lgiven", ["server_host", "lgiven"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lmiddle", ["server_host", "lmiddle"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lnickname", ["server_host", "lnickname"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lbday", ["server_host", "lbday"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lctry", ["server_host", "lctry"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_llocality", ["server_host", "llocality"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lemail", ["server_host", "lemail"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lorgname", ["server_host", "lorgname"]),
create_index(State, "vcard_search", "i_vcard_search_server_host_lorgunit", ["server_host", "lorgunit"]),
drop_sh_default(State, "vcard_search");
false ->
ok
@@ -276,7 +276,7 @@ update_tables(State) ->
true ->
drop_index(State, "privacy_list", "i_privacy_list_username"),
drop_index(State, "privacy_list", "i_privacy_list_username_name"),
create_unique_index(State, "privacy_list", "i_privacy_list_sh_username_name", ["server_host", "username", "name"]),
create_unique_index(State, "privacy_list", "i_privacy_list_server_host_username_name", ["server_host", "username", "name"]),
drop_sh_default(State, "privacy_list");
false ->
ok
@@ -345,7 +345,7 @@ update_tables(State) ->
drop_index(State, "sm", "i_sm_username"),
drop_pkey(State, "sm"),
add_pkey(State, "sm", ["usec", "pid"]),
create_index(State, "sm", "i_sm_sh_username", ["server_host", "username"]),
create_index(State, "sm", "i_sm_server_host_username", ["server_host", "username"]),
drop_sh_default(State, "sm");
false ->
ok
@@ -356,7 +356,7 @@ update_tables(State) ->
drop_index(State, "push_session", "i_push_usn"),
drop_index(State, "push_session", "i_push_ut"),
create_unique_index(State, "push_session", "i_push_session_susn", ["server_host", "username", "service", "node"]),
create_index(State, "push_session", "i_push_session_sh_username_timestamp", ["server_host", "username", "timestamp"]),
create_index(State, "push_session", "i_push_session_server_host_username_timestamp", ["server_host", "username", "timestamp"]),
drop_sh_default(State, "push_session");
false ->
ok
@@ -443,7 +443,8 @@ drop_pkey(#state{dbtype = mysql} = State, Table) ->
["ALTER TABLE ", Table, " DROP PRIMARY KEY;"]).
add_pkey(#state{dbtype = pgsql} = State, Table, Cols) ->
SCols = string:join(Cols, ", "),
Cols2 = lists:map(fun("type") -> "\"type\""; (V) -> V end, Cols),
SCols = string:join(Cols2, ", "),
sql_query(
State#state.host,
["ALTER TABLE ", Table, " ADD PRIMARY KEY (", SCols, ");"]);
@@ -522,7 +523,7 @@ create_unique_index(#state{dbtype = pgsql} = State, Table, Index, Cols) ->
State#state.host,
["CREATE UNIQUE INDEX ", Index, " ON ", Table, " USING btree (",
SCols, ");"]);
create_unique_index(#state{dbtype = mssql} = State, Table, "i_privacy_list_sh_username_name" = Index, Cols) ->
create_unique_index(#state{dbtype = mssql} = State, Table, "i_privacy_list_server_host_username_name" = Index, Cols) ->
create_index(State, Table, Index, Cols);
create_unique_index(#state{dbtype = mssql} = State, Table, Index, Cols) ->
SCols = string:join(Cols, ", "),
@@ -580,10 +581,10 @@ old_index_name(mssql, "i_sr_user_jid_grp") -> "sr_user_jid_group";
old_index_name(mssql, Index) -> string:substr(Index, 3);
old_index_name(_Type, Index) -> Index.
new_index_name(mssql, "i_rosterg_sh_user_jid") -> "rostergroups_sh_username_jid";
new_index_name(mssql, "i_rosteru_sh_jid") -> "rosterusers_sh_jid";
new_index_name(mssql, "i_rosteru_sh_user_jid") -> "rosterusers_sh_username_jid";
new_index_name(mssql, "i_sr_user_sh_jid_grp") -> "sr_user_sh_jid_group";
new_index_name(mssql, "i_rosterg_server_host_user_jid") -> "rostergroups_server_host_username_jid";
new_index_name(mssql, "i_rosteru_server_host_jid") -> "rosterusers_server_host_jid";
new_index_name(mssql, "i_rosteru_server_host_user_jid") -> "rosterusers_server_host_username_jid";
new_index_name(mssql, "i_sr_user_server_host_jid_grp") -> "sr_user_server_host_jid_group";
new_index_name(mssql, Index) -> string:substr(Index, 3);
new_index_name(_Type, Index) -> Index.
@@ -616,9 +617,9 @@ mysql_keylen(_, "pid") -> "(75)";
mysql_keylen(_, "server_host") -> "(191)";
mysql_keylen(_, "service") -> "(191)";
mysql_keylen(_, "topic") -> "(191)";
mysql_keylen("i_privacy_list_sh_username_name", "username") -> "(75)";
mysql_keylen("i_rosterg_sh_user_jid", "username") -> "(75)";
mysql_keylen("i_rosteru_sh_user_jid", "username") -> "(75)";
mysql_keylen("i_privacy_list_server_host_username_name", "username") -> "(75)";
mysql_keylen("i_rosterg_server_host_user_jid", "username") -> "(75)";
mysql_keylen("i_rosteru_server_host_user_jid", "username") -> "(75)";
mysql_keylen(_, "username") -> "(191)";
mysql_keylen(_, _) -> "".
+216 -14
View File
@@ -23,6 +23,8 @@
%%%
%%%----------------------------------------------------------------------
%%; definitions
%%% Implements a small subset of XEP-0133: Service Administration
%%% Version 1.1 (2005-08-19)
@@ -49,9 +51,24 @@
announce_all_hosts_motd_update/1,
announce_motd_delete/1,
announce_all_hosts_motd_delete/1]).
%% ejabberd_commands
-export([announce_send_all/3,
announce_send_online/3,
get_stored_motd/1,
announce_motd_set_online/3,
announce_motd_update/3,
announce_motd_delete_api/1,
get_commands_spec/0]).
%% WebAdmin
-export([webadmin_menu/3, webadmin_page/3]).
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("mod_announce.hrl").
-include("translate.hrl").
@@ -77,12 +94,28 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
%%====================================================================
%% gen_mod callbacks
%%====================================================================
%%; gen_mod callbacks
start(Host, Opts) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:register_commands(?MODULE, get_commands_spec());
true ->
ok
end,
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 50),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end,
ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
gen_mod:stop_child(?MODULE, Host).
reload(Host, NewOpts, OldOpts) ->
@@ -99,8 +132,8 @@ depends(_Host, _Opts) ->
[{mod_adhoc, hard}].
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%; gen_server callbacks
init([Host|_]) ->
process_flag(trap_exit, true),
Opts = gen_mod:get_module_opts(Host, ?MODULE),
@@ -165,7 +198,9 @@ terminate(_Reason, #state{host = Host}) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Announcing via messages to a custom resource
%%====================================================================
%%; Announcing via messages to a custom resource
-spec announce(stanza()) -> ok | stop.
announce(#message{to = #jid{luser = <<>>} = To} = Packet) ->
Proc = gen_mod:get_module_proc(To#jid.lserver, ?MODULE),
@@ -200,8 +235,12 @@ announce(#message{to = #jid{luser = <<>>} = To} = Packet) ->
announce(_Packet) ->
ok.
%%-------------------------------------------------------------------------
%% Announcing via ad-hoc commands
%%====================================================================
%%; Announcing via ad-hoc commands
%%====================================================================
%%; -- disco identity
-define(INFO_COMMAND(Lang, Node),
[#identity{category = <<"automation">>,
type = <<"command-node">>,
@@ -234,7 +273,8 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
Acc
end.
%%-------------------------------------------------------------------------
%%====================================================================
%%; -- disco features
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
@@ -296,7 +336,9 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
end
end.
%%-------------------------------------------------------------------------
%%====================================================================
%%; -- disco items
-define(NODE_TO_ITEM(Lang, Server, Node),
#disco_item{jid = jid:make(Server),
node = Node,
@@ -374,7 +416,9 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
end
end.
%%-------------------------------------------------------------------------
%%====================================================================
%%; -- announce items
-spec announce_items(empty | {error, stanza_error()} | {result, [disco_item()]},
jid(), jid(), binary()) -> {error, stanza_error()} |
{result, [disco_item()]} |
@@ -413,7 +457,8 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
{result, Items ++ Nodes1 ++ Nodes2}
end.
%%-------------------------------------------------------------------------
%%====================================================================
%%; -- commands
commands_result(Allow, From, To, Request) ->
case Allow of
@@ -500,6 +545,9 @@ vvaluel(Val) ->
_ -> [Val]
end.
%%====================================================================
%%; -- adhoc form
generate_adhoc_form(Lang, Node, ServerHost) ->
LNode = tokenize(Node),
{OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
@@ -623,7 +671,8 @@ get_title(Lang, ?NS_ADMIN_DELETE_MOTD) ->
get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) ->
translate:translate(Lang, ?T("Delete message of the day on all hosts")).
%%-------------------------------------------------------------------------
%%====================================================================
%%; -- ad-hoc commands implementation
announce_all(#message{to = To} = Packet) ->
Local = jid:make(To#jid.server),
@@ -841,6 +890,151 @@ route_forbidden_error(Packet) ->
Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang),
ejabberd_router:route_error(Packet, Err).
%%====================================================================
%%; Announcing via API commands
%% @format-begin
-spec get_commands_spec() -> [ejabberd_commands()].
get_commands_spec() ->
HostAll = "If HOST is `all`, send to all hosts. ",
BodyNew = "You can use ' \\n ' in the message body to write a newline.",
[#ejabberd_commands{name = announce_send_all,
tags = [announce],
desc = "Send announcement to all users",
longdesc = HostAll ++ BodyNew,
module = ?MODULE,
function = announce_send_all,
note = "added in 25.10",
args = [{host, binary}, {subject, binary}, {body, binary}],
result = {res, rescode}},
#ejabberd_commands{name = announce_send_online,
tags = [announce],
desc = "Send announcement to online users",
longdesc = HostAll ++ BodyNew,
module = ?MODULE,
function = announce_send_online,
note = "added in 25.10",
args = [{host, binary}, {subject, binary}, {body, binary}],
result = {res, rescode}},
#ejabberd_commands{name = announce_motd_get,
tags = [announce],
desc = "Get Message Of The Day",
longdesc = BodyNew,
module = ?MODULE,
function = get_stored_motd,
note = "added in 25.10",
args = [{host, binary}],
result = {motd, {tuple, [{subject, string}, {body, string}]}}},
#ejabberd_commands{name = announce_motd_set_online,
tags = [announce],
desc = "Set Message Of The Day and send to online users",
longdesc = HostAll ++ BodyNew,
module = ?MODULE,
function = announce_motd_set_online,
note = "added in 25.10",
args = [{host, binary}, {subject, binary}, {body, binary}],
result = {res, rescode}},
#ejabberd_commands{name = announce_motd_update,
tags = [announce],
desc = "Update Message Of The Day",
longdesc = HostAll ++ BodyNew,
module = ?MODULE,
function = announce_motd_update,
note = "added in 25.10",
args = [{host, binary}, {subject, binary}, {body, binary}],
result = {res, rescode}},
#ejabberd_commands{name = announce_motd_delete,
tags = [announce],
desc = "Delete Message Of The Day",
longdesc = HostAll,
module = ?MODULE,
function = announce_motd_delete_api,
note = "added in 25.10",
args = [{host, binary}],
result = {res, rescode}}].
announce_send_all(<<"all">>, Subject, Body) ->
Host = hd(ejabberd_option:hosts()),
announce_all_hosts_all(make_packet(Host, Body, Subject));
announce_send_all(Host, Subject, Body) ->
announce_all(make_packet(Host, Body, Subject)).
announce_send_online(<<"all">>, Subject, Body) ->
Host = hd(ejabberd_option:hosts()),
announce_all_hosts_online(make_packet(Host, Body, Subject));
announce_send_online(Host, Subject, Body) ->
announce_online(make_packet(Host, Body, Subject)).
announce_motd_set_online(<<"all">>, Subject, Body) ->
Host = hd(ejabberd_option:hosts()),
announce_all_hosts_motd(make_packet(Host, Body, Subject));
announce_motd_set_online(Host, Subject, Body) ->
announce_motd(make_packet(Host, Body, Subject)).
announce_motd_update(<<"all">>, Subject, Body) ->
Host = hd(ejabberd_option:hosts()),
[{ok, _} | _] = announce_all_hosts_motd_update(make_packet(Host, Body, Subject)),
ok;
announce_motd_update(Host, Subject, Body) ->
{ok, _} = announce_motd_update(make_packet(Host, Body, Subject)),
ok.
announce_motd_delete_api(<<"all">>) ->
Host = hd(ejabberd_option:hosts()),
announce_all_hosts_motd_delete(make_packet(Host));
announce_motd_delete_api(Host) ->
announce_motd_delete(make_packet(Host)).
make_packet(Host) ->
From = To = jid:make(Host),
#message{from = From, to = To}.
make_packet(Host, Body, Subject) ->
From = To = jid:make(Host),
Body2 = binary:replace(Body, <<"\\n">>, <<"\n">>, [global]),
#message{from = From,
to = To,
type = headline,
body = xmpp:mk_text(Body2),
subject = xmpp:mk_text(Subject)}.
%%====================================================================
%%; Announcing via WebAdmin
webadmin_menu(Acc, _Host, Lang) ->
[{<<"announce">>, translate:translate(Lang, ?T("Announcements"))} | Acc].
webadmin_page(_,
Host,
#request{us = _US,
path = [<<"announce">>],
lang = Lang} =
R) ->
PageTitle = translate:translate(Lang, ?T("Announcements")),
Head = ?H1GL(PageTitle, <<"modules/#mod_announce">>, <<"mod_announce">>),
Ann = [?X(<<"hr">>),
?XE(<<"blockquote">>,
[make_command(announce_send_all, R, [{<<"host">>, Host}], []),
make_command(announce_send_online, R, [{<<"host">>, Host}], [])])],
SetMotd =
[make_command(announce_motd_set_online, R, [{<<"host">>, Host}], []),
make_command(announce_motd_update, R, [{<<"host">>, Host}], []),
make_command(announce_motd_delete, R, [{<<"host">>, Host}], [{style, danger}])],
GetMotd = [make_command(announce_motd_get, R, [{<<"host">>, Host}], [])],
Motd =
[?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"motd">>}], <<"Message Of The Day">>),
?XE(<<"blockquote">>, GetMotd ++ SetMotd)],
{stop, Head ++ Ann ++ Motd};
webadmin_page(Acc, _, _) ->
Acc.
%% @format-end
%%====================================================================
%%; Cache management
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
@@ -878,7 +1072,10 @@ clean_cache(LServer) ->
?MOTD_CACHE,
fun({_, S}, _) -> S /= LServer end).
%%-------------------------------------------------------------------------
%%====================================================================
%%; ejd2sql callbacks
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
@@ -894,6 +1091,9 @@ import(LServer, {sql, _}, DBType, Tab, List) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Tab, List).
%%====================================================================
%%; Options and Documentation
mod_opt_type(access) ->
econf:acl();
mod_opt_type(db_type) ->
@@ -921,7 +1121,7 @@ mod_doc() ->
"announcements and to set the message of the day (MOTD). "
"Configured users can perform these actions with an XMPP "
"client either using Ad-Hoc Commands or sending messages "
"to specific JIDs."), "",
"to specific JIDs. Equivalent API commands are also available."), "",
?T("NOTE: This module can be resource intensive on large "
"deployments as it may broadcast a lot of messages. This module "
"should be disabled for instances of ejabberd with hundreds of "
@@ -984,3 +1184,5 @@ mod_doc() ->
#{value => "timeout()",
desc =>
?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
%%% vim: set foldmethod=marker foldmarker=%%;,%%=:
+913
View File
@@ -0,0 +1,913 @@
%%%----------------------------------------------------------------------
%%% File : mod_antispam.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Filter spam messages based on sender JID and content
%%% Created : 31 Mar 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2019-2025 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.
%%%
%%%----------------------------------------------------------------------
%%| definitions
-module(mod_antispam).
-author('holger@zedat.fu-berlin.de').
-author('stefan@strigler.de').
-behaviour(gen_server).
-behaviour(gen_mod).
%% gen_mod callbacks.
-export([start/2,
prep_stop/1,
stop/1,
reload/3,
depends/2,
mod_doc/0,
mod_opt_type/1,
mod_options/1]).
%% gen_server callbacks.
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-export([get_rtbl_services_option/1]).
%% ejabberd_commands callbacks.
-export([add_blocked_domain/2,
add_to_spam_filter_cache/2,
drop_from_spam_filter_cache/2,
expire_spam_filter_cache/2,
get_blocked_domains/1,
get_commands_spec/0,
get_spam_filter_cache/1,
reload_spam_filter_files/1,
remove_blocked_domain/2]).
-include("ejabberd_commands.hrl").
-include("logger.hrl").
-include("mod_antispam.hrl").
-include("translate.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-record(state,
{host = <<>> :: binary(),
dump_fd = undefined :: file:io_device() | undefined,
url_set = sets:new() :: url_set(),
jid_set = sets:new() :: jid_set(),
jid_cache = #{} :: map(),
max_cache_size = 0 :: non_neg_integer() | unlimited,
rtbl_host = none :: binary() | none,
rtbl_subscribed = false :: boolean(),
rtbl_retry_timer = undefined :: reference() | undefined,
rtbl_domains_node :: binary(),
blocked_domains = #{} :: #{binary() => any()},
whitelist_domains = #{} :: #{binary() => false}
}).
-type state() :: #state{}.
-define(COMMAND_TIMEOUT, timer:seconds(30)).
-define(DEFAULT_CACHE_SIZE, 10000).
%% @format-begin
%%--------------------------------------------------------------------
%%| gen_mod callbacks
-spec start(binary(), gen_mod:opts()) -> ok | {error, any()}.
start(Host, Opts) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:register_commands(?MODULE, get_commands_spec());
true ->
ok
end,
gen_mod:start_child(?MODULE, Host, Opts).
-spec prep_stop(binary()) -> ok | {error, any()}.
prep_stop(Host) ->
case try_call_by_host(Host, prepare_stop) of
ready_to_stop ->
ok
end.
-spec stop(binary()) -> ok | {error, any()}.
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end,
gen_mod:stop_child(?MODULE, Host).
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(Host, NewOpts, OldOpts) ->
?DEBUG("reloading", []),
Proc = get_proc_name(Host),
gen_server:cast(Proc, {reload, NewOpts, OldOpts}).
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[{mod_pubsub, soft}].
-spec mod_opt_type(atom()) -> econf:validator().
mod_opt_type(access_spam) ->
econf:acl();
mod_opt_type(cache_size) ->
econf:pos_int(unlimited);
mod_opt_type(rtbl_services) ->
econf:list(
econf:either(
econf:binary(),
econf:map(
econf:binary(),
econf:map(
econf:enum([spam_source_domains_node]), econf:binary()))));
mod_opt_type(spam_domains_file) ->
econf:either(
econf:enum([none]), econf:file());
mod_opt_type(spam_dump_file) ->
econf:either(
econf:bool(), econf:file(write));
mod_opt_type(spam_jids_file) ->
econf:either(
econf:enum([none]), econf:file());
mod_opt_type(spam_urls_file) ->
econf:either(
econf:enum([none]), econf:file());
mod_opt_type(whitelist_domains_file) ->
econf:either(
econf:enum([none]), econf:file()).
-spec mod_options(binary()) -> [{rtbl_services, [tuple()]} | {atom(), any()}].
mod_options(_Host) ->
[{access_spam, none},
{cache_size, ?DEFAULT_CACHE_SIZE},
{rtbl_services, []},
{spam_domains_file, none},
{spam_dump_file, false},
{spam_jids_file, none},
{spam_urls_file, none},
{whitelist_domains_file, none}].
mod_doc() ->
#{desc =>
?T("Filter spam messages and subscription requests received from "
"remote servers based on "
"https://xmppbl.org/[Real-Time Block Lists (RTBL)], "
"lists of known spammer JIDs and/or URLs mentioned in spam messages. "
"Traffic classified as spam is rejected with an error "
"(and an '[info]' message is logged) unless the sender "
"is subscribed to the recipient's presence."),
note => "added in 25.07",
opts =>
[{access_spam,
#{value => ?T("Access"),
desc =>
?T("Access rule that controls what accounts may receive spam messages. "
"If the rule returns 'allow' for a given recipient, "
"spam messages aren't rejected for that recipient. "
"The default value is 'none', which means that all recipients "
"are subject to spam filtering verification.")}},
{cache_size,
#{value => "pos_integer()",
desc =>
?T("Maximum number of JIDs that will be cached due to sending spam URLs. "
"If that limit is exceeded, the least recently used "
"entries are removed from the cache. "
"Setting this option to '0' disables the caching feature. "
"Note that separate caches are used for each virtual host, "
" and that the caches aren't distributed across cluster nodes. "
"The default value is '10000'.")}},
{rtbl_services,
#{value => ?T("[Service]"),
example =>
["rtbl_services:",
" - pubsub.server1.localhost:",
" spam_source_domains_node: actual_custom_pubsub_node"],
desc =>
?T("Query a RTBL service to get domains to block, as provided by "
"https://xmppbl.org/[xmppbl.org]. "
"Please note right now this option only supports one service in that list. "
"For blocking spam and abuse on MUC channels, please use _`mod_muc_rtbl`_ for now. "
"If only the host is provided, the default node names will be assumed. "
"If the node name is different than 'spam_source_domains', "
"you can setup the custom node name with the option 'spam_source_domains_node'. "
"The default value is an empty list of services.")}},
{spam_domains_file,
#{value => ?T("none | Path"),
desc =>
?T("Path to a plain text file containing a list of "
"known spam domains, one domain per line. "
"Messages and subscription requests sent from one of the listed domains "
"are classified as spam if sender is not in recipient's roster. "
"This list of domains gets merged with the one retrieved "
"by an RTBL host if any given. "
"Use an absolute path, or the '@CONFIG_PATH@' "
"https://docs.ejabberd.im/admin/configuration/file-format/#predefined-keywords[predefined keyword] "
"if the file is available in the configuration directory. "
"The default value is 'none'.")}},
{spam_dump_file,
#{value => ?T("false | true | Path"),
desc =>
?T("Path to the file to store blocked messages. "
"Use an absolute path, or the '@LOG_PATH@' "
"https://docs.ejabberd.im/admin/configuration/file-format/#predefined-keywords[predefined keyword] "
"to store logs "
"in the same place that the other ejabberd log files. "
"If set to 'false', it doesn't dump stanzas, which is the default. "
"If set to 'true', it stores in '\"@LOG_PATH@/spam_dump_@HOST@.log\"'.")}},
{spam_jids_file,
#{value => ?T("none | Path"),
desc =>
?T("Path to a plain text file containing a list of "
"known spammer JIDs, one JID per line. "
"Messages and subscription requests sent from one of "
"the listed JIDs are classified as spam. "
"Messages containing at least one of the listed JIDs"
"are classified as spam as well. "
"Furthermore, the sender's JID will be cached, "
"so that future traffic originating from that JID will also be classified as spam. "
"Use an absolute path, or the '@CONFIG_PATH@' "
"https://docs.ejabberd.im/admin/configuration/file-format/#predefined-keywords[predefined keyword] "
"if the file is available in the configuration directory. "
"The default value is 'none'.")}},
{spam_urls_file,
#{value => ?T("none | Path"),
desc =>
?T("Path to a plain text file containing a list of "
"URLs known to be mentioned in spam message bodies. "
"Messages containing at least one of the listed URLs are classified as spam. "
"Furthermore, the sender's JID will be cached, "
"so that future traffic originating from that JID will be classified as spam as well. "
"Use an absolute path, or the '@CONFIG_PATH@' "
"https://docs.ejabberd.im/admin/configuration/file-format/#predefined-keywords[predefined keyword] "
"if the file is available in the configuration directory. "
"The default value is 'none'.")}},
{whitelist_domains_file,
#{value => ?T("none | Path"),
desc =>
?T("Path to a file containing a list of "
"domains to whitelist from being blocked, one per line. "
"If either it is in 'spam_domains_file' or more realistically "
"in a domain sent by a RTBL host (see option 'rtbl_services') "
"then this domain will be ignored and stanzas from there won't be blocked. "
"Use an absolute path, or the '@CONFIG_PATH@' "
"https://docs.ejabberd.im/admin/configuration/file-format/#predefined-keywords[predefined keyword] "
"if the file is available in the configuration directory. "
"The default value is 'none'.")}}],
example =>
["modules:",
" mod_antispam:",
" rtbl_services:",
" - xmppbl.org",
" spam_jids_file: \"@CONFIG_PATH@/spam_jids.txt\"",
" spam_dump_file: \"@LOG_PATH@/spam/host-@HOST@.log\""]}.
%%--------------------------------------------------------------------
%%| gen_server callbacks
-spec init(list()) -> {ok, state()} | {stop, term()}.
init([Host, Opts]) ->
process_flag(trap_exit, true),
mod_antispam_files:init_files(Host),
FilesResults = read_files(Host),
#{jid := JIDsSet,
url := URLsSet,
domains := SpamDomainsSet,
whitelist_domains := WhitelistDomains} =
FilesResults,
ejabberd_hooks:add(local_send_to_resource_hook,
Host,
mod_antispam_rtbl,
pubsub_event_handler,
50),
[#rtbl_service{host = RTBLHost, node = RTBLDomainsNode}] = get_rtbl_services_option(Opts),
mod_antispam_filter:init_filtering(Host),
InitState =
#state{host = Host,
jid_set = JIDsSet,
url_set = URLsSet,
dump_fd = mod_antispam_dump:init_dumping(Host),
max_cache_size = gen_mod:get_opt(cache_size, Opts),
blocked_domains = set_to_map(SpamDomainsSet),
whitelist_domains = set_to_map(WhitelistDomains, false),
rtbl_host = RTBLHost,
rtbl_domains_node = RTBLDomainsNode},
mod_antispam_rtbl:request_blocked_domains(RTBLHost, RTBLDomainsNode, Host),
{ok, InitState}.
-spec handle_call(term(), {pid(), term()}, state()) ->
{reply, {spam_filter, term()}, state()} | {noreply, state()}.
handle_call({check_jid, From}, _From, #state{jid_set = JIDsSet} = State) ->
{Result, State1} = filter_jid(From, JIDsSet, State),
{reply, {spam_filter, Result}, State1};
handle_call({check_body, URLs, JIDs, From},
_From,
#state{url_set = URLsSet, jid_set = JIDsSet} = State) ->
{Result1, State1} = filter_body(URLs, URLsSet, From, State),
{Result2, State2} = filter_body(JIDs, JIDsSet, From, State1),
Result =
if Result1 == spam ->
Result1;
true ->
Result2
end,
{reply, {spam_filter, Result}, State2};
handle_call(reload_spam_files, _From, State) ->
{Result, State1} = reload_files(State),
{reply, {spam_filter, Result}, State1};
handle_call({expire_cache, Age}, _From, State) ->
{Result, State1} = expire_cache(Age, State),
{reply, {spam_filter, Result}, State1};
handle_call({add_to_cache, JID}, _From, State) ->
{Result, State1} = add_to_cache(JID, State),
{reply, {spam_filter, Result}, State1};
handle_call({drop_from_cache, JID}, _From, State) ->
{Result, State1} = drop_from_cache(JID, State),
{reply, {spam_filter, Result}, State1};
handle_call(get_cache, _From, #state{jid_cache = Cache} = State) ->
{reply, {spam_filter, maps:to_list(Cache)}, State};
handle_call({add_blocked_domain, Domain},
_From,
#state{blocked_domains = BlockedDomains} = State) ->
BlockedDomains1 = maps:merge(BlockedDomains, #{Domain => true}),
Txt = format("~s added to blocked domains", [Domain]),
{reply, {spam_filter, {ok, Txt}}, State#state{blocked_domains = BlockedDomains1}};
handle_call({remove_blocked_domain, Domain},
_From,
#state{blocked_domains = BlockedDomains} = State) ->
BlockedDomains1 = maps:remove(Domain, BlockedDomains),
Txt = format("~s removed from blocked domains", [Domain]),
{reply, {spam_filter, {ok, Txt}}, State#state{blocked_domains = BlockedDomains1}};
handle_call(get_blocked_domains,
_From,
#state{blocked_domains = BlockedDomains, whitelist_domains = WhitelistDomains} =
State) ->
{reply, {blocked_domains, maps:merge(BlockedDomains, WhitelistDomains)}, State};
handle_call({is_blocked_domain, Domain},
_From,
#state{blocked_domains = BlockedDomains, whitelist_domains = WhitelistDomains} =
State) ->
{reply,
maps:get(Domain, maps:merge(BlockedDomains, WhitelistDomains), false) =/= false,
State};
handle_call(prepare_stop,
_From,
#state{host = Host,
rtbl_host = RTBLHost,
rtbl_domains_node = RTBLDomainsNode} =
State) ->
mod_antispam_rtbl:unsubscribe(RTBLHost, RTBLDomainsNode, Host),
{reply, ready_to_stop, State};
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{noreply, State}.
-spec handle_cast(term(), state()) -> {noreply, state()}.
handle_cast({dump_stanza, XML}, #state{dump_fd = Fd} = State) ->
mod_antispam_dump:write_stanza_dump(Fd, XML),
{noreply, State};
handle_cast(reopen_log, #state{host = Host, dump_fd = Fd} = State) ->
{noreply, State#state{dump_fd = mod_antispam_dump:reopen_dump_file(Host, Fd)}};
handle_cast({reload, NewOpts, OldOpts},
#state{host = Host,
dump_fd = Fd,
rtbl_host = OldRTBLHost,
rtbl_domains_node = OldRTBLDomainsNode,
rtbl_retry_timer = RTBLRetryTimer} =
State) ->
misc:cancel_timer(RTBLRetryTimer),
State1 =
State#state{dump_fd = mod_antispam_dump:reload_dumping(Host, Fd, OldOpts, NewOpts)},
State2 =
case {gen_mod:get_opt(cache_size, OldOpts), gen_mod:get_opt(cache_size, NewOpts)} of
{OldMax, NewMax} when NewMax < OldMax ->
shrink_cache(State1#state{max_cache_size = NewMax});
{OldMax, NewMax} when NewMax > OldMax ->
State1#state{max_cache_size = NewMax};
{_OldMax, _NewMax} ->
State1
end,
ok = mod_antispam_rtbl:unsubscribe(OldRTBLHost, OldRTBLDomainsNode, Host),
{_Result, State3} = reload_files(State2#state{blocked_domains = #{}}),
[#rtbl_service{host = RTBLHost, node = RTBLDomainsNode}] =
get_rtbl_services_option(NewOpts),
ok = mod_antispam_rtbl:request_blocked_domains(RTBLHost, RTBLDomainsNode, Host),
{noreply, State3#state{rtbl_host = RTBLHost, rtbl_domains_node = RTBLDomainsNode}};
handle_cast({update_blocked_domains, NewItems},
#state{blocked_domains = BlockedDomains} = State) ->
{noreply, State#state{blocked_domains = maps:merge(BlockedDomains, NewItems)}};
handle_cast(Request, State) ->
?ERROR_MSG("Got unexpected request from: ~p", [Request]),
{noreply, State}.
-spec handle_info(term(), state()) -> {noreply, state()}.
handle_info({iq_reply, timeout, blocked_domains}, State) ->
?WARNING_MSG("Fetching blocked domains failed: fetch timeout. Retrying in 60 seconds",
[]),
{noreply,
State#state{rtbl_retry_timer =
erlang:send_after(60000, self(), request_blocked_domains)}};
handle_info({iq_reply, #iq{type = error} = IQ, blocked_domains}, State) ->
?WARNING_MSG("Fetching blocked domains failed: ~p. Retrying in 60 seconds",
[xmpp:format_stanza_error(
xmpp:get_error(IQ))]),
{noreply,
State#state{rtbl_retry_timer =
erlang:send_after(60000, self(), request_blocked_domains)}};
handle_info({iq_reply, IQReply, blocked_domains},
#state{blocked_domains = OldBlockedDomains,
rtbl_host = RTBLHost,
rtbl_domains_node = RTBLDomainsNode,
host = Host} =
State) ->
case mod_antispam_rtbl:parse_blocked_domains(IQReply) of
undefined ->
?WARNING_MSG("Fetching initial list failed: invalid result payload", []),
{noreply, State#state{rtbl_retry_timer = undefined}};
NewBlockedDomains ->
ok = mod_antispam_rtbl:subscribe(RTBLHost, RTBLDomainsNode, Host),
{noreply,
State#state{rtbl_retry_timer = undefined,
rtbl_subscribed = true,
blocked_domains = maps:merge(OldBlockedDomains, NewBlockedDomains)}}
end;
handle_info({iq_reply, timeout, subscribe_result}, State) ->
?WARNING_MSG("Subscription error: request timeout", []),
{noreply, State#state{rtbl_subscribed = false}};
handle_info({iq_reply, #iq{type = error} = IQ, subscribe_result}, State) ->
?WARNING_MSG("Subscription error: ~p",
[xmpp:format_stanza_error(
xmpp:get_error(IQ))]),
{noreply, State#state{rtbl_subscribed = false}};
handle_info({iq_reply, IQReply, subscribe_result}, State) ->
?DEBUG("Got subscribe result: ~p", [IQReply]),
{noreply, State#state{rtbl_subscribed = true}};
handle_info({iq_reply, _IQReply, unsubscribe_result}, State) ->
%% FIXME: we should check it's true (of type `result`, not `error`), but at that point, what
%% would we do?
{noreply, State#state{rtbl_subscribed = false}};
handle_info(request_blocked_domains,
#state{host = Host,
rtbl_host = RTBLHost,
rtbl_domains_node = RTBLDomainsNode} =
State) ->
mod_antispam_rtbl:request_blocked_domains(RTBLHost, RTBLDomainsNode, Host),
{noreply, State};
handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
terminate(Reason,
#state{host = Host,
dump_fd = Fd,
rtbl_host = RTBLHost,
rtbl_domains_node = RTBLDomainsNode,
rtbl_retry_timer = RTBLRetryTimer} =
_State) ->
?DEBUG("Stopping spam filter process for ~s: ~p", [Host, Reason]),
misc:cancel_timer(RTBLRetryTimer),
mod_antispam_dump:terminate_dumping(Host, Fd),
mod_antispam_files:terminate_files(Host),
mod_antispam_filter:terminate_filtering(Host),
ejabberd_hooks:delete(local_send_to_resource_hook,
Host,
mod_antispam_rtbl,
pubsub_event_handler,
50),
mod_antispam_rtbl:unsubscribe(RTBLHost, RTBLDomainsNode, Host),
ok.
-spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, #state{host = Host} = State, _Extra) ->
?DEBUG("Updating spam filter process for ~s", [Host]),
{ok, State}.
%%--------------------------------------------------------------------
%%| Internal functions
-spec filter_jid(ljid(), jid_set(), state()) -> {ham | spam, state()}.
filter_jid(From, Set, #state{host = Host} = State) ->
case sets:is_element(From, Set) of
true ->
?DEBUG("Spam JID found: ~s", [jid:encode(From)]),
ejabberd_hooks:run(spam_found, Host, [{jid, From}]),
{spam, State};
false ->
case cache_lookup(From, State) of
{true, State1} ->
?DEBUG("Spam JID found: ~s", [jid:encode(From)]),
ejabberd_hooks:run(spam_found, Host, [{jid, From}]),
{spam, State1};
{false, State1} ->
?DEBUG("JID not listed: ~s", [jid:encode(From)]),
{ham, State1}
end
end.
-spec filter_body({urls, [url()]} | {jids, [ljid()]} | none,
url_set() | jid_set(),
jid(),
state()) ->
{ham | spam, state()}.
filter_body({_, Addrs}, Set, From, #state{host = Host} = State) ->
case lists:any(fun(Addr) -> sets:is_element(Addr, Set) end, Addrs) of
true ->
?DEBUG("Spam addresses found: ~p", [Addrs]),
ejabberd_hooks:run(spam_found, Host, [{body, Addrs}]),
{spam, cache_insert(From, State)};
false ->
?DEBUG("Addresses not listed: ~p", [Addrs]),
{ham, State}
end;
filter_body(none, _Set, _From, State) ->
{ham, State}.
-spec reload_files(state()) -> {ok | {error, binary()}, state()}.
reload_files(#state{host = Host, blocked_domains = BlockedDomains} = State) ->
case read_files(Host) of
#{jid := JIDsSet,
url := URLsSet,
domains := SpamDomainsSet,
whitelist_domains := WhitelistDomains} ->
case sets_equal(JIDsSet, State#state.jid_set) of
true ->
?INFO_MSG("Reloaded spam JIDs for ~s (unchanged)", [Host]);
false ->
?INFO_MSG("Reloaded spam JIDs for ~s (changed)", [Host])
end,
case sets_equal(URLsSet, State#state.url_set) of
true ->
?INFO_MSG("Reloaded spam URLs for ~s (unchanged)", [Host]);
false ->
?INFO_MSG("Reloaded spam URLs for ~s (changed)", [Host])
end,
{ok,
State#state{jid_set = JIDsSet,
url_set = URLsSet,
blocked_domains = maps:merge(BlockedDomains, set_to_map(SpamDomainsSet)),
whitelist_domains = set_to_map(WhitelistDomains, false)}};
{config_error, ErrorText} ->
{{error, ErrorText}, State}
end.
set_to_map(Set) ->
set_to_map(Set, true).
set_to_map(Set, V) ->
sets:fold(fun(K, M) -> M#{K => V} end, #{}, Set).
read_files(Host) ->
AccInitial =
#{jid => sets:new(),
url => sets:new(),
domains => sets:new(),
whitelist_domains => sets:new()},
Files =
#{jid => gen_mod:get_module_opt(Host, ?MODULE, spam_jids_file),
url => gen_mod:get_module_opt(Host, ?MODULE, spam_urls_file),
domains => gen_mod:get_module_opt(Host, ?MODULE, spam_domains_file),
whitelist_domains => gen_mod:get_module_opt(Host, ?MODULE, whitelist_domains_file)},
ejabberd_hooks:run_fold(antispam_get_lists, Host, AccInitial, [Files]).
get_rtbl_services_option(Host) when is_binary(Host) ->
get_rtbl_services_option(gen_mod:get_module_opts(Host, ?MODULE));
get_rtbl_services_option(Opts) when is_map(Opts) ->
Services = gen_mod:get_opt(rtbl_services, Opts),
case length(Services) =< 1 of
true ->
ok;
false ->
?WARNING_MSG("Option rtbl_services only supports one service, but several "
"were configured. Will use only first one",
[])
end,
case Services of
[] ->
[#rtbl_service{}];
[Host | _] when is_binary(Host) ->
[#rtbl_service{host = Host, node = ?DEFAULT_RTBL_DOMAINS_NODE}];
[[{Host, [{spam_source_domains_node, Node}]}] | _] ->
[#rtbl_service{host = Host, node = Node}]
end.
-spec get_proc_name(binary()) -> atom().
get_proc_name(Host) ->
gen_mod:get_module_proc(Host, ?MODULE).
-spec get_spam_filter_hosts() -> [binary()].
get_spam_filter_hosts() ->
[H || H <- ejabberd_option:hosts(), gen_mod:is_loaded(H, ?MODULE)].
-spec sets_equal(sets:set(), sets:set()) -> boolean().
sets_equal(A, B) ->
sets:is_subset(A, B) andalso sets:is_subset(B, A).
-spec format(io:format(), [term()]) -> binary().
format(Format, Data) ->
iolist_to_binary(io_lib:format(Format, Data)).
%%--------------------------------------------------------------------
%%| Caching
-spec cache_insert(ljid(), state()) -> state().
cache_insert(_LJID, #state{max_cache_size = 0} = State) ->
State;
cache_insert(LJID, #state{jid_cache = Cache, max_cache_size = MaxSize} = State)
when MaxSize /= unlimited, map_size(Cache) >= MaxSize ->
cache_insert(LJID, shrink_cache(State));
cache_insert(LJID, #state{jid_cache = Cache} = State) ->
?INFO_MSG("Caching spam JID: ~s", [jid:encode(LJID)]),
Cache1 = Cache#{LJID => erlang:monotonic_time(second)},
State#state{jid_cache = Cache1}.
-spec cache_lookup(ljid(), state()) -> {boolean(), state()}.
cache_lookup(LJID, #state{jid_cache = Cache} = State) ->
case Cache of
#{LJID := _Timestamp} ->
Cache1 = Cache#{LJID => erlang:monotonic_time(second)},
State1 = State#state{jid_cache = Cache1},
{true, State1};
#{} ->
{false, State}
end.
-spec shrink_cache(state()) -> state().
shrink_cache(#state{jid_cache = Cache, max_cache_size = MaxSize} = State) ->
ShrinkedSize = round(MaxSize / 2),
N = map_size(Cache) - ShrinkedSize,
L = lists:keysort(2, maps:to_list(Cache)),
Cache1 =
maps:from_list(
lists:nthtail(N, L)),
State#state{jid_cache = Cache1}.
-spec expire_cache(integer(), state()) -> {{ok, binary()}, state()}.
expire_cache(Age, #state{jid_cache = Cache} = State) ->
Threshold = erlang:monotonic_time(second) - Age,
Cache1 = maps:filter(fun(_, TS) -> TS >= Threshold end, Cache),
NumExp = map_size(Cache) - map_size(Cache1),
Txt = format("Expired ~B cache entries", [NumExp]),
{{ok, Txt}, State#state{jid_cache = Cache1}}.
-spec add_to_cache(ljid(), state()) -> {{ok, binary()}, state()}.
add_to_cache(LJID, State) ->
State1 = cache_insert(LJID, State),
Txt = format("~s added to cache", [jid:encode(LJID)]),
{{ok, Txt}, State1}.
-spec drop_from_cache(ljid(), state()) -> {{ok, binary()}, state()}.
drop_from_cache(LJID, #state{jid_cache = Cache} = State) ->
Cache1 = maps:remove(LJID, Cache),
if map_size(Cache1) < map_size(Cache) ->
Txt = format("~s removed from cache", [jid:encode(LJID)]),
{{ok, Txt}, State#state{jid_cache = Cache1}};
true ->
Txt = format("~s wasn't cached", [jid:encode(LJID)]),
{{ok, Txt}, State}
end.
%%--------------------------------------------------------------------
%%| ejabberd command callbacks
-spec get_commands_spec() -> [ejabberd_commands()].
get_commands_spec() ->
[#ejabberd_commands{name = reload_spam_filter_files,
tags = [spam],
desc = "Reload spam JID/URL files",
module = ?MODULE,
function = reload_spam_filter_files,
note = "added in 25.07",
args = [{host, binary}],
result = {res, rescode}},
#ejabberd_commands{name = get_spam_filter_cache,
tags = [spam],
desc = "Show spam filter cache contents",
module = ?MODULE,
function = get_spam_filter_cache,
note = "added in 25.07",
args = [{host, binary}],
result =
{spammers,
{list, {spammer, {tuple, [{jid, string}, {timestamp, integer}]}}}}},
#ejabberd_commands{name = expire_spam_filter_cache,
tags = [spam],
desc = "Remove old/unused spam JIDs from cache",
module = ?MODULE,
function = expire_spam_filter_cache,
note = "added in 25.07",
args = [{host, binary}, {seconds, integer}],
result = {res, restuple}},
#ejabberd_commands{name = add_to_spam_filter_cache,
tags = [spam],
desc = "Add JID to spam filter cache",
module = ?MODULE,
function = add_to_spam_filter_cache,
note = "added in 25.07",
args = [{host, binary}, {jid, binary}],
result = {res, restuple}},
#ejabberd_commands{name = drop_from_spam_filter_cache,
tags = [spam],
desc = "Drop JID from spam filter cache",
module = ?MODULE,
function = drop_from_spam_filter_cache,
note = "added in 25.07",
args = [{host, binary}, {jid, binary}],
result = {res, restuple}},
#ejabberd_commands{name = get_blocked_domains,
tags = [spam],
desc = "Get list of domains being blocked",
module = ?MODULE,
function = get_blocked_domains,
note = "added in 25.07",
args = [{host, binary}],
result = {blocked_domains, {list, {jid, string}}}},
#ejabberd_commands{name = add_blocked_domain,
tags = [spam],
desc = "Add domain to list of blocked domains",
module = ?MODULE,
function = add_blocked_domain,
note = "added in 25.07",
args = [{host, binary}, {domain, binary}],
result = {res, restuple}},
#ejabberd_commands{name = remove_blocked_domain,
tags = [spam],
desc = "Remove domain from list of blocked domains",
module = ?MODULE,
function = remove_blocked_domain,
note = "added in 25.07",
args = [{host, binary}, {domain, binary}],
result = {res, restuple}}].
for_all_hosts(F, A) ->
try lists:map(fun(Host) -> apply(F, [Host | A]) end, get_spam_filter_hosts()) of
List ->
case lists:filter(fun ({error, _}) ->
true;
(_) ->
false
end,
List)
of
[] ->
hd(List);
Errors ->
hd(Errors)
end
catch
error:{badmatch, {error, _Reason} = Error} ->
Error
end.
try_call_by_host(Host, Call) ->
LServer = jid:nameprep(Host),
Proc = get_proc_name(LServer),
try gen_server:call(Proc, Call, ?COMMAND_TIMEOUT) of
Result ->
Result
catch
exit:{noproc, _} ->
{error, "Not configured for " ++ binary_to_list(Host)};
exit:{timeout, _} ->
{error, "Timeout while querying ejabberd"}
end.
-spec reload_spam_filter_files(binary()) -> ok | {error, string()}.
reload_spam_filter_files(<<"global">>) ->
for_all_hosts(fun reload_spam_filter_files/1, []);
reload_spam_filter_files(Host) ->
case try_call_by_host(Host, reload_spam_files) of
{spam_filter, ok} ->
ok;
{spam_filter, {error, Txt}} ->
{error, Txt};
{error, _R} = Error ->
Error
end.
-spec get_blocked_domains(binary()) -> [binary()].
get_blocked_domains(Host) ->
case try_call_by_host(Host, get_blocked_domains) of
{blocked_domains, BlockedDomains} ->
maps:keys(
maps:filter(fun (_, false) ->
false;
(_, _) ->
true
end,
BlockedDomains));
{error, _R} = Error ->
Error
end.
-spec add_blocked_domain(binary(), binary()) -> {ok, string()}.
add_blocked_domain(<<"global">>, Domain) ->
for_all_hosts(fun add_blocked_domain/2, [Domain]);
add_blocked_domain(Host, Domain) ->
case try_call_by_host(Host, {add_blocked_domain, Domain}) of
{spam_filter, {Status, Txt}} ->
{Status, binary_to_list(Txt)};
{error, _R} = Error ->
Error
end.
-spec remove_blocked_domain(binary(), binary()) -> {ok, string()}.
remove_blocked_domain(<<"global">>, Domain) ->
for_all_hosts(fun remove_blocked_domain/2, [Domain]);
remove_blocked_domain(Host, Domain) ->
case try_call_by_host(Host, {remove_blocked_domain, Domain}) of
{spam_filter, {Status, Txt}} ->
{Status, binary_to_list(Txt)};
{error, _R} = Error ->
Error
end.
-spec get_spam_filter_cache(binary()) -> [{binary(), integer()}] | {error, string()}.
get_spam_filter_cache(Host) ->
case try_call_by_host(Host, get_cache) of
{spam_filter, Cache} ->
[{jid:encode(JID), TS + erlang:time_offset(second)} || {JID, TS} <- Cache];
{error, _R} = Error ->
Error
end.
-spec expire_spam_filter_cache(binary(), integer()) -> {ok | error, string()}.
expire_spam_filter_cache(<<"global">>, Age) ->
for_all_hosts(fun expire_spam_filter_cache/2, [Age]);
expire_spam_filter_cache(Host, Age) ->
case try_call_by_host(Host, {expire_cache, Age}) of
{spam_filter, {Status, Txt}} ->
{Status, binary_to_list(Txt)};
{error, _R} = Error ->
Error
end.
-spec add_to_spam_filter_cache(binary(), binary()) ->
[{binary(), integer()}] | {error, string()}.
add_to_spam_filter_cache(<<"global">>, JID) ->
for_all_hosts(fun add_to_spam_filter_cache/2, [JID]);
add_to_spam_filter_cache(Host, EncJID) ->
try jid:decode(EncJID) of
#jid{} = JID ->
LJID =
jid:remove_resource(
jid:tolower(JID)),
case try_call_by_host(Host, {add_to_cache, LJID}) of
{spam_filter, {Status, Txt}} ->
{Status, binary_to_list(Txt)};
{error, _R} = Error ->
Error
end
catch
_:{bad_jid, _} ->
{error, "Not a valid JID: " ++ binary_to_list(EncJID)}
end.
-spec drop_from_spam_filter_cache(binary(), binary()) -> {ok | error, string()}.
drop_from_spam_filter_cache(<<"global">>, JID) ->
for_all_hosts(fun drop_from_spam_filter_cache/2, [JID]);
drop_from_spam_filter_cache(Host, EncJID) ->
try jid:decode(EncJID) of
#jid{} = JID ->
LJID =
jid:remove_resource(
jid:tolower(JID)),
case try_call_by_host(Host, {drop_from_cache, LJID}) of
{spam_filter, {Status, Txt}} ->
{Status, binary_to_list(Txt)};
{error, _R} = Error ->
Error
end
catch
_:{bad_jid, _} ->
{error, "Not a valid JID: " ++ binary_to_list(EncJID)}
end.
%%--------------------------------------------------------------------
%%| vim: set foldmethod=marker foldmarker=%%|,%%-:
+186
View File
@@ -0,0 +1,186 @@
%%%----------------------------------------------------------------------
%%% File : mod_antispam_dump.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Manage dump file for filtered spam messages
%%% Created : 31 Mar 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2019-2025 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.
%%%
%%%----------------------------------------------------------------------
%%| Definitions
%% @format-begin
-module(mod_antispam_dump).
-author('holger@zedat.fu-berlin.de').
-author('stefan@strigler.de').
-export([init_dumping/1, terminate_dumping/2, reload_dumping/4, reopen_dump_file/2,
write_stanza_dump/2]).
%% ejabberd_hooks callbacks
-export([dump_spam_stanza/1, reopen_log/0]).
-include("logger.hrl").
-include("mod_antispam.hrl").
-include("translate.hrl").
-include_lib("xmpp/include/xmpp.hrl").
%%--------------------------------------------------------------------
%%| Exported
init_dumping(Host) ->
case get_path_option(Host) of
false ->
undefined;
DumpFile when is_binary(DumpFile) ->
case filelib:ensure_dir(DumpFile) of
ok ->
ejabberd_hooks:add(spam_stanza_rejected, Host, ?MODULE, dump_spam_stanza, 50),
ejabberd_hooks:add(reopen_log_hook, ?MODULE, reopen_log, 50),
open_dump_file(DumpFile);
{error, Reason} ->
Dirname = filename:dirname(DumpFile),
throw({open, Dirname, Reason})
end
end.
terminate_dumping(_Host, false) ->
ok;
terminate_dumping(Host, Fd) ->
DumpFile1 = get_path_option(Host),
close_dump_file(Fd, DumpFile1),
ejabberd_hooks:delete(spam_stanza_rejected, Host, ?MODULE, dump_spam_stanza, 50),
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_hooks:delete(reopen_log_hook, ?MODULE, reopen_log, 50);
true ->
ok
end.
reload_dumping(Host, Fd, OldOpts, NewOpts) ->
case {get_path_option(Host, OldOpts), get_path_option(Host, NewOpts)} of
{Old, Old} ->
Fd;
{Old, New} ->
reopen_dump_file(Fd, Old, New)
end.
-spec reopen_dump_file(binary(), file:io_device()) -> file:io_device().
reopen_dump_file(Host, Fd) ->
DumpFile1 = get_path_option(Host),
reopen_dump_file(Fd, DumpFile1, DumpFile1).
%%--------------------------------------------------------------------
%%| Hook callbacks
-spec dump_spam_stanza(message()) -> ok.
dump_spam_stanza(#message{to = #jid{lserver = LServer}} = Msg) ->
By = jid:make(<<>>, LServer),
Proc = get_proc_name(LServer),
Time = erlang:timestamp(),
Msg1 = misc:add_delay_info(Msg, By, Time),
XML = fxml:element_to_binary(
xmpp:encode(Msg1)),
gen_server:cast(Proc, {dump_stanza, XML}).
-spec reopen_log() -> ok.
reopen_log() ->
lists:foreach(fun(Host) ->
Proc = get_proc_name(Host),
gen_server:cast(Proc, reopen_log)
end,
get_spam_filter_hosts()).
%%--------------------------------------------------------------------
%%| File management
-spec open_dump_file(filename()) -> undefined | file:io_device().
open_dump_file(false) ->
undefined;
open_dump_file(Name) ->
Modes = [append, raw, binary, delayed_write],
case file:open(Name, Modes) of
{ok, Fd} ->
?DEBUG("Opened ~s", [Name]),
Fd;
{error, Reason} ->
?ERROR_MSG("Cannot open dump file ~s: ~s", [Name, file:format_error(Reason)]),
undefined
end.
-spec close_dump_file(undefined | file:io_device(), filename()) -> ok.
close_dump_file(undefined, false) ->
ok;
close_dump_file(Fd, Name) ->
case file:close(Fd) of
ok ->
?DEBUG("Closed ~s", [Name]);
{error, Reason} ->
?ERROR_MSG("Cannot close ~s: ~s", [Name, file:format_error(Reason)])
end.
-spec reopen_dump_file(file:io_device(), binary(), binary()) -> file:io_device().
reopen_dump_file(Fd, OldDumpFile, NewDumpFile) ->
close_dump_file(Fd, OldDumpFile),
open_dump_file(NewDumpFile).
write_stanza_dump(Fd, XML) ->
case file:write(Fd, [XML, <<$\n>>]) of
ok ->
ok;
{error, Reason} ->
?ERROR_MSG("Cannot write spam to dump file: ~s", [file:format_error(Reason)])
end.
%%--------------------------------------------------------------------
%%| Auxiliary
get_path_option(Host) ->
Opts = gen_mod:get_module_opts(Host, ?MODULE_ANTISPAM),
get_path_option(Host, Opts).
get_path_option(Host, Opts) ->
case gen_mod:get_opt(spam_dump_file, Opts) of
false ->
false;
true ->
LogDirPath =
iolist_to_binary(filename:dirname(
ejabberd_logger:get_log_path())),
filename:join([LogDirPath, <<"spam_dump_", Host/binary, ".log">>]);
B when is_binary(B) ->
B
end.
%%--------------------------------------------------------------------
%%| Copied from mod_antispam.erl
-spec get_proc_name(binary()) -> atom().
get_proc_name(Host) ->
gen_mod:get_module_proc(Host, ?MODULE_ANTISPAM).
-spec get_spam_filter_hosts() -> [binary()].
get_spam_filter_hosts() ->
[H || H <- ejabberd_option:hosts(), gen_mod:is_loaded(H, ?MODULE_ANTISPAM)].
%%--------------------------------------------------------------------
%%| vim: set foldmethod=marker foldmarker=%%|,%%-:
+181
View File
@@ -0,0 +1,181 @@
%%%----------------------------------------------------------------------
%%% File : mod_antispam_files.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Filter spam messages based on sender JID and content
%%% Created : 31 Mar 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2019-2025 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.
%%%
%%%----------------------------------------------------------------------
%%| definitions
%% @format-begin
-module(mod_antispam_files).
-author('holger@zedat.fu-berlin.de').
-author('stefan@strigler.de').
%% Exported
-export([init_files/1, terminate_files/1]).
% Hooks
-export([get_files_lists/2]).
-include("ejabberd_commands.hrl").
-include("logger.hrl").
-include("mod_antispam.hrl").
-include("translate.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-type files_map() :: #{atom() => filename()}.
-type lists_map() ::
#{jid => jid_set(),
url => url_set(),
atom() => sets:set(binary())}.
-define(COMMAND_TIMEOUT, timer:seconds(30)).
-define(DEFAULT_CACHE_SIZE, 10000).
-define(HTTPC_TIMEOUT, timer:seconds(3)).
%%--------------------------------------------------------------------
%%| Exported
init_files(Host) ->
ejabberd_hooks:add(antispam_get_lists, Host, ?MODULE, get_files_lists, 50).
terminate_files(Host) ->
ejabberd_hooks:delete(antispam_get_lists, Host, ?MODULE, get_files_lists, 50).
%%--------------------------------------------------------------------
%%| Hooks
-spec get_files_lists(lists_map(), files_map()) -> lists_map().
get_files_lists(#{jid := AccJids,
url := AccUrls,
domains := AccDomains,
whitelist_domains := AccWhitelist} =
Acc,
Files) ->
try read_files(Files) of
#{jid := JIDsSet,
url := URLsSet,
domains := SpamDomainsSet,
whitelist_domains := WhitelistDomains} ->
Acc#{jid => sets:union(AccJids, JIDsSet),
url => sets:union(AccUrls, URLsSet),
domains => sets:union(AccDomains, SpamDomainsSet),
whitelist_domains => sets:union(AccWhitelist, WhitelistDomains)}
catch
{Op, File, Reason} when Op == open; Op == read ->
ErrorText = format("Error trying to ~s file ~s: ~s", [Op, File, format_error(Reason)]),
?CRITICAL_MSG(ErrorText, []),
{stop, {config_error, ErrorText}}
end.
%%--------------------------------------------------------------------
%%| read_files
-spec read_files(files_map()) -> lists_map().
read_files(Files) ->
maps:map(fun(Type, Filename) -> read_file(Filename, line_parser(Type)) end, Files).
-spec line_parser(Type :: atom()) -> fun((binary()) -> binary()).
line_parser(jid) ->
fun parse_jid/1;
line_parser(url) ->
fun parse_url/1;
line_parser(_) ->
fun trim/1.
-spec read_file(filename(), fun((binary()) -> ljid() | url())) -> jid_set() | url_set().
read_file(none, _ParseLine) ->
sets:new();
read_file(File, ParseLine) ->
case file:open(File, [read, binary, raw, {read_ahead, 65536}]) of
{ok, Fd} ->
try
read_line(Fd, ParseLine, sets:new())
catch
E ->
throw({read, File, E})
after
ok = file:close(Fd)
end;
{error, Reason} ->
throw({open, File, Reason})
end.
-spec read_line(file:io_device(),
fun((binary()) -> ljid() | url()),
jid_set() | url_set()) ->
jid_set() | url_set().
read_line(Fd, ParseLine, Set) ->
case file:read_line(Fd) of
{ok, Line} ->
read_line(Fd, ParseLine, sets:add_element(ParseLine(Line), Set));
{error, Reason} ->
throw(Reason);
eof ->
Set
end.
-spec parse_jid(binary()) -> ljid().
parse_jid(S) ->
try jid:decode(trim(S)) of
#jid{} = JID ->
jid:remove_resource(
jid:tolower(JID))
catch
_:{bad_jid, _} ->
throw({bad_jid, S})
end.
-spec parse_url(binary()) -> url().
parse_url(S) ->
URL = trim(S),
RE = <<"https?://\\S+$">>,
Options = [anchored, caseless, {capture, none}],
case re:run(URL, RE, Options) of
match ->
URL;
nomatch ->
throw({bad_url, S})
end.
-spec trim(binary()) -> binary().
trim(S) ->
re:replace(S, <<"\\s+$">>, <<>>, [{return, binary}]).
%% Function copied from mod_antispam.erl
-spec format(io:format(), [term()]) -> binary().
format(Format, Data) ->
iolist_to_binary(io_lib:format(Format, Data)).
-spec format_error(atom() | tuple()) -> binary().
format_error({bad_jid, JID}) ->
<<"Not a valid JID: ", JID/binary>>;
format_error({bad_url, URL}) ->
<<"Not an HTTP(S) URL: ", URL/binary>>;
format_error(Reason) ->
list_to_binary(file:format_error(Reason)).
%%--------------------------------------------------------------------
%%| vim: set foldmethod=marker foldmarker=%%|,%%-:
+298
View File
@@ -0,0 +1,298 @@
%%%----------------------------------------------------------------------
%%% File : mod_antispam_filter.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Filter C2S and S2S stanzas
%%% Created : 31 Mar 2019 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2019-2025 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.
%%%
%%%----------------------------------------------------------------------
%%| Definitions
%% @format-begin
-module(mod_antispam_filter).
-author('holger@zedat.fu-berlin.de').
-author('stefan@strigler.de').
-export([init_filtering/1, terminate_filtering/1]).
%% ejabberd_hooks callbacks
-export([s2s_in_handle_info/2, s2s_receive_packet/1, sm_receive_packet/1]).
-include("logger.hrl").
-include("translate.hrl").
-include("mod_antispam.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-type s2s_in_state() :: ejabberd_s2s_in:state().
-define(HTTPC_TIMEOUT, timer:seconds(3)).
%%--------------------------------------------------------------------
%%| Exported
init_filtering(Host) ->
ejabberd_hooks:add(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 90),
ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 50),
ejabberd_hooks:add(sm_receive_packet, Host, ?MODULE, sm_receive_packet, 50).
terminate_filtering(Host) ->
ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, s2s_receive_packet, 50),
ejabberd_hooks:delete(sm_receive_packet, Host, ?MODULE, sm_receive_packet, 50),
ejabberd_hooks:delete(s2s_in_handle_info, Host, ?MODULE, s2s_in_handle_info, 90).
%%--------------------------------------------------------------------
%%| Hook callbacks
-spec s2s_receive_packet({stanza() | drop, s2s_in_state()}) ->
{stanza() | drop, s2s_in_state()} | {stop, {drop, s2s_in_state()}}.
s2s_receive_packet({A, State}) ->
case sm_receive_packet(A) of
{stop, drop} ->
{stop, {drop, State}};
Result ->
{Result, State}
end.
-spec sm_receive_packet(stanza() | drop) -> stanza() | drop | {stop, drop}.
sm_receive_packet(drop = Acc) ->
Acc;
sm_receive_packet(#message{from = From,
to = #jid{lserver = LServer} = To,
type = Type} =
Msg)
when Type /= groupchat, Type /= error ->
do_check(From, To, LServer, Msg);
sm_receive_packet(#presence{from = From,
to = #jid{lserver = LServer} = To,
type = subscribe} =
Presence) ->
do_check(From, To, LServer, Presence);
sm_receive_packet(Acc) ->
Acc.
%%--------------------------------------------------------------------
%%| Filtering deciding
do_check(From, To, LServer, Stanza) ->
case needs_checking(From, To) of
true ->
case check_from(LServer, From) of
ham ->
case check_stanza(LServer, From, Stanza) of
ham ->
Stanza;
spam ->
reject(Stanza),
{stop, drop}
end;
spam ->
reject(Stanza),
{stop, drop}
end;
false ->
Stanza
end.
check_stanza(LServer, From, #message{body = Body}) ->
check_body(LServer, From, xmpp:get_text(Body));
check_stanza(_, _, _) ->
ham.
-spec s2s_in_handle_info(s2s_in_state(), any()) ->
s2s_in_state() | {stop, s2s_in_state()}.
s2s_in_handle_info(State, {_Ref, {spam_filter, _}}) ->
?DEBUG("Dropping expired spam filter result", []),
{stop, State};
s2s_in_handle_info(State, _) ->
State.
-spec needs_checking(jid(), jid()) -> boolean().
needs_checking(#jid{lserver = FromHost} = From, #jid{lserver = LServer} = To) ->
case gen_mod:is_loaded(LServer, ?MODULE_ANTISPAM) of
true ->
Access = gen_mod:get_module_opt(LServer, ?MODULE_ANTISPAM, access_spam),
case acl:match_rule(LServer, Access, To) of
allow ->
?DEBUG("Spam not filtered for ~s", [jid:encode(To)]),
false;
deny ->
?DEBUG("Spam is filtered for ~s", [jid:encode(To)]),
not mod_roster:is_subscribed(From, To)
andalso not
mod_roster:is_subscribed(
jid:make(<<>>, FromHost),
To) % likely a gateway
end;
false ->
?DEBUG("~s not loaded for ~s", [?MODULE_ANTISPAM, LServer]),
false
end.
-spec check_from(binary(), jid()) -> ham | spam.
check_from(Host, From) ->
Proc = get_proc_name(Host),
LFrom =
{_, FromDomain, _} =
jid:remove_resource(
jid:tolower(From)),
try
case gen_server:call(Proc, {is_blocked_domain, FromDomain}) of
true ->
?DEBUG("Spam JID found in blocked domains: ~p", [From]),
ejabberd_hooks:run(spam_found, Host, [{jid, From}]),
spam;
false ->
case gen_server:call(Proc, {check_jid, LFrom}) of
{spam_filter, Result} ->
Result
end
end
catch
exit:{timeout, _} ->
?WARNING_MSG("Timeout while checking ~s against list of blocked domains or spammers",
[jid:encode(From)]),
ham
end.
-spec check_body(binary(), jid(), binary()) -> ham | spam.
check_body(Host, From, Body) ->
case {extract_urls(Host, Body), extract_jids(Body)} of
{none, none} ->
?DEBUG("No JIDs/URLs found in message", []),
ham;
{URLs, JIDs} ->
Proc = get_proc_name(Host),
LFrom =
jid:remove_resource(
jid:tolower(From)),
try gen_server:call(Proc, {check_body, URLs, JIDs, LFrom}) of
{spam_filter, Result} ->
Result
catch
exit:{timeout, _} ->
?WARNING_MSG("Timeout while checking body", []),
ham
end
end.
%%--------------------------------------------------------------------
%%| Auxiliary
-spec extract_urls(binary(), binary()) -> {urls, [url()]} | none.
extract_urls(Host, Body) ->
RE = <<"https?://\\S+">>,
Options = [global, {capture, all, binary}],
case re:run(Body, RE, Options) of
{match, Captured} when is_list(Captured) ->
Urls = resolve_redirects(Host, lists:flatten(Captured)),
{urls, Urls};
nomatch ->
none
end.
-spec resolve_redirects(binary(), [url()]) -> [url()].
resolve_redirects(_Host, URLs) ->
try do_resolve_redirects(URLs, []) of
ResolvedURLs ->
ResolvedURLs
catch
exit:{timeout, _} ->
?WARNING_MSG("Timeout while resolving redirects: ~p", [URLs]),
URLs
end.
-spec do_resolve_redirects([url()], [url()]) -> [url()].
do_resolve_redirects([], Result) ->
Result;
do_resolve_redirects([URL | Rest], Acc) ->
case httpc:request(get,
{URL, [{"user-agent", "curl/8.7.1"}]},
[{autoredirect, false}, {timeout, ?HTTPC_TIMEOUT}],
[])
of
{ok, {{_, StatusCode, _}, Headers, _Body}} when StatusCode >= 300, StatusCode < 400 ->
Location = proplists:get_value("location", Headers),
case Location == undefined orelse lists:member(Location, Acc) of
true ->
do_resolve_redirects(Rest, [URL | Acc]);
false ->
do_resolve_redirects([Location | Rest], [URL | Acc])
end;
_Res ->
do_resolve_redirects(Rest, [URL | Acc])
end.
-spec extract_jids(binary()) -> {jids, [ljid()]} | none.
extract_jids(Body) ->
RE = <<"\\S+@\\S+">>,
Options = [global, {capture, all, binary}],
case re:run(Body, RE, Options) of
{match, Captured} when is_list(Captured) ->
{jids, lists:filtermap(fun try_decode_jid/1, lists:flatten(Captured))};
nomatch ->
none
end.
-spec try_decode_jid(binary()) -> {true, ljid()} | false.
try_decode_jid(S) ->
try jid:decode(S) of
#jid{} = JID ->
{true,
jid:remove_resource(
jid:tolower(JID))}
catch
_:{bad_jid, _} ->
false
end.
-spec reject(stanza()) -> ok.
reject(#message{from = From,
to = To,
type = Type,
lang = Lang} =
Msg)
when Type /= groupchat, Type /= error ->
?INFO_MSG("Rejecting unsolicited message from ~s to ~s",
[jid:encode(From), jid:encode(To)]),
Txt = <<"Your message is unsolicited">>,
Err = xmpp:err_policy_violation(Txt, Lang),
ejabberd_hooks:run(spam_stanza_rejected, To#jid.lserver, [Msg]),
ejabberd_router:route_error(Msg, Err);
reject(#presence{from = From,
to = To,
lang = Lang} =
Presence) ->
?INFO_MSG("Rejecting unsolicited presence from ~s to ~s",
[jid:encode(From), jid:encode(To)]),
Txt = <<"Your traffic is unsolicited">>,
Err = xmpp:err_policy_violation(Txt, Lang),
ejabberd_router:route_error(Presence, Err);
reject(_) ->
ok.
-spec get_proc_name(binary()) -> atom().
get_proc_name(Host) ->
gen_mod:get_module_proc(Host, ?MODULE_ANTISPAM).
%%--------------------------------------------------------------------
%%| vim: set foldmethod=marker foldmarker=%%|,%%-:
+62
View File
@@ -0,0 +1,62 @@
%% Generated automatically
%% DO NOT EDIT: run `make options` instead
-module(mod_antispam_opt).
-export([access_spam/1]).
-export([cache_size/1]).
-export([rtbl_services/1]).
-export([spam_domains_file/1]).
-export([spam_dump_file/1]).
-export([spam_jids_file/1]).
-export([spam_urls_file/1]).
-export([whitelist_domains_file/1]).
-spec access_spam(gen_mod:opts() | global | binary()) -> 'none' | acl:acl().
access_spam(Opts) when is_map(Opts) ->
gen_mod:get_opt(access_spam, Opts);
access_spam(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, access_spam).
-spec cache_size(gen_mod:opts() | global | binary()) -> 'unlimited' | pos_integer().
cache_size(Opts) when is_map(Opts) ->
gen_mod:get_opt(cache_size, Opts);
cache_size(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, cache_size).
-spec rtbl_services(gen_mod:opts() | global | binary()) -> [binary() | [{binary(),[{'spam_source_domains_node',binary()}]}]].
rtbl_services(Opts) when is_map(Opts) ->
gen_mod:get_opt(rtbl_services, Opts);
rtbl_services(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, rtbl_services).
-spec spam_domains_file(gen_mod:opts() | global | binary()) -> 'none' | binary().
spam_domains_file(Opts) when is_map(Opts) ->
gen_mod:get_opt(spam_domains_file, Opts);
spam_domains_file(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, spam_domains_file).
-spec spam_dump_file(gen_mod:opts() | global | binary()) -> boolean() | binary().
spam_dump_file(Opts) when is_map(Opts) ->
gen_mod:get_opt(spam_dump_file, Opts);
spam_dump_file(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, spam_dump_file).
-spec spam_jids_file(gen_mod:opts() | global | binary()) -> 'none' | binary().
spam_jids_file(Opts) when is_map(Opts) ->
gen_mod:get_opt(spam_jids_file, Opts);
spam_jids_file(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, spam_jids_file).
-spec spam_urls_file(gen_mod:opts() | global | binary()) -> 'none' | binary().
spam_urls_file(Opts) when is_map(Opts) ->
gen_mod:get_opt(spam_urls_file, Opts);
spam_urls_file(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, spam_urls_file).
-spec whitelist_domains_file(gen_mod:opts() | global | binary()) -> 'none' | binary().
whitelist_domains_file(Opts) when is_map(Opts) ->
gen_mod:get_opt(whitelist_domains_file, Opts);
whitelist_domains_file(Host) ->
gen_mod:get_module_opt(Host, mod_antispam, whitelist_domains_file).
+147
View File
@@ -0,0 +1,147 @@
%%%----------------------------------------------------------------------
%%% File : mod_antispam_rtbl.erl
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Collection of RTBL specific functionality
%%% Created : 20 Mar 2025 by Stefan Strigler <stefan@strigler.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2025 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_antispam_rtbl).
-author('stefan@strigler.de').
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("mod_antispam.hrl").
-define(SERVICE_MODULE, mod_antispam).
-define(SERVICE_JID_PREFIX, "rtbl-").
-export([parse_blocked_domains/1,
parse_pubsub_event/1,
pubsub_event_handler/1,
request_blocked_domains/3,
subscribe/3,
unsubscribe/3]).
%% @format-begin
subscribe(RTBLHost, RTBLDomainsNode, From) ->
FromJID = service_jid(From),
SubIQ =
#iq{type = set,
to = jid:make(RTBLHost),
from = FromJID,
sub_els = [#pubsub{subscribe = #ps_subscribe{jid = FromJID, node = RTBLDomainsNode}}]},
?DEBUG("Sending subscription request:~n~p", [xmpp:encode(SubIQ)]),
ejabberd_router:route_iq(SubIQ, subscribe_result, self()).
-spec unsubscribe(binary() | none, binary(), binary()) -> ok.
unsubscribe(none, _PSNode, _From) ->
ok;
unsubscribe(RTBLHost, RTBLDomainsNode, From) ->
FromJID = jid:make(From),
SubIQ =
#iq{type = set,
to = jid:make(RTBLHost),
from = FromJID,
sub_els =
[#pubsub{unsubscribe = #ps_unsubscribe{jid = FromJID, node = RTBLDomainsNode}}]},
ejabberd_router:route_iq(SubIQ, unsubscribe_result, self()).
-spec request_blocked_domains(binary() | none, binary(), binary()) -> ok.
request_blocked_domains(none, _PSNode, _From) ->
ok;
request_blocked_domains(RTBLHost, RTBLDomainsNode, From) ->
IQ = #iq{type = get,
from = jid:make(From),
to = jid:make(RTBLHost),
sub_els = [#pubsub{items = #ps_items{node = RTBLDomainsNode}}]},
?DEBUG("Requesting RTBL blocked domains from ~s:~n~p", [RTBLHost, xmpp:encode(IQ)]),
ejabberd_router:route_iq(IQ, blocked_domains, self()).
-spec parse_blocked_domains(stanza()) -> #{binary() => any()} | undefined.
parse_blocked_domains(#iq{to = #jid{lserver = LServer}, type = result} = IQ) ->
?DEBUG("parsing iq-result items: ~p", [IQ]),
[#rtbl_service{node = RTBLDomainsNode}] = mod_antispam:get_rtbl_services_option(LServer),
case xmpp:get_subtag(IQ, #pubsub{}) of
#pubsub{items = #ps_items{node = RTBLDomainsNode, items = Items}} ->
?DEBUG("Got items:~n~p", [Items]),
parse_items(Items);
_ ->
undefined
end.
-spec parse_pubsub_event(stanza()) -> #{binary() => any()}.
parse_pubsub_event(#message{to = #jid{lserver = LServer}} = Msg) ->
[#rtbl_service{node = RTBLDomainsNode}] = mod_antispam:get_rtbl_services_option(LServer),
case xmpp:get_subtag(Msg, #ps_event{}) of
#ps_event{items =
#ps_items{node = RTBLDomainsNode,
items = Items,
retract = RetractIds}} ->
maps:merge(retract_items(RetractIds), parse_items(Items));
Other ->
?WARNING_MSG("Couldn't extract items: ~p", [Other]),
#{}
end.
-spec parse_items([ps_item()]) -> #{binary() => any()}.
parse_items(Items) ->
lists:foldl(fun(#ps_item{id = ID}, Acc) ->
%% TODO extract meta/extra instructions
maps:put(ID, true, Acc)
end,
#{},
Items).
-spec retract_items([binary()]) -> #{binary() => false}.
retract_items(Ids) ->
lists:foldl(fun(ID, Acc) -> Acc#{ID => false} end, #{}, Ids).
-spec service_jid(binary()) -> jid().
service_jid(Host) ->
jid:make(<<>>, Host, <<?SERVICE_JID_PREFIX, (ejabberd_cluster:node_id())/binary>>).
%%--------------------------------------------------------------------
%% Hook callbacks.
%%--------------------------------------------------------------------
-spec pubsub_event_handler(stanza()) -> drop | stanza().
pubsub_event_handler(#message{from = FromJid,
to =
#jid{lserver = LServer,
lresource = <<?SERVICE_JID_PREFIX, _/binary>>}} =
Msg) ->
?DEBUG("Got RTBL message:~n~p", [Msg]),
From = jid:encode(FromJid),
[#rtbl_service{host = RTBLHost}] = mod_antispam:get_rtbl_services_option(LServer),
case RTBLHost of
From ->
ParsedItems = parse_pubsub_event(Msg),
Proc = gen_mod:get_module_proc(LServer, ?SERVICE_MODULE),
gen_server:cast(Proc, {update_blocked_domains, ParsedItems}),
%% FIXME what's the difference between `{drop, ...}` and `{stop, {drop, ...}}`?
drop;
_Other ->
?INFO_MSG("Got unexpected message from ~s to rtbl resource:~n~p", [From, Msg]),
Msg
end;
pubsub_event_handler(Acc) ->
?DEBUG("unexpected something on pubsub_event_handler: ~p", [Acc]),
Acc.
+14 -4
View File
@@ -29,8 +29,8 @@
-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,
get_tokens/3, get_mechanisms/1]).
-export([c2s_inline_features/3, c2s_handle_sasl2_inline/1,
get_tokens/3, get_mechanisms/1, remove_user_tokens/2]).
-include_lib("xmpp/include/xmpp.hrl").
-include_lib("xmpp/include/scram.hrl").
@@ -54,7 +54,10 @@ start(Host, Opts) ->
Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(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_inline, c2s_handle_sasl2_inline, 10},
{hook, set_password, remove_user_tokens, 50},
{hook, sm_kick_user, remove_user_tokens, 50},
{hook, remove_user, remove_user_tokens, 50}]}.
-spec stop(binary()) -> ok.
stop(_Host) ->
@@ -128,7 +131,7 @@ get_tokens(LServer, LUser, UA) ->
{{Type, CreatedAt < ToRefresh}, Token}
end, Mod:get_tokens(LServer, LUser, ua_hash(UA))).
c2s_inline_features({Sasl, Bind, Extra}, Host) ->
c2s_inline_features({Sasl, Bind, Extra}, Host, _State) ->
{Sasl ++ [#fast{mechs = get_mechanisms(Host)}], Bind, Extra}.
gen_token(#{sasl2_ua_id := UA, server := Server, user := User}) ->
@@ -165,3 +168,10 @@ c2s_handle_sasl2_inline({#{server := Server, user := User, sasl2_ua_id := UA,
_ ->
Acc
end.
-spec remove_user_tokens(binary(), binary()) -> ok.
remove_user_tokens(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:del_tokens(LServer, LUser).
+10 -2
View File
@@ -28,12 +28,12 @@
%% API
-export([init/2]).
-export([get_tokens/3, del_token/4, set_token/6, rotate_token/3]).
-export([get_tokens/3, del_token/4, del_tokens/2, set_token/6, rotate_token/3]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-record(mod_auth_fast, {key = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary()} | '$1',
-record(mod_auth_fast, {key = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() | '_'} | '$1',
token = <<>> :: binary() | '_',
created_at = 0 :: non_neg_integer() | '_',
expires_at = 0 :: non_neg_integer() | '_'}).
@@ -94,6 +94,14 @@ del_token(LServer, LUser, UA, Type) ->
end,
transaction(F).
-spec del_tokens(binary(), binary()) -> ok | {error, atom()}.
del_tokens(LServer, LUser) ->
F = fun() ->
Elements = mnesia:match_object(#mod_auth_fast{key = {LServer, LUser, '_'}, _ = '_'}),
[mnesia:delete_object(E) || E <- Elements]
end,
transaction(F).
-spec set_token(binary(), binary(), binary(), current | next, binary(), non_neg_integer()) ->
ok | {error, atom()}.
set_token(LServer, LUser, UA, Type, Token, Expires) ->
+6 -6
View File
@@ -281,9 +281,9 @@ mod_doc() ->
#{value => ?T("AccessName"),
desc =>
?T("The option is supposed to be used when 'allow_local_users' "
"and 'allow_transports' are not enough. It's an ACL where "
"'deny' means the message will be rejected (or a CAPTCHA "
"would be generated for a presence, if configured), and "
"and 'allow_transports' are not enough. It's an Access Rule where "
"'deny' means the stanza will be rejected; there's an exception "
"if option 'captcha' is configured. And "
"'allow' means the sender is whitelisted and the stanza "
"will pass through. The default value is 'none', which "
"means nothing is whitelisted.")}},
@@ -314,8 +314,8 @@ mod_doc() ->
{captcha,
#{value => "true | false",
desc =>
?T("Whether to generate CAPTCHA or not in response to "
"messages from strangers. See also section "
"_`basic.md#captcha|CAPTCHA`_"
?T("Whether to generate CAPTCHA challenges in response to "
"incoming presence subscription requests from strangers. "
"See also section _`basic.md#captcha|CAPTCHA`_"
" of the Configuration Guide. "
"The default value is 'false'.")}}]}.

Some files were not shown because too many files have changed in this diff Show More