Compare commits

...

161 Commits

Author SHA1 Message Date
Badlop 543404bcb8 Fixing minor typos in CHANGELOG
Container / Container (push) Failing after 2m8s
Installers / Binaries (push) Failing after 53s
Runtime / Rebars (20.3, rebar) (push) Failing after 2s
Runtime / Rebars (20.3, rebar3) (push) Failing after 3s
Runtime / Rebars (25.3, rebar) (push) Failing after 2s
Runtime / Rebars (25.3, rebar3) (push) Failing after 2s
Runtime / Rebars (26, rebar3) (push) Failing after 3s
Installers / Release (push) Has been skipped
CI / Tests (20.0) (push) Has been cancelled
CI / Tests (25.3) (push) Has been cancelled
CI / Tests (26.1) (push) Has been cancelled
Runtime / Mix (1.10.3, 21.3) (push) Has been cancelled
Runtime / Mix (1.11.4, 21.3) (push) Has been cancelled
Runtime / Mix (1.13.4, 25.0) (push) Has been cancelled
Runtime / Mix (1.14.5, 25.0) (push) Has been cancelled
Runtime / Mix (1.14.5, 26) (push) Has been cancelled
Runtime / Mix (1.15, 25.0) (push) Has been cancelled
Runtime / Mix (1.15, 26) (push) Has been cancelled
2023-10-17 15:57:50 +02:00
Badlop b7166d7da0 Set version to 23.10 2023-10-17 15:31:32 +02:00
Badlop cf54608c71 Update changelog 2023-10-17 15:29:20 +02:00
Alexey Shchepin ec20691188 Disable update_sql_schema by default 2023-10-16 19:31:32 +03:00
Alexey Shchepin c1af36ac20 Automatically create and update SQL schema 2023-10-16 18:21:08 +03:00
Alexey Shchepin f6e8eb52f0 Fix ejabberd_sql:sql_query* types 2023-10-16 18:21:08 +03:00
Paweł Chmielowski ad7db90c80 Use tagged deps 2023-10-16 15:14:31 +02:00
Holger Weiss cbfb8eb805 Example configuration: Specify s2s shaper
Specify a shaper for incoming s2s connections (got lost in commit
91a74e3e27).

Thanks to Paul Menzel for noting that it was missing.
2023-10-16 14:44:01 +02:00
Holger Weiss df60818883 Example configuration: Clarify direct TLS listener
The ejabberd_c2s listener for port 5223 is meant to support direct TLS
access rather than STARTTLS.  Therefore, remove the 'starttls_required'
option, which had no effect.
2023-10-16 14:32:38 +02:00
Badlop d5de93b8fa Update other translations 2023-10-16 11:28:51 +02:00
Badlop ab6da9530d Update Ukrainian translation (thanks to Олександр Кревський) 2023-10-16 11:28:29 +02:00
Badlop ca82376657 Update man page 2023-10-16 11:18:59 +02:00
Badlop 87f18aa8d7 Result of running: make doap options 2023-10-16 11:09:13 +02:00
Badlop 6340d61397 Dcoument ejabberd version number in the new options 2023-10-16 10:58:59 +02:00
Badlop 80d1e36542 mod_private: Document that it supports XEP-0402 now 2023-10-16 10:56:15 +02:00
Paweł Chmielowski f75909db4c Allow pubsub node owner to overwrite items published by other persons
Owner is already permitted to delete those items, so it could do that by
deleting old item, and publishing it again, so i don't see reason to not
allow that overwrite.
2023-10-12 13:16:32 +02:00
Paweł Chmielowski 12d47455ba Add auth_external_user_exists_check option
This makes `user_check` hook work better with authentication methods
that don't have a way to determine if user exists (like is the case for
jwt and cert based authentication), and as result will improve mod_offline
and mod_mam handling of offline messages to those users. This reuses
information stored by `mod_last` for this purpose.

Should fix issue #3377.
2023-10-11 14:17:18 +02:00
Badlop 9acf591242 Run 'make translations' 2023-10-11 01:17:27 +02:00
Badlop bab8673055 Update Portuguese (Brazil) translation (thanks to Wellington Uemura) 2023-10-11 01:17:24 +02:00
Badlop 3851a77134 Update German translation (thanks to nautilusx) 2023-10-11 01:17:21 +02:00
Badlop 8e324e67a4 New Bulgarian translation (thanks to Mr. EddX) 2023-10-11 01:13:49 +02:00
Badlop 10245b40ee Add support to register nick in a room (#3455)
Registering a nick in the MUC service or in a room is mutually exclusive:
- A nick that is registered in the service cannot be registered in any
  room, not even the original owner can register it.
- Similarly, a nick registered in any room cannot be registered in the
  service.
2023-10-10 13:08:56 +02:00
Badlop 9534ca2da1 ext_mod: Support when git repository name is not identical to the module name
For example, ejabberd-contrib has an extra module, mod_prometheus,
that is hosted in a git repository named ejabberd-prometheus-exporter
2023-10-04 20:57:02 +02:00
Badlop f9d11265d0 ext_mod: Don't crash when github page response is 403 2023-10-04 20:55:01 +02:00
Badlop d85c125bef ext_mod: Use the same URL that ejabberd-contrib spec files 2023-10-04 20:54:49 +02:00
Paweł Chmielowski 62d3d7a32d Relay pubsub iq queries to muc members without using bare jid
We do something similar for vcard queries, this allows target server
to respond to those queries by target server, which is what we want to
do in both of those cases.
2023-10-04 17:20:20 +02:00
Holger Weiss a3f4a05b0c make-binaries: Don't use non-prefixed cross tools
Omit the directory that contains cross compilation tools without
"$target-" prefix from the PATH.  Having it in the path might lead to
problems when native tools are needed during cross compilation.  For
actual cross compilation, the prefixed tools should always be used
anyway.
2023-10-03 17:56:27 +02:00
Paweł Chmielowski 86465c418d Try to fill xep-0402 bookmarks from private storage on first access 2023-10-02 14:39:30 +02:00
Paweł Chmielowski a63d3bf0d6 Fix crash in mod_private bookmarks converter
This should fix issue reported in #4092
2023-10-02 08:32:24 +02:00
Holger Weiss 762e4951f2 make-binaries: Bump Erlang/OTP version to 26.1.1 2023-09-29 19:01:50 +02:00
Holger Weiss d4fc54be18 make-binaries: Let curl fail on HTTP errors 2023-09-29 17:33:09 +02:00
Holger Weiss f327f4cc67 make-binaries: Suppress curl's info messages 2023-09-29 17:32:17 +02:00
Paweł Chmielowski 9ba645503b Fix dialyzer warning 2023-09-27 23:02:32 +02:00
badlop 5406693a1e Merge pull request #4079 from sando38/fix/Dockerfile-odbc
Dockerfile: add missing dependency for mssql databases
2023-09-27 21:10:16 +02:00
Paweł Chmielowski d55955f7d8 Clean also urn:xmpp:bookmarks:1 after mod_private tests 2023-09-27 19:38:14 +02:00
Paweł Chmielowski 765770aaa5 Add support for xep-0402 - PEP Native Bookmarks 2023-09-27 18:36:30 +02:00
Paweł Chmielowski c3e0b746d7 Add pubsub_delete_item hook 2023-09-27 18:30:17 +02:00
Paweł Chmielowski accb0bc35a Report support of config-node-max in pep 2023-09-27 18:29:56 +02:00
Holger Weiss b16530bb6a make-binaries: Bump dependency versions 2023-09-27 00:28:46 +02:00
Holger Weiss ad00553bf8 make-binaries: Bump Erlang/OTP version to 26.1 2023-09-27 00:17:30 +02:00
Holger Weiss b70bef77cb make-binaries: Bump crosstool-NG version to 1.26.0 2023-09-26 23:55:01 +02:00
Holger Weiss ac47b7b8cb ejabberd_web_admin: Make text translatable
This also fixes a type issue.
2023-09-26 23:21:45 +02:00
Badlop abe0817553 Mention what software versions are used to build the container image 2023-09-26 18:51:57 +02:00
Badlop ab431b378a WebAdmin: Show a warning when visiting webadmin with non-privileged account (4089) 2023-09-26 18:51:56 +02:00
Badlop a534196315 Fix unused variable, forgotten in the recent commit 739a231 2023-09-26 18:51:51 +02:00
Badlop cd421f98d7 ejabberdctl: Document how to stop a debug shell: control+g
control+g is the correct way to enter shell break mode, as documented in
https://www.erlang.org/doc/apps/erts/tty

The ejabberdctl script included in installers use the included VT100,
and that may break when hitting control+c.
In that scenario let's explicitly recommend to not use control+c.

Thanks to Holger Weiß for the report.
2023-09-26 18:51:47 +02:00
badlop b33d660f88 Merge pull request #4088 from badlop/get-roster-command
Improve get_roster command result: show groups as a list
2023-09-25 13:15:26 +02:00
Badlop 245c9ae446 ejabberd_ctl: Support policy=user in the help and return proper arguments 2023-09-21 13:47:07 +02:00
Badlop 8d39431d68 ejabberd_ctl: Improve printing lists in results
When formatting the results of a command:
- If the top of the result is a list, split elements with newline as usual
- If it's a list in one of the resulting lines, split elements with ;
2023-09-21 13:47:05 +02:00
Badlop 739a231259 Improve get_roster command to return groups in a list instead of newlines 2023-09-21 13:47:00 +02:00
Paweł Chmielowski 426fd14b11 Make sure that policy=user commands have host instead of server arg in docs
We renamed them in ejabberd_command, but forgot to update doc geenerator.
2023-09-20 14:06:23 +02:00
dependabot[bot] 4735372682 Bump docker/build-push-action from 4 to 5
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:15 +02:00
dependabot[bot] 4b3fa13163 Bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:15 +02:00
dependabot[bot] d40250c3d6 Bump docker/setup-qemu-action from 2 to 3
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:15 +02:00
dependabot[bot] 7d2cfd2aaa Bump docker/metadata-action from 4 to 5
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:14 +02:00
dependabot[bot] 11fdd417dd Bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:14 +02:00
dependabot[bot] 1b7b23fab6 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [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/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:14 +02:00
dependabot[bot] 10882af7c8 Bump ex_doc from 0.30.5 to 0.30.6
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.30.5 to 0.30.6.
- [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.30.5...v0.30.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 11:05:14 +02:00
Holger Weiss 6c573cc9fd mod_push: Set 'last-message-sender' to bare JID
If the mod_push option 'include_sender' is set to 'true', use the bare
JID rather than the full JID for the 'last-message-sender' field.
2023-09-09 17:33:16 +02:00
Holger Weiss 2782430887 mod_push: Add 'notify_on' option
If the new 'notify_on' option is set to 'messages', notifications are
only triggered by actual chat messages with a body (or encrypted
payload).  The default behavior remains to generate a notification on
any kind of stanzas.

Thanks to EISST International Ltd for sponsoring this work.
2023-09-08 19:49:17 +02:00
Badlop 7522c29f25 Update xmpp version to send roomconfig_changesubject in disco info (#4085) 2023-09-08 18:37:38 +02:00
Badlop 706424f0d2 Fix crash when loading room from DB older than ffa07c6, 23.04
Before commit ffa07c6, the subject_author was just the author nick,
since that commit, subject_author contains {author nick, JID}.
2023-09-04 11:32:28 +02:00
Badlop 57d404a99b Remove mailing list link, it had no activity and was removed long ago 2023-09-04 11:32:07 +02:00
Badlop 19e2e169b1 Let "ejabberdctl etop" work in a release (if observer is available) 2023-08-28 18:27:30 +02:00
Badlop 2a6ea79260 When building OTP release with mix, keep ERLANG_NODE=ejabberd@localhost
This updates the mix.exs code from commit 8ca12d4 (ejabberd 21.07)
Consequently, no need in make-binaries to update ERLANG_NODE.
The ecs docker image 23.04 got a similar change in
  https://github.com/processone/docker-ejabberd/pull/73/commits/f81905d5e315d2eb813a715b51d4a8351a7d4e6b
2023-08-28 18:27:23 +02:00
Badlop 40333066d6 Update syntax of function calls as recommended by Elixir compiler 2023-08-28 18:26:55 +02:00
Badlop 6d596063de Elixir 1.15 removed support for --app
Removing that argument does not affect iexlive at all

For reference:
https://github.com/elixir-lang/elixir/commit/e1eecb8ca698712f5cff76f52ff49b381ce64aa9
2023-08-28 18:26:55 +02:00
Badlop a7c3c9b77d Pass ERLANG_OPTS when calling erl to parse the INET_DIST_INTERFACE (#4066)
This is required when running ejabberdctl in binary installers
and INET_DIST_INTERFACE was configured, because some boot files were removed
2023-08-28 18:26:48 +02:00
Badlop a01de8d944 Fix small bug introduced in 5d549dc
When providing a client-id as explained in
  https://www.process-one.net/blog/understanding-ejabberd-oauth-support-roadmap/
ejabberd crashed with an error
  exception error: no function clause matching
  ejabberd_oauth:get_client_identity(
as reported in
  https://stackoverflow.com/questions/76922951/ejabberd-oauth-api-http-1-1-502-bad-gateway
2023-08-28 18:26:03 +02:00
Paweł Chmielowski c0e7774937 Don't always store messages passed through muc_filter_message
Recently we added new places where we call muc_filter_message to add
occupandid info to messages, but this also made them be stored in archive
as mod_mam uses that hook for getting sent messages - in case of those
messages we shouldn't be doing this.

This should fix issue #4083
2023-08-25 14:20:51 +02:00
Paweł Chmielowski 00c76003cb Add ability to force alternative upsert implementation in mysql 2023-08-18 11:46:37 +02:00
Badlop b29f87a978 Result of running: make doap options 2023-08-16 13:14:02 +02:00
Holger Weiss 2dc843cddd mod_privilege: Don't fail to edit roster (#3942) 2023-08-16 13:08:21 +02:00
dependabot[bot] 9f08b4aa15 Bump ex_doc from 0.30.1 to 0.30.5
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.30.1 to 0.30.5.
- [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.30.1...v0.30.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-16 12:27:23 +02:00
Badlop a84fbd6a74 Improve syntax of many command descriptions for the Docs site 2023-08-16 12:27:23 +02:00
Badlop f8af3a0005 create_room_with_opts: fix typo and move examples to args_example (#4080) 2023-08-16 12:27:23 +02:00
Badlop 83e51c815d Pass also MUC room retract messages over the muc_filter_message (#3397) 2023-08-16 12:27:23 +02:00
Badlop ff24700156 Fix support to retract a MUC room message
Now this works as expected
https://xmpp.org/extensions/xep-0425.html#example-4
2023-08-16 12:27:22 +02:00
Badlop 7683691f5a mod_muc_occupantid: New mnesia table to store rooms salts (#3397) 2023-08-16 12:27:22 +02:00
Badlop 3479f88dab Pass MUC room private messages over the muc_filter_message too (#3397) 2023-08-16 12:27:22 +02:00
Badlop 86fc2f157e Always add the occupant id, even in non-anonymous rooms (#3397) 2023-08-16 12:27:22 +02:00
Badlop ffa07c649b Store the subject author JID, and run muc_filter_message when sending subject (#3397)
When changing the room subject, store the original author JID,
so later it can be provided in the hook and mod_room_occupantid
can use it to calculate and provide the occupant id

This is noticeable when a new occupant joins an existing room,
and receives the room subject.
2023-08-16 12:27:22 +02:00
Badlop 2bd61abd71 Convert allow_private_message MUC room option to allowpm (#3736) 2023-08-16 12:25:46 +02:00
Badlop 66df953da1 Fix usage of plugins option, which produced default_node_config ignore (#4070) 2023-08-16 12:25:42 +02:00
Badlop 550a586d2a New listener option unix_socket, useful when setting unix socket files (#4059)
listen:
  -
    port: "unix://tmp/asd/socket"
    unix_socket:
      mode: '0775'
      owner: 117
      group: 135
2023-08-16 12:25:36 +02:00
Badlop 16473ab691 When sending message on behalf of user, trigger user_send_packet (#3990)
This way, MAM and CarbonCopy get triggered.
This is useful for transports like Slidge.
2023-08-16 12:25:29 +02:00
Paweł Chmielowski c5afd0322e Properly parse mysql version even if it doesn't have type tag 2023-08-14 17:12:16 +02:00
sando38 c4563c429c Dockerfile: add missing dependency for mssql databases 2023-08-12 14:46:30 +02:00
Alexey Shchepin eeacace02a Update some modules to the new gen_mod API 2023-08-09 17:08:45 +03:00
Holger Weiss caf3807bcc ejabberd_systemd: Add a few debug messages 2023-08-09 01:58:48 +02:00
Holger Weiss 6c7e85d3d8 ejabberd_systemd: Avoid using gen_server timeout
Don't (ab)use the gen_server timeout mechanism for pinging the systemd
watchdog.  Under certain conditions (e.g., the process receiving sys
messages), the gen_server timeout might not be triggered as expected.

Fixes #4054, fixes #4058,
2023-08-09 01:54:12 +02:00
Holger Weiss f0db7623d1 mod_push_keepalive: Don't let hook crash
Check whether mod_push_keepalive is loaded for a given host before
querying the module configuration for that host.  This avoids a hook
crash in the case where the module is enabled for some but not all
hosts.
2023-08-09 00:11:43 +02:00
Holger Weiss 26ed6539ba mod_push_keepalive: Delay 'wake_on_start'
Delay the 'wake_on_start' notifications until ejabberd is fully
initialized.  This makes sure no s2s connections are initiated before
certificates are loaded.

Many thanks to Friedrich Altheide for reporting the issue.
2023-08-08 20:17:10 +02:00
Alexey Shchepin a9347cd248 Fix unused variable warnings in mod_stats 2023-08-04 20:54:02 +03:00
Alexey Shchepin 60002fc145 Update some modules to the new gen_mod API 2023-08-04 18:53:50 +03:00
Paweł Chmielowski 20a8654be2 Reset scram fields when setting plain password in ejabberd_auth_sql
Setting scram password, then disabling scram and setting plain password
again, will make us think we are still using scramed password and crash
when trying to process it as such. This makes sure that when set plain
password we don't leave parts from old scram password.
2023-08-03 13:07:13 +02:00
Badlop 25411333da Add support for XEP-0421 Occupant Id in anonymous MUC rooms (#3397) 2023-08-02 17:54:54 +02:00
Badlop 07d4282603 Update XMPP library which supports XEP-0421 (3397) 2023-08-02 17:54:07 +02:00
Alexey Shchepin 4bd77797fc Add shorter forms for gen_mod hook/iq_handler API 2023-08-01 18:14:08 +03:00
Alexey Shchepin 03ffbe00c1 Update mod_disco to the new gen_mod API 2023-07-31 16:52:00 +03:00
Alexey Shchepin 5a9099f49c Extend gen_mod API to simplify hooks and IQ handlers registration 2023-07-31 16:52:00 +03:00
Badlop b501ee2b8d Remove obsolete example files, and add link in Docs to the archived copies 2023-07-27 14:03:40 +02:00
Badlop a7c3368635 Move example Perl extauth script from ejabberd git to Docs site 2023-07-27 14:03:37 +02:00
Paweł Chmielowski 84ee724aa3 Fix dialyzer warning 2023-07-25 18:49:45 +02:00
Paweł Chmielowski da7fe59834 Commit changes to ejabberd_sql.hrl as well 2023-07-25 18:24:24 +02:00
Paweł Chmielowski 3710dc1e3b Use prepared statement with mysql 2023-07-25 18:11:24 +02:00
Badlop db03c7428c Web Admin roster page: move the AddJID textbox to top (#4067) 2023-07-24 20:27:01 +02:00
Badlop 9c6fe98f76 Partially revert "Improve ejabberdctl script: Copy recent commit 6272c0e90 to the container"
This reverts commit e66ba2e424
following the revert in commit 8288774787
2023-07-24 20:26:37 +02:00
Holger Weiss c03af0afb3 make-binaries: Remove outdated workaround
Remove a workaround for an issue that has been fixed in Erlang/OTP 25:

https://github.com/erlang/otp/pull/5558
2023-07-24 00:11:32 +02:00
Holger Weiss d109d7f0c5 make-binaries: Revert to Linux-PAM 1.5.2
Linux-PAM's --disable-examples flag doesn't seem to have the expected
effect.
2023-07-23 16:34:21 +02:00
Holger Weiss bf9b257eab make-binaries: Don't build Linux-PAM examples
Building the examples shipped with Linux-PAM 1.5.3 fails with musl libc:

https://github.com/linux-pam/linux-pam/commit/5374f677e4cae669eb9accf2449178b602e8a40a
2023-07-23 16:08:25 +02:00
Holger Weiss 11dc0c1774 make-binaries: Bump dependency versions 2023-07-23 15:52:32 +02:00
Holger Weiss 0a5eda0777 make-binaries: Fix check for current Expat version 2023-07-23 15:51:27 +02:00
Holger Weiss a657a6d2f6 make-binaries: Apply another minor simplification 2023-07-23 15:35:09 +02:00
Holger Weiss 121acd1da7 make-binaries: Apply minor simplifications 2023-07-23 15:06:40 +02:00
Holger Weiss ec86079747 make-binaries: Don't duplicate config entries 2023-07-23 14:56:01 +02:00
Holger Weiss f1b0a9cb32 make-binaries: Don't hard-code musl version
The default is to link against the most-recent musl version available
within the crosstool-ng toolchain, which is currently 1.2.2.  Unlike
with glibc, there's no point in sticking to some older version.
2023-07-23 14:54:04 +02:00
Holger Weiss 0b6cb77b3c make-binaries: Omit unnecessary glibc setting
The CT_GLIBC_KERNEL_VERSION_NONE option is about the minimum kernel
version supported by the toolchain's glibc.  The default is to stick to
the version of the kernel headers, which should be just fine.
2023-07-23 14:52:18 +02:00
Holger Weiss 8a740d5087 make-binaries: Set kernel version for all builds
Specify the same min. supported kernel version for builds against musl
libc and glibc.
2023-07-23 14:49:07 +02:00
Jérôme Sautret 8288774787 Add EJABBERD_OPTS in ejabberdctl.cfg & revert "Improve ejabberdctl script"
EJABBERD_OPTS is used to pass options to erl only when starting
ejabberd, to enable -heart for example.

This partially reverts commit 6272c0e901.
2023-07-18 15:31:10 +02:00
Paweł Chmielowski d349e3a88e Revert "Add stun app to cond_apps in mix.exs"
Looks like this is required only when using older mix versions.

This reverts commit 2ef9fbc111.
2023-07-18 11:14:57 +02:00
Paweł Chmielowski 2ef9fbc111 Add stun app to cond_apps in mix.exs 2023-07-17 18:40:39 +02:00
Badlop 78f81de252 Improve support to stop external modules written in Elixir 2023-07-13 13:41:18 +02:00
dependabot[bot] 8d9ee8e35b Bump ex_doc from 0.29.4 to 0.30.1
Bumps [ex_doc](https://github.com/elixir-lang/ex_doc) from 0.29.4 to 0.30.1.
- [Changelog](https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elixir-lang/ex_doc/compare/v0.29.4...v0.30.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 13:41:14 +02:00
Badlop e66ba2e424 Improve ejabberdctl script: Copy recent commit 6272c0e90 to the container 2023-07-13 13:41:06 +02:00
Paweł Chmielowski fafb48e88f Merge pull request #4025 from jpds/increase-default-backlog
ejabberd_listener.erl: Increase default listen queue backlog value
2023-07-11 12:19:33 +02:00
Jérôme Sautret 6272c0e901 Improve ejabberdctl script
Improved handling of ERLANG_OPTS and fixed hanging process when running some ejabberdctl commands as root, such as debug or foreground.
2023-07-10 16:18:06 +02:00
Paweł Chmielowski 54314e5bb9 Better error handling in mod_muc_rtbl
Should fix issue #4050
2023-07-04 18:18:49 +02:00
Badlop ffbcf19156 Halt ejabberd if a command in CTL_ON_ fails during ejabberd startup
See processone/ejabberd-contrib#97
2023-06-20 17:54:10 +02:00
Badlop dcc8149f58 New command to halt ejabberd abruptly with an error status code
Used for processone/ejabberd-contrib#97
2023-06-20 17:54:10 +02:00
Badlop d2c54fd5fe Fix calling ejabberdctl command with wrong number of arguments with Erlang 26
In Erlang up to 25.3, the lists:zip arguments were [A1, A2]
Since Erlang 26.0, the arguments are: [A1, A2, fail]

https://github.com/erlang/otp/commit/93748a8d841efc8f8246074ca721607efecebe2d
2023-06-13 12:25:27 +02:00
Badlop f40a7b1c77 OAuth: Handle badpass error message 2023-06-13 12:25:22 +02:00
Badlop 16f758e13f Support to provide only the dependency name
This is used in ejabberd-contrib repository's ci.yml,
and useful for a custom development that doesn't require rebar2 support.
2023-06-12 20:48:47 +02:00
Badlop c333cc0776 New option install_contrib_modules
This option is read during ejabberd start or config reload.
It installs the listed modules which aren't yet installed,
as long as allow_contrib_modules is not disabled.
Edit ejabberd.yml and configure the desired ejabberd-contrib modules,
add them in the install_contrib_modules option,
finally start ejabberd (or reload config).
2023-06-09 10:27:13 +02:00
Badlop 3263e81972 Move Xref and Dialyzer options from workflows to rebar.config
And also include some more applications in Dialyzer plt_extra_apps,
which apparently is required since Erlang 26.
2023-06-09 00:02:20 +02:00
Badlop 397a08afca Move configure options from the Test to the Compile section 2023-06-09 00:02:18 +02:00
Badlop 19070e4b04 Add sections to rebar.config to organize its content 2023-06-09 00:02:17 +02:00
Badlop 0bbc255814 Dialyzer dirty workarounds because re:mp() is not an exported type
Since Erlang/OTP 26, Dialyzer by default reports unknown types.
ejabberd's type specs refer to the re:mp() type,
but that isn't exported in the OTP source code,
and cannot be used in any other modules.
This commit provides very dirty workarounds, and any cleaner
alternative is very welcomed.
2023-06-09 00:02:15 +02:00
Badlop 1b06f4ca4f The warnings_as_errors compiler option is already disabled by default 2023-06-09 00:02:13 +02:00
Badlop b3eeac637f Update tests to Erlang/OTP 26 and recent Elixir 2023-06-09 00:02:10 +02:00
Badlop 480b42b36d Run Dialyzer again with Erlang/OTP 26, and let's solve its problems 2023-06-09 00:02:06 +02:00
Badlop 436074c67a When installing module already configured, keep config as example
When installing a module using ext_mod, if it has already configuration
in the modules section, copy its specific config file as an example
(copy file and rename it).
This may happen when using the new install_contrib_modules option.
2023-06-09 00:02:02 +02:00
Badlop c9a2117570 Ignore .tool-versions in git, this file is used by asdf 2023-06-09 00:01:56 +02:00
sando38 6155b001b4 Dockerfile: Reorder stages and steps for consistency
Also avoid duplicated lines.
2023-06-09 00:01:54 +02:00
sando38 8f05af7810 Dockerfile: Use Alpine as base for METHOD=package
No need to use the "large" docker.io/erlang image as we do not need any erlang/otp for the binary installers.
2023-06-09 00:01:51 +02:00
sando38 33ac7916d3 Dockerfile: Cosmetic changes
Ommit the path to ejabberdctl as it is already located at the $PATH

Also, do not copy Dockerfile into the container as this may unnecessarily trigger re-compiling of ejabberd.
2023-06-09 00:01:49 +02:00
sando38 461c1ddf3d Dockerfile: Rename packages to improve compatibility
Rename libcap packages to improve compatibility between Alpine versions. This may be beneficial if one specifies an OTP_VSN which was built using an older Alpine base version.

The alpine package libcap has been splitted into libcap2 and libcap-utils in Alpine 3.17. 'libcap' is now an alias for libcap2 and libcap-utils. We define 'so:libcap.so.2' for the runtime stage, as we only need the libraries, not the binaries.
2023-06-09 00:01:46 +02:00
sando38 2428f74fbd Dockerfile: Provide specific OTP and elixir vsn for direct compilation
Ejabberd images can now be built with specific erlang/OTP and elixir vsn with the new build arguments OTP_VSN and ELIXIR_VSN.
2023-06-09 00:01:36 +02:00
Paweł Chmielowski bb8e892323 Add alternate version of mysql upsert
This one works by issuing select and then insert or update or skip depending
on what select returns. We use this on mysql 5.7.26 and 8.0.20 where
previous implementation using 'replace' or 'on conflict update' can cause
excessive deadlocks.
2023-06-07 16:38:07 +02:00
Paweł Chmielowski 3eecf4ae8a Remove existing role information for users that are kicked from room
This should fix issue reported in #4035
2023-05-24 12:15:20 +02:00
Paweł Chmielowski 4a53d4cb56 Expand rule "mucsub subscribers are members in members only rooms" to more places 2023-05-24 11:26:48 +02:00
Paweł Chmielowski 1818a29c29 Don't crash in mod_shared_roster_ldap:get_member_jid on empty output
This based on crash from issue #3614
2023-05-22 12:48:29 +02:00
Paweł Chmielowski dd2efc360b Fix return values from calculate_diff inside mod_privacy_sql 2023-05-02 15:59:40 +02:00
Paweł Chmielowski 8e64992f47 Use more efficient way to calculate changes in set_privacy_list 2023-05-02 11:43:29 +02:00
Jonathan Davies 040c72f1c8 ejabberd_listener.erl: Increase default listen queue backlog value to 128, which
is the default value on both Linux and FreeBSD.
2023-04-25 20:11:04 +01:00
Badlop d95a1bac3e Use container names to differentiate them; don't force logo height 2023-04-25 09:50:19 +02:00
Badlop c6b295b5a0 Fix typo in the previous commit 2023-04-25 09:48:02 +02:00
Badlop 2a4a6bec18 Add trailing backslash to URLs shown in mod_muc_log disco#info 2023-04-24 13:43:09 +02:00
Badlop 0d3f8c7b9f Make mod_register_web redirect to page that end with / (#3177)
Code copied from ejabberd_web_admin.erl, commit 5ec21438
2023-04-24 13:43:07 +02:00
Badlop d299b97261 Raise Erlang/OTP requirement to 20.0 2023-04-24 13:43:00 +02:00
Paweł Chmielowski 5b8ebed81b Optimize mod_privacy_sql:set_list
Previously we always did delete everything and set all entries back, now
we check if we need to delete anything and if not insert only missing data.
2023-04-20 10:55:30 +02:00
150 changed files with 4627 additions and 1752 deletions
+1 -1
View File
@@ -43,4 +43,4 @@ Mnesia.nonode@nohost/
/ejabberd-*.rpm
/ejabberd-*.run
/ejabberd-*.tar.gz
/.github/container/Dockerfile
+41 -23
View File
@@ -1,5 +1,10 @@
#' Define default build variables
## specifc ARGs for METHOD='direct'
ARG OTP_VSN='25.3'
ARG ELIXIR_VSN='1.14.4'
## specifc ARGs for METHOD='package'
ARG ALPINE_VSN='3.17'
## general ARGs
ARG UID='9000'
ARG USER='ejabberd'
ARG HOME="opt/$USER"
@@ -9,7 +14,7 @@ ARG VERSION='master'
################################################################################
#' METHOD='direct' - build and install ejabberd directly from source
FROM alpine:${ALPINE_VSN} AS direct
FROM docker.io/erlang:${OTP_VSN}-alpine AS direct
RUN apk -U add --no-cache \
autoconf \
@@ -17,9 +22,6 @@ RUN apk -U add --no-cache \
bash \
build-base \
curl \
elixir \
erlang-odbc \
erlang-reltool \
expat-dev \
file \
gd-dev \
@@ -33,6 +35,13 @@ RUN apk -U add --no-cache \
yaml-dev \
zlib-dev
ARG ELIXIR_VSN
RUN wget -O - https://github.com/elixir-lang/elixir/archive/v$ELIXIR_VSN.tar.gz \
| tar -xzf -
WORKDIR elixir-$ELIXIR_VSN
RUN make install clean
RUN mix local.hex --force \
&& mix local.rebar --force
@@ -66,7 +75,7 @@ RUN wget -O "$HOME/conf/cacert.pem" 'https://curl.se/ca/cacert.pem' \
################################################################################
#' METHOD='package' - install ejabberd from binary tarball package
FROM alpine:${ALPINE_VSN} AS package
FROM docker.io/alpine:${ALPINE_VSN} AS package
COPY tarballs/ejabberd-*-linux-musl-*.tar.gz /tmp/
WORKDIR /rootfs
ARG HOME
@@ -80,7 +89,7 @@ RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
FROM ${METHOD} AS ejabberd
RUN apk -U add --no-cache \
git \
libcap-utils \
libcap \
openssl
WORKDIR /rootfs
@@ -119,30 +128,39 @@ RUN home_root_dir=$(echo $HOME | sed 's|\(.*\)/.*|\1 |') \
> usr/local/bin/ejabberdctl \
&& chmod +x usr/local/bin/* \
&& scanelf --needed --nobanner --format '%n#p' --recursive $home_root_dir \
| tr ',' '\n' | sort -u | awk 'system("[ -e $home_root_dir" $1 " ]") == 0 { next } \
{ print "so:" $1 }' > /tmp/runDeps
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e $home_root_dir" $1 " ]") == 0 { next } { print "so:" $1 }' \
| sed -e "s|so:libc.so|so:libc.musl-$(uname -m).so.1|" \
> /tmp/runDeps
ARG UID
RUN chown -R $UID:$UID $HOME
################################################################################
#' METHOD='package' - install runtime dependencies
FROM alpine:${ALPINE_VSN} AS runtime-package
#' METHOD='direct' - Remove erlang/OTP & rebar3
FROM docker.io/erlang:${OTP_VSN}-alpine AS runtime-direct
RUN apk del .erlang-rundeps \
&& rm -f $(which rebar3) \
&& find /usr -type d -name 'erlang' -exec rm -rf {} + \
&& find /usr -type l -exec test ! -e {} \; -delete
################################################################################
#' METHOD='package' - define runtime base image
FROM docker.io/alpine:${ALPINE_VSN} AS runtime-package
################################################################################
#' Update alpine, finalize runtime environment
FROM runtime-${METHOD} AS runtime
COPY --from=ejabberd /tmp/runDeps /tmp/runDeps
RUN apk -U upgrade --available --no-cache \
&& apk add --no-cache \
libcap2 \
tini
$(cat /tmp/runDeps) \
so:libcap.so.2 \
so:libtdsodbc.so.0 \
tini \
&& ln -fs /usr/lib/libtdsodbc.so.0 /usr/lib/libtdsodbc.so
################################################################################
#' METHOD='direct' - install runtime dependencies
FROM runtime-package AS runtime-direct
COPY --from=ejabberd /tmp/runDeps /tmp/runDeps
RUN apk add --no-cache \
$(cat /tmp/runDeps)
################################################################################
#' Finalize runtime environment
FROM runtime-${METHOD} AS runtime
ARG USER
ARG UID
ARG HOME
@@ -170,5 +188,5 @@ USER $USER
VOLUME ["/$HOME"]
EXPOSE 1883 4369-4399 5210 5222 5269 5280 5443
ENTRYPOINT ["/sbin/tini","--","/usr/local/bin/ejabberdctl"]
ENTRYPOINT ["/sbin/tini","--","ejabberdctl"]
CMD ["foreground"]
+13 -5
View File
@@ -77,7 +77,7 @@ if [ -n "$FIREWALL_WINDOW" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
if [ -n "$INET_DIST_INTERFACE" ] ; then
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
INET_DIST_INTERFACE2=$("$ERL" $ERLANG_OPTS -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
if [ -n "$INET_DIST_INTERFACE2" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
fi
@@ -129,8 +129,8 @@ run_cmd()
exec_cmd()
{
case $EXEC_CMD in
as_install_user) su -s /bin/sh -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
as_current_user) exec "$@" ;;
as_install_user) su -s /bin/sh -c 'exec "$0" "$@"' "$INSTALLUSER" -- "$@" ;;
esac
}
run_erl()
@@ -162,9 +162,11 @@ debugwarning()
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To detach this shell from ejabberd, press:"
echo " control+c, control+c"
echo "To exit and detach this shell from ejabberd, press:"
echo " control+g and then q"
echo ""
#vt100 echo "Please do NOT use control+c in this debug shell !"
#vt100 echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
@@ -283,6 +285,12 @@ post_waiter_loop()
TAIL=${LIST#* ; }
echo ":> ejabberdctl $HEAD"
$0 $HEAD
ctlstatus=$?
if [ $ctlstatus -ne 0 ] ; then
echo ":> FAILURE in command '$HEAD' !!! Stopping ejabberd..."
$0 halt > /dev/null
exit $ctlstatus
fi
[ "$HEAD" = "$TAIL" ] || post_waiter_loop $TAIL
}
@@ -352,7 +360,7 @@ case $1 in
;;
iexlive)
livewarning
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS"
;;
ping)
PEER=${2:-$ERLANG_NODE}
+2 -16
View File
@@ -25,7 +25,7 @@ jobs:
strategy:
fail-fast: false
matrix:
otp: ['20.0', '25.3', '26.0-rc3']
otp: ['20.0', '25.3', '26.1']
runs-on: ubuntu-20.04
services:
redis:
@@ -35,7 +35,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Test shell scripts
if: matrix.otp == '25.3'
@@ -100,19 +100,6 @@ jobs:
sudo apt-get -qq install libexpat1-dev libgd-dev libpam0g-dev \
libsqlite3-dev libwebp-dev libyaml-dev
- name: Prepare rebar
run: |
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
]}.' >>rebar.config
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
locals_not_used, undefined_function_calls, undefined_functions]}.
% Disabled: exports_not_used,' >>rebar.config
echo '{dialyzer, [{get_warnings, true}, {plt_extra_apps, [cache_tab,
eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
sqlite3, stringprep, stun, xmpp, yconf]} ]}.' >>rebar.config
echo "{ct_opts, [{keep_logs, 20}]}." >>rebar.config
- name: Remove syntax_tools from release
run: sed -i 's|, syntax_tools||g' src/ejabberd.app.src.script
@@ -149,7 +136,6 @@ jobs:
- run: make options
- run: make xref
- run: make dialyzer
if: matrix.otp != '26.0-rc3'
- name: Check Production Release
run: |
+7 -7
View File
@@ -24,7 +24,7 @@ jobs:
packages: write
steps:
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -75,13 +75,13 @@ jobs:
mv ejabberd-*.tar.gz tarballs
- name: Checkout ejabberd-contrib
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: processone/ejabberd-contrib
path: .ejabberd-modules/sources/ejabberd-contrib
- name: Log in to the Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -93,7 +93,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
labels: |
@@ -102,13 +102,13 @@ jobs:
org.opencontainers.image.vendor=ProcessOne
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
build-args: |
METHOD=package
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
uses: actions/cache@v3
with:
path: ~/build/
key: ${{runner.os}}-ct-ng-1.25.0
key: ${{runner.os}}-ct-ng-1.26.0
- name: Install prerequisites
run: |
sudo apt-get -qq update
@@ -41,7 +41,7 @@ jobs:
gem install --no-document --user-install fpm
echo $HOME/.local/share/gem/ruby/*/bin >> $GITHUB_PATH
- name: Check out repository code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build binary archives
+16 -16
View File
@@ -42,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Prepare libraries
run: |
@@ -62,14 +62,6 @@ jobs:
make update
make
- name: Prepare rebar
run: |
echo '{xref_ignores, [{eldap_filter_yecc, return_error, 2}
]}.' >>rebar.config
echo '{xref_checks, [deprecated_function_calls, deprecated_functions,
locals_not_used, undefined_function_calls, undefined_functions]}.
% Disabled: exports_not_used,' >>rebar.config
- run: make xref
- name: Test rel (rebar2)
@@ -111,28 +103,36 @@ jobs:
strategy:
fail-fast: false
matrix:
otp: ['21.3', '22.0', '25.0']
elixir: ['1.10.3', '1.11.4', '1.12.3', '1.13.0', '1.14.0']
otp: ['21.3', '25.0', '26']
elixir: ['1.10.3', '1.11.4', '1.12.3', '1.13.4', '1.14.5', '1.15']
exclude:
- otp: '21.3'
elixir: '1.12.3'
- otp: '21.3'
elixir: '1.13.0'
elixir: '1.13.4'
- otp: '21.3'
elixir: '1.14.0'
- otp: '22.0'
elixir: '1.14.0'
elixir: '1.14.5'
- otp: '21.3'
elixir: '1.15'
- otp: '25.0'
elixir: '1.10.3'
- otp: '25.0'
elixir: '1.11.4'
- otp: '25.0'
elixir: '1.12.3'
- otp: '26'
elixir: '1.10.3'
- otp: '26'
elixir: '1.11.4'
- otp: '26'
elixir: '1.12.3'
- otp: '26'
elixir: '1.13.4'
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get specific Erlang/OTP
uses: erlef/setup-beam@v1
+1
View File
@@ -5,6 +5,7 @@
\#*#
.#*
.edts
.tool-versions
*.dump
/Makefile
/doc
+108
View File
@@ -1,3 +1,111 @@
# Version 23.10
Compilation:
- Erlang/OTP: Raise the requirement to Erlang/OTP 20.0 as a minimum
- CI: Update tests to Erlang/OTP 26 and recent Elixir
- Move Xref and Dialyzer options from workflows to `rebar.config`
- Add sections to `rebar.config` to organize its content
- Dialyzer dirty workarounds because `re:mp()` is not an exported type
- When installing module already configured, keep config as example
- Elixir 1.15 removed support for `--app`
- Elixir: Improve support to stop external modules written in Elixir
- Elixir: Update syntax of function calls as recommended by Elixir compiler
- Elixir: When building OTP release with mix, keep `ERLANG_NODE=ejabberd@localhost`
- `ejabberdctl`: Pass `ERLANG_OPTS` when calling `erl` to parse the `INET_DIST_INTERFACE` ([#4066](https://github.com/processone/ejabberd/issues/#4066)
Commands:
- `create_room_with_opts`: Fix typo and move examples to `args_example` ([#4080](https://github.com/processone/ejabberd/issues/#4080))
- `etop`: Let `ejabberdctl etop` work in a release (if `observer` application is available)
- `get_roster`: Command now returns groups in a list instead of newlines ([#4088](https://github.com/processone/ejabberd/issues/#4088))
- `halt`: New command to halt ejabberd abruptly with an error status code
- `ejabberdctl`: Fix calling ejabberdctl command with wrong number of arguments with Erlang 26
- `ejabberdctl`: Improve printing lists in results
- `ejabberdctl`: Support `policy=user` in the help and return proper arguments
- `ejabberdctl`: Document how to stop a debug shell: control+g
Container:
- Dockerfile: Add missing dependency for mssql databases
- Dockerfile: Reorder stages and steps for consistency
- Dockerfile: Use Alpine as base for `METHOD=package`
- Dockerfile: Rename packages to improve compatibility
- Dockerfile: Provide specific OTP and elixir vsn for direct compilation
- Halt ejabberd if a command in `CTL_ON_` fails during ejabberd startup
Core:
- `auth_external_user_exists_check`: New option ([#3377](https://github.com/processone/ejabberd/issues/#3377))
- `gen_mod`: Extend `gen_mod` API to simplify hooks and IQ handlers registration
- `gen_mod`: Add shorter forms for `gen_mod` hook/`iq_handler` API
- `gen_mod`: Update modules to the new `gen_mod` API
- `install_contrib_modules`: New option to define contrib modules to install automatically
- `unix_socket`: New listener option, useful when setting unix socket files ([#4059](https://github.com/processone/ejabberd/issues/#4059))
- `ejabberd_systemd`: Add a few debug messages
- `ejabberd_systemd`: Avoid using `gen_server` timeout ([#4054](https://github.com/processone/ejabberd/issues/#4054))([#4058](https://github.com/processone/ejabberd/issues/#4058))
- `ejabberd_listener`: Increase default listen queue backlog value to 128, which is the default value on both Linux and FreeBSD ([#4025](https://github.com/processone/ejabberd/issues/#4025))
- OAuth: Handle `badpass` error message
- When sending message on behalf of user, trigger `user_send_packet` ([#3990](https://github.com/processone/ejabberd/issues/#3990))
- Web Admin: In roster page move the `AddJID` textbox to top ([#4067](https://github.com/processone/ejabberd/issues/#4067))
- Web Admin: Show a warning when visiting webadmin with non-privileged account ([#4089](https://github.com/processone/ejabberd/issues/#4089))
Docs:
- Example configuration: clarify 5223 tls options; specify s2s shaper
- Make sure that `policy=user` commands have `host` instead of `server` arg in docs
- Improve syntax of many command descriptions for the Docs site
- Move example Perl extauth script from ejabberd git to Docs site
- Remove obsolete example files, and add link in Docs to the archived copies
Installers (`make-binaries`):
- Bump Erlang/OTP version to 26.1.1, and other dependencies
- Remove outdated workaround
- Don't build Linux-PAM examples
- Fix check for current Expat version
- Apply minor simplifications
- Don't duplicate config entries
- Don't hard-code musl version
- Omit unnecessary glibc setting
- Set kernel version for all builds
- Let curl fail on HTTP errors
Modules:
- `mod_muc_log`: Add trailing backslash to URLs shown in disco info
- `mod_muc_occupantid`: New module with support for XEP-0421 Occupant Id ([#3397](https://github.com/processone/ejabberd/issues/#3397))
- `mod_muc_rtbl`: Better error handling in ([#4050](https://github.com/processone/ejabberd/issues/#4050))
- `mod_private`: Add support for XEP-0402 PEP Native Bookmarks
- `mod_privilege`: Don't fail to edit roster ([#3942](https://github.com/processone/ejabberd/issues/#3942))
- `mod_pubsub`: Fix usage of `plugins` option, which produced `default_node_config` ignore ([#4070](https://github.com/processone/ejabberd/issues/#4070))
- `mod_pubsub`: Add `pubsub_delete_item` hook
- `mod_pubsub`: Report support of `config-node-max` in pep
- `mod_pubsub`: Relay pubsub iq queries to muc members without using bare jid ([#4093](https://github.com/processone/ejabberd/issues/#4093))
- `mod_pubsub`: Allow pubsub node owner to overwrite items published by other persons
- `mod_push_keepalive`: Delay `wake_on_start`
- `mod_push_keepalive`: Don't let hook crash
- `mod_push`: Add `notify_on` option
- `mod_push`: Set `last-message-sender` to bare JID
- `mod_register_web`: Make redirect to page that end with `/` ([#3177](https://github.com/processone/ejabberd/issues/#3177))
- `mod_shared_roster_ldap`: Don't crash in `get_member_jid` on empty output ([#3614](https://github.com/processone/ejabberd/issues/#3614))
MUC:
- Add support to register nick in a room ([#3455](https://github.com/processone/ejabberd/issues/#3455))
- Convert `allow_private_message` MUC room option to `allowpm` ([#3736](https://github.com/processone/ejabberd/issues/#3736))
- Update xmpp version to send `roomconfig_changesubject` in disco#info ([#4085](https://github.com/processone/ejabberd/issues/#4085))
- Fix crash when loading room from DB older than ffa07c6, 23.04
- Fix support to retract a MUC room message
- Don't always store messages passed through `muc_filter_message` ([#4083](https://github.com/processone/ejabberd/issues/#4083))
- Pass also MUC room retract messages over the `muc_filter_message` ([#3397](https://github.com/processone/ejabberd/issues/#3397))
- Pass MUC room private messages over the `muc_filter_message` too ([#3397](https://github.com/processone/ejabberd/issues/#3397))
- Store the subject author JID, and run `muc_filter_message` when sending subject ([#3397](https://github.com/processone/ejabberd/issues/#3397))
- Remove existing role information for users that are kicked from room ([#4035](https://github.com/processone/ejabberd/issues/#4035))
- Expand rule "mucsub subscribers are members in members only rooms" to more places
SQL:
- Add ability to force alternative upsert implementation in mysql
- Properly parse mysql version even if it doesn't have type tag
- Use prepared statement with mysql
- Add alternate version of mysql upsert
- `ejabberd_auth_sql`: Reset scram fields when setting plain password
- `mod_privacy_sql`: Fix return values from `calculate_diff`
- `mod_privacy_sql`: Optimize `set_list`
- `mod_privacy_sql`: Use more efficient way to calculate changes in `set_privacy_list`
# Version 23.04
General:
+1 -1
View File
@@ -19,7 +19,7 @@ To compile ejabberd you need:
- GCC
- Libexpat ≥ 1.95
- Libyaml ≥ 0.1.4
- Erlang/OTP ≥ 19.3
- Erlang/OTP ≥ 20.0
- OpenSSL ≥ 1.0.0
Other optional libraries are:
+4 -4
View File
@@ -1,11 +1,10 @@
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/processone/ejabberd?sort=semver&logo=embarcadero&label=&color=49c0c4)](https://github.com/processone/ejabberd/tags)
[![GitHub Container](https://img.shields.io/github/v/tag/processone/ejabberd?label=container&sort=semver)](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/ejabberd/ecs?label=docker)](https://hub.docker.com/r/ejabberd/ecs/)
[![GitHub Container](https://img.shields.io/github/v/tag/processone/ejabberd?label=ejabberd&sort=semver&logo=docker)](https://github.com/processone/ejabberd/pkgs/container/ejabberd)
ejabberd Container
==================
`ejabberd` Container Image
==========================
[ejabberd][home] is an open-source,
robust, scalable and extensible realtime platform built using [Erlang/OTP][erlang],
@@ -20,6 +19,7 @@ that includes [XMPP][xmpp] Server, [MQTT][mqtt] Broker and [SIP][sip] Service.
This document explains how to use the `ejabberd` container image available in
[ghcr.io/processone/ejabberd](https://github.com/processone/ejabberd/pkgs/container/ejabberd),
built using the files in `.github/container/`.
This image is based in Alpine 3.17, includes Erlang/OTP 25.3 and Elixir 1.14.4.
Alternatively, there is also the `ecs` container image available in
[docker.io/ejabberd/ecs](https://hub.docker.com/r/ejabberd/ecs/),
+2
View File
@@ -442,7 +442,9 @@ Makefile: Makefile.in
ifeq "$(REBAR_VER)" "3"
dialyzer:
find src/*_opt.erl -type f \! -regex ".*git.*" -exec sed -i 's/re:mp/ tuple/g' {} \;
$(REBAR) dialyzer
find src/*_opt.erl -type f \! -regex ".*git.*" -exec sed -i 's/ tuple/re:mp/g' {} \;
else
deps := $(wildcard $(DEPSDIR)/*/ebin)
+8 -10
View File
@@ -1,19 +1,18 @@
<p align="center">
<img src="https://www.process-one.net/wp-content/uploads/2022/05/ejabberd-logo-rounded-index.png"
height="216">
<img src="https://www.process-one.net/wp-content/uploads/2022/05/ejabberd-logo-rounded-index.png">
</p>
<p align="center">
<a href="https://github.com/processone/ejabberd/tags" alt="GitHub tag (latest SemVer)">
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?sort=semver&logo=embarcadero&label=&color=3fb0d2&logoWidth=20" /></a>
<a href="https://hex.pm/packages/ejabberd" alt="Hex version">
<img src="https://img.shields.io/hexpm/v/ejabberd.svg" /></a>
<a href="https://github.com/processone/ejabberd/pkgs/container/ejabberd" alt="GitHub Container">
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?label=container&sort=semver" /></a>
<a href="https://hub.docker.com/r/ejabberd/ecs/" alt="Docker Image Version (latest semver)">
<img src="https://img.shields.io/docker/v/ejabberd/ecs?label=docker" /></a>
<a href="https://formulae.brew.sh/formula/ejabberd" alt="homebrew version">
<img src="https://img.shields.io/homebrew/v/ejabberd" /></a>
<a href="https://hub.docker.com/r/ejabberd/ecs/" alt="Docker Image Version (latest semver)">
<img src="https://img.shields.io/docker/v/ejabberd/ecs?label=ecs&logo=docker" /></a>
<a href="https://github.com/processone/ejabberd/pkgs/container/ejabberd" alt="GitHub Container">
<img src="https://img.shields.io/github/v/tag/processone/ejabberd?label=ejabberd&sort=semver&logo=docker" /></a>
<br />
<a href="https://github.com/processone/ejabberd/actions/workflows/ci.yml" alt="CI">
<img src="https://github.com/processone/ejabberd/actions/workflows/ci.yml/badge.svg" /></a>
@@ -39,8 +38,8 @@ There are several ways to install ejabberd:
- Source code: compile yourself, see [COMPILE](COMPILE.md)
- Installers from [ProcessOne Download][p1download] or [ejabberd GitHub Releases][releases] (run/deb/rpm for x64 and arm64)
- Container image from [ejabberd Docker Hub][hubecs], see [ecs README][docker-ecs-readme] (for x64)
- Container image from [ejabberd Github Packages][packages], see [CONTAINER](CONTAINER.md) (for x64 and arm64)
- `ecs` container image available in [Docker Hub][hubecs] and [Github Packages][packagesecs], see [ecs README][docker-ecs-readme] (for x64)
- `ejabberd` container image available in [Github Packages][packages], see [CONTAINER](CONTAINER.md) (for x64 and arm64)
- Using your [Operating System package][osp]
- Using the [Homebrew][homebrew] package manager
@@ -86,7 +85,6 @@ Community
There are several places to get in touch with other ejabberd developers and administrators:
- ejabberd XMPP chatroom: [ejabberd@conference.process-one.net][muc]
- [Mailing list][list]
- [GitHub Discussions][discussions]
- [Stack Overflow][stackoverflow]
@@ -109,7 +107,6 @@ and [ejabberd translations](https://github.com/processone/ejabberd-po/) under MI
[hubecs]: https://hub.docker.com/r/ejabberd/ecs/
[im]: https://ejabberd.im/
[issues]: https://github.com/processone/ejabberd/issues
[list]: https://lists.jabber.ru/mailman/listinfo/ejabberd
[localization]: https://docs.ejabberd.im/developer/extending-ejabberd/localization/
[mqtt]: https://mqtt.org/
[muc]: xmpp:ejabberd@conference.process-one.net
@@ -118,6 +115,7 @@ and [ejabberd translations](https://github.com/processone/ejabberd-po/) under MI
[p1download]: https://www.process-one.net/en/ejabberd/downloads/
[p1home]: https://www.process-one.net/en/ejabberd/
[packages]: https://github.com/processone/ejabberd/pkgs/container/ejabberd
[packagesecs]: https://github.com/processone/docker-ejabberd/pkgs/container/ecs
[releases]: https://github.com/processone/ejabberd/releases
[sip]: https://en.wikipedia.org/wiki/Session_Initiation_Protocol
[stackoverflow]: https://stackoverflow.com/questions/tagged/ejabberd?sort=newest
+2 -2
View File
@@ -2,8 +2,8 @@
# 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 23.04` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="8.3 (Erlang/OTP 19.3)"
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 23.10` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="9.0.5 (Erlang/OTP 20.0)"
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
+18
View File
@@ -638,6 +638,15 @@
<xmpp:note>mod_avatar, mod_vcard_xupdate</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
<xmpp:version>1.1.3</xmpp:version>
<xmpp:since>23.10</xmpp:since>
<xmpp:status></xmpp:status>
<xmpp:note>mod_private</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0405.html"/>
@@ -665,6 +674,15 @@
<xmpp:note>mod_private</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0421.html"/>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>23.10</xmpp:since>
<xmpp:status></xmpp:status>
<xmpp:note>mod_muc_occupantid</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0425.html"/>
+2 -2
View File
@@ -36,17 +36,17 @@ listen:
-
port: 5223
ip: "::"
tls: true
module: ejabberd_c2s
max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
starttls_required: true
tls: true
-
port: 5269
ip: "::"
module: ejabberd_s2s_in
max_stanza_size: 524288
shaper: s2s_shaper
-
port: 5443
ip: "::"
+15 -4
View File
@@ -108,10 +108,8 @@
#.
#' ERL_OPTIONS: Additional Erlang options
#
# The next variable allows to specify additional options passed to erlang while
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
# ejabberd is started from an init.d script options -noshell and -detached are
# added implicitly. See erl(1) for more info.
# The next variable allows to specify additional options passed to
# erlang. See erl(1) for more info.
#
# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you
# want to add local modules in this path.
@@ -120,6 +118,19 @@
#
#ERL_OPTIONS=""
#.
#' EJABBERD_OPTS: Additional Erlang options to start ejabberd
#
# The next variable allows to specify additional options passed to erlang while
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
# ejabberd is started from an init.d script options -noshell and -detached are
# added implicitly. See erl(1) for more info.
#
# Default: ""
#
#EJABBERD_OPTS=""
EJABBERD_OPTS="-heart -env HEART_BEAT_TIMEOUT 120 -env ERL_CRASH_DUMP_SECONDS 60"
#.
#' ERLANG_NODE: Erlang node name
#
+9 -7
View File
@@ -76,7 +76,7 @@ if [ -n "$FIREWALL_WINDOW" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
if [ -n "$INET_DIST_INTERFACE" ] ; then
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
INET_DIST_INTERFACE2=$("$ERL" $ERLANG_OPTS -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
if [ -n "$INET_DIST_INTERFACE2" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
fi
@@ -121,7 +121,7 @@ set_dist_client()
exec_cmd()
{
case $EXEC_CMD in
as_install_user) su -s /bin/sh -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
as_install_user) su -s /bin/sh -c 'exec "$0" "$@"' "$INSTALLUSER" -- "$@" ;;
as_current_user) "$@" ;;
esac
}
@@ -149,9 +149,11 @@ debugwarning()
echo "Please be extremely cautious with your actions,"
echo "and exit immediately if you are not completely sure."
echo ""
echo "To detach this shell from ejabberd, press:"
echo " control+c, control+c"
echo "To exit and detach this shell from ejabberd, press:"
echo " control+g and then q"
echo ""
#vt100 echo "Please do NOT use control+c in this debug shell !"
#vt100 echo ""
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
@@ -305,8 +307,8 @@ case $1 in
;;
etop)
set_dist_client
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
-s erlang halt -output text
exec_erl "$(uid top)" -hidden -remsh "$ERLANG_NODE" -s etop \
-output text
;;
iexdebug)
debugwarning
@@ -315,7 +317,7 @@ case $1 in
;;
iexlive)
livewarning
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS"
;;
ping)
PEER=${2:-$ERLANG_NODE}
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/perl
use Unix::Syslog qw(:macros :subs);
my $domain = $ARGV[0] || "example.com";
while(1)
{
# my $rin = '',$rout;
# vec($rin,fileno(STDIN),1) = 1;
# $ein = $rin;
# my $nfound = select($rout=$rin,undef,undef,undef);
my $buf = "";
syslog LOG_INFO,"waiting for packet";
my $nread = sysread STDIN,$buf,2;
do { syslog LOG_INFO,"port closed"; exit; } unless $nread == 2;
my $len = unpack "n",$buf;
my $nread = sysread STDIN,$buf,$len;
my ($op,$user,$host,$password) = split /:/,$buf;
#$user =~ s/\./\//og;
my $jid = "$user\@$domain";
my $result;
syslog(LOG_INFO,"request (%s)", $op);
SWITCH:
{
$op eq 'auth' and do
{
$result = 1;
},last SWITCH;
$op eq 'setpass' and do
{
$result = 1;
},last SWITCH;
$op eq 'isuser' and do
{
# password is null. Return 1 if the user $user\@$domain exitst.
$result = 1;
},last SWITCH;
$op eq 'tryregister' and do
{
$result = 1;
},last SWITCH;
$op eq 'removeuser' and do
{
# password is null. Return 1 if the user $user\@$domain exitst.
$result = 1;
},last SWITCH;
$op eq 'removeuser3' and do
{
$result = 1;
},last SWITCH;
};
my $out = pack "nn",2,$result ? 1 : 0;
syswrite STDOUT,$out;
}
closelog;
-75
View File
@@ -1,75 +0,0 @@
#!/bin/sh
#
# PROVIDE: ejabberd
# REQUIRE: DAEMON
# KEYWORD: shutdown
#
HOME=/usr/pkg/jabber D=/usr/pkg/jabber/ejabberd export HOME
name="ejabberd"
rcvar=$name
if [ -r /etc/rc.conf ]
then
. /etc/rc.conf
else
eval ${rcvar}=YES
fi
# $flags from environment overrides ${rcvar}_flags
if [ -n "${flags}" ]
then
eval ${rcvar}_flags="${flags}"
fi
checkyesno()
{
eval _value=\$${1}
case $_value in
[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;;
[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;;
*)
echo "\$${1} is not set properly."
return 1
;;
esac
}
cmd=${1:-start}
case ${cmd} in
force*)
cmd=${cmd#force}
eval ${rcvar}=YES
;;
esac
if checkyesno ${rcvar}
then
else
exit 0
fi
case ${cmd} in
start)
if [ -x $D/src ]; then
echo "Starting ${name}."
cd $D/src
ERL_MAX_PORTS=32000 export ERL_MAX_PORTS
ulimit -n $ERL_MAX_PORTS
su jabber -c "/usr/pkg/bin/erl -sname ejabberd -s ejabberd -heart -detached -sasl sasl_error_logger '{file, \"ejabberd-sasl.log\"}' &" \
1>/dev/null 2>&1
fi
;;
stop)
echo "rpc:call('ejabberd@`hostname -s`', init, stop, [])." | \
su jabber -c "/usr/pkg/bin/erl -sname ejabberdstop"
;;
restart)
echo "rpc:call('ejabberd@`hostname -s`', init, restart, [])." | \
su jabber -c "/usr/pkg/bin/erl -sname ejabberdrestart"
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
-81
View File
@@ -1,81 +0,0 @@
#!/bin/sh
echo '1. fetch, compile, and install erlang'
if [ ! pkg_info erlang 1>/dev/null 2>&1 ]; then
cd /usr/pkgsrc/lang/erlang
make fetch-list|sh
make
make install
fi
if pkg_info erlang | grep -q erlang-9.1nb1; then
else
echo "erlang-9.1nb1 not installed" 1>&2
exit 1
fi
echo '2. install crypt_drv.so'
if [ ! -d /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib ] ; then
mkdir -p /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/crypto_drv.so ]; then
cp work/otp*/lib/crypto/priv/*/*/crypto_drv.so \
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
echo '3. compile and install elibcrypto.so'
if [ ! -f /usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib/elibcrypto.so ]; then
cd /usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/crypto/c_src
ld -r -u CRYPTO_set_mem_functions -u MD5 -u MD5_Init -u MD5_Update \
-u MD5_Final -u SHA1 -u SHA1_Init -u SHA1_Update -u SHA1_Final \
-u des_set_key -u des_ncbc_encrypt -u des_ede3_cbc_encrypt \
-L/usr/lib -lcrypto -o ../priv/obj/i386--netbsdelf/elibcrypto.o
cc -shared \
-L/usr/pkgsrc/lang/erlang/work/otp_src_R9B-1/lib/erl_interface/obj/i386--netbsdelf \
-o ../priv/obj/i386--netbsdelf/elibcrypto.so \
../priv/obj/i386--netbsdelf/elibcrypto.o -L/usr/lib -lcrypto
cp ../priv/obj/i386--netbsdelf/elibcrypto.so \
/usr/pkg/lib/erlang/lib/crypto-1.1.2.1/priv/lib
fi
echo '4. compile and install ssl_esock'
if [ ! -f /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/bin/ssl_esock ]; then
cd /usr/pkg/lib/erlang/lib/ssl-2.3.5/priv/obj/
make
fi
echo '5. initial ejabberd configuration'
cd /usr/pkg/jabber/ejabberd/src
./configure
echo '6. edit ejabberd Makefiles'
for M in Makefile mod_*/Makefile; do
if [ ! -f $M.orig ]; then
mv $M $M.orig
sed -e s%/usr/local%/usr/pkg%g < $M.orig > $M
fi
done
echo '7. compile ejabberd'
gmake
for A in mod_muc mod_pubsub; do
(cd $A; gmake)
done
echo ''
echo 'now edit ejabberd.cfg'
echo ''
echo 'to start ejabberd: erl -sname ejabberd -s ejabberd'
-65
View File
@@ -1,65 +0,0 @@
% jabber.dbc.mtview.ca.us
override_acls.
{acl, admin, {user, "mrose", "jabber.dbc.mtview.ca.us"}}.
{access, announce, [{allow, admin},
{deny, all}]}.
{access, c2s, [{deny, blocked},
{allow, all}]}.
{access, c2s_shaper, [{none, admin},
{normal, all}]}.
{access, configure, [{allow, admin},
{deny, all}]}.
{access, disco_admin, [{allow, admin},
{deny, all}]}.
{access, muc_admin, [{allow, admin},
{deny, all}]}.
{access, register, [{deny, all}]}.
{access, s2s_shaper, [{fast, all}]}.
{auth_method, internal}.
{host, "jabber.dbc.mtview.ca.us"}.
{outgoing_s2s_port, 5269}.
{shaper, normal, {maxrate, 1000}}.
{shaper, fast, {maxrate, 50000}}.
{welcome_message, none}.
{listen, [{5222, ejabberd_c2s,
[{access, c2s},
{shaper, c2s_shaper}]},
{5223, ejabberd_c2s,
[{access, c2s},
{shaper, c2s_shaper},
{ssl, [{certfile, "/etc/openssl/certs/ejabberd.pem"}]}]},
{5269, ejabberd_s2s_in,
[{shaper, s2s_shaper}]}]}.
{modules, [
{mod_register, []},
{mod_roster, []},
{mod_privacy, []},
{mod_configure, []},
{mod_disco, []},
{mod_stats, []},
{mod_vcard, []},
{mod_offline, []},
{mod_echo, [{host, "echo.jabber.dbc.mtview.ca.us"}]},
{mod_private, []},
{mod_muc, []},
{mod_pubsub, []},
{mod_time, []},
{mod_last, []},
{mod_version, []}
]}.
% Local Variables:
% mode: erlang
% End:
+19
View File
@@ -34,12 +34,14 @@
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), pos_integer()}}).
-else.
-record(sql_query, {hash :: binary(),
format_query :: fun(),
format_res :: fun(),
args :: fun(),
flags :: non_neg_integer(),
loc :: {module(), {pos_integer(), pos_integer()}}}).
-endif.
@@ -48,3 +50,20 @@
boolean :: fun((boolean()) -> binary()),
in_array_string :: fun((binary()) -> binary()),
like_escape :: fun(() -> binary())}).
-record(sql_index, {columns,
unique = false :: boolean()}).
-record(sql_column, {name :: binary(),
type,
default = false,
opts = []}).
-record(sql_table, {name :: binary(),
columns :: [#sql_column{}],
indices = [] :: [#sql_index{}],
post_create}).
-record(sql_schema, {version :: integer(),
tables :: [#sql_table{}],
update = []}).
-record(sql_references, {table :: binary(),
column :: binary()}).
+2
View File
@@ -62,5 +62,7 @@
_ -> 'Elixir.Logger':bare_log(error, io_lib:format(Format, Args), [?MODULE])
end).
-type re_mp() :: {re_pattern, _, _, _, _}. % Copied from re.erl
%% Uncomment if you want to debug p1_fsm/gen_fsm
%%-define(DBGFSM, true).
+2 -2
View File
@@ -38,7 +38,7 @@
description = <<"">> :: binary(),
allow_change_subj = true :: boolean(),
allow_query_users = true :: boolean(),
allow_private_messages = true :: boolean(),
allowpm = anyone :: anyone | participants | moderators | none,
allow_private_messages_from_visitors = anyone :: anyone | moderators | nobody ,
allow_visitor_status = true :: boolean(),
allow_visitor_nickchange = true :: boolean(),
@@ -125,7 +125,7 @@
roles = #{} :: roles(),
history = #lqueue{} :: lqueue(),
subject = [] :: [text()],
subject_author = <<"">> :: binary(),
subject_author = {<<"">>, #jid{}} :: {binary(), jid()},
hats_users = #{} :: map(), % FIXME on OTP 21+: #{ljid() => #{binary() => binary()}},
just_created = erlang:system_time(microsecond) :: true | integer(),
activity = treap:empty() :: treap:treap(),
+2 -2
View File
@@ -36,8 +36,8 @@ defmodule Ejabberd.Config do
case force do
true ->
Ejabberd.Config.Store.stop
Ejabberd.Config.Store.start_link
Ejabberd.Config.Store.stop()
Ejabberd.Config.Store.start_link()
do_init(file_path)
false ->
if not init_already_executed, do: do_init(file_path)
+3 -3
View File
@@ -14,15 +14,15 @@ defmodule Mix.Tasks.Ejabberd.Deps.Tree do
def run(_argv) do
# First we need to start manually the store to be available
# during the compilation of the config file.
Ejabberd.Config.Store.start_link
Ejabberd.Config.Store.start_link()
Ejabberd.Config.init(:ejabberd_config.path())
Mix.shell.info "ejabberd modules"
Mix.shell().info "ejabberd modules"
Ejabberd.Config.Store.get(:modules)
|> Enum.reverse # Because of how mods are stored inside the store
|> format_mods
|> Mix.shell.info
|> Mix.shell().info
end
defp format_mods(mods) when is_list(mods) do
+64 -15
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/17/2023
.\" Date: 10/16/2023
.\" Manual: \ \&
.\" Source: \ \&
.\" Language: English
.\"
.TH "EJABBERD\&.YML" "5" "04/17/2023" "\ \&" "\ \&"
.TH "EJABBERD\&.YML" "5" "10/16/2023" "\ \&" "\ \&"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -82,7 +82,7 @@ All options can be changed in runtime by running \fIejabberdctl reload\-config\f
.sp
Some options can be specified for particular virtual host(s) only using \fIhost_config\fR or \fIappend_host_config\fR options\&. Such options are called \fIlocal\fR\&. Examples are \fImodules\fR, \fIauth_method\fR and \fIdefault_db\fR\&. The options that cannot be defined per virtual host are called \fIglobal\fR\&. Examples are \fIloglevel\fR, \fIcertfiles\fR and \fIlisten\fR\&. It is a configuration mistake to put \fIglobal\fR options under \fIhost_config\fR or \fIappend_host_config\fR section \- ejabberd will refuse to load such configuration\&.
.sp
It is not recommended to write ejabberd\&.yml from scratch\&. Instead it is better to start from "default" configuration file available at https://github\&.com/processone/ejabberd/blob/23\&.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/23\&.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"
@@ -303,7 +303,9 @@ acme:
.PP
\fBallow_contrib_modules\fR: \fItrue | false\fR
.RS 4
Whether to allow installation of third\-party modules or not\&. The default value is
Whether to allow installation of third\-party modules or not\&. See
ejabberd\-contrib
documentation section\&. The default value is
\fItrue\fR\&.
.RE
.PP
@@ -396,6 +398,18 @@ Same as
\fIcache_size\fR
will be used\&.
.RE
.sp
\fINote\fR about the next option: added in 23\&.10:
.PP
\fBauth_external_user_exists_check\fR: \fItrue | false\fR
.RS 4
Supplement check for user existence based on
\fImod_last\fR
data, for authentication methods that don\(cqt have a way to reliable tell if user exists (like is the case for
\fIjwt\fR
and certificate based authentication)\&. This helps with processing offline message for those users\&. The default value is
\fItrue\fR\&.
.RE
.PP
\fBauth_method\fR: \fI[mnesia | sql | anonymous | external | jwt | ldap | pam, \&.\&.\&.]\fR
.RS 4
@@ -881,6 +895,16 @@ Disallows the usage of those options in the included file
\fIFilename\fR\&. The options that match this criteria are not accepted\&. The default value is an empty list\&.
.RE
.RE
.sp
\fINote\fR about the next option: added in 23\&.10:
.PP
\fBinstall_contrib_modules\fR: \fI[Module, \&.\&.\&.]\fR
.RS 4
Modules to install from
ejabberd\-contrib
at start time\&. The default value is an empty list of modules:
\fI[]\fR\&.
.RE
.PP
\fBjwt_auth_only_rule\fR: \fIAccessName\fR
.RS 4
@@ -1115,7 +1139,7 @@ This option can be used to tune tick time parameter of
Whether to use
\fInew\fR
SQL schema\&. All schemas are located at
https://github\&.com/processone/ejabberd/tree/23\&.04/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
https://github\&.com/processone/ejabberd/tree/23\&.10/sql\&. There are two schemas available\&. The default legacy schema allows to store one XMPP domain into one ejabberd database\&. The
\fInew\fR
schema allows to handle several XMPP domains in a single ejabberd database\&. Using this
\fInew\fR
@@ -3930,6 +3954,8 @@ This module provides support for XEP\-0045: Multi\-User Chat\&. Users can discov
.sp
The MUC service allows any Jabber ID to register a nickname, so nobody else can use that nickname in any room in the MUC service\&. To register a nickname, open the Service Discovery in your XMPP client and register in the MUC service\&.
.sp
It is also possible to register a nickname in a room, so nobody else can use that nickname in that room\&. If a nick is registered in the MUC service, that nick cannot be registered in any room, and vice versa: a nick that is registered in a room cannot be registered at the MUC service\&.
.sp
This module supports clustering and load balancing\&. One module can be started per cluster node\&. Rooms are distributed at creation time on all available MUC module instances\&. The multi\-user chat module is clustered but the rooms themselves are not clustered nor fault\-tolerant: if the node managing a set of rooms goes down, the rooms disappear and they will be recreated on an available node on first connection attempt\&.
.sp
.it 1 an-trap
@@ -3972,12 +3998,14 @@ To configure who is allowed to modify the
room option\&. The default value is
\fIall\fR, which means everyone is allowed to modify that option\&.
.RE
.sp
\fINote\fR about the next option: improved in 23\&.10:
.PP
\fBaccess_register\fR: \fIAccessName\fR
.RS 4
This option specifies who is allowed to register nickname within the Multi\-User Chat service\&. The default is
This option specifies who is allowed to register nickname within the Multi\-User Chat service and rooms\&. The default is
\fIall\fR
for backward compatibility, which means that any user is allowed to register any free nick\&.
for backward compatibility, which means that any user is allowed to register any free nick in the MUC service and in the rooms\&.
.RE
.sp
\fINote\fR about the next option: added in 22\&.05:
@@ -4009,12 +4037,6 @@ Allow occupants to change the subject\&. The default value is
\fItrue\fR\&.
.RE
.PP
\fBallow_private_messages\fR: \fItrue | false\fR
.RS 4
Occupants can send private messages to other occupants\&. The default value is
\fItrue\fR\&.
.RE
.PP
\fBallow_private_messages_from_visitors\fR: \fIanyone | moderators | nobody\fR
.RS 4
Visitors can send private messages to other occupants\&. The default value is
@@ -4059,6 +4081,12 @@ Allow visitors in a moderated room to request voice\&. The default value is
\fItrue\fR\&.
.RE
.PP
\fBallowpm\fR: \fIanyone | participants | moderators | none\fR
.RS 4
Who can send private messages\&. The default value is
\fIanyone\fR\&.
.RE
.PP
\fBanonymous\fR: \fItrue | false\fR
.RS 4
The room is anonymous: occupants don\(cqt see the real JIDs of other occupants\&. Note that the room moderators can always see the real JIDs of the occupants\&. The default value is
@@ -4675,6 +4703,15 @@ or a conference JID is appended to the
otherwise\&. There is no default value\&.
.RE
.RE
.SS "mod_muc_occupantid"
.sp
This module implements XEP\-0421: Anonymous unique occupant identifiers for MUCs\&.
.sp
When the module is enabled, the feature is enabled in all semi\-anonymous rooms\&.
.sp
This module is available since ejabberd 23\&.10\&.
.sp
The module has no options\&.
.SS "mod_muc_rtbl"
.sp
This module implement Real\-time blocklists for MUC rooms\&.
@@ -5197,6 +5234,8 @@ This module adds support for XEP\-0049: Private XML Storage\&.
.sp
Using this method, XMPP entities can store private data on the server, retrieve it whenever necessary and share it between multiple connected clients of the same user\&. The data stored might be anything, as long as it is a valid XML\&. One typical usage is storing a bookmark of all user\(cqs conferences (XEP\-0048: Bookmarks)\&.
.sp
It also implements the bookmark conversion described in XEP\-0402: PEP Native Bookmarks, see the command bookmarks_to_pep\&.
.sp
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
@@ -5938,6 +5977,16 @@ If this option is set to
\fItrue\fR, the sender\(cqs JID is included with push notifications generated for incoming messages with a body\&. The default value is
\fIfalse\fR\&.
.RE
.sp
\fINote\fR about the next option: added in 23\&.10:
.PP
\fBnotify_on\fR: \fImessages | all\fR
.RS 4
If this option is set to
\fImessages\fR, notifications are generated only for actual chat messages with a body text (or some encrypted payload)\&. If it\(cqs set to
\fIall\fR, any kind of XMPP stanza will trigger a notification\&. If unsure, it\(cqs strongly recommended to stick to
\fIall\fR, which is the default value\&.
.RE
.PP
\fBuse_cache\fR: \fItrue | false\fR
.RS 4
@@ -7748,13 +7797,13 @@ TODO
ProcessOne\&.
.SH "VERSION"
.sp
This document describes the configuration file of ejabberd 23\&.04\&. Configuration options of other ejabberd versions may differ significantly\&.
This document describes the configuration file of ejabberd 23\&.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/23\&.04/ejabberd\&.yml\&.example
Default configuration file: https://github\&.com/processone/ejabberd/blob/23\&.10/ejabberd\&.yml\&.example
.sp
Main site: https://ejabberd\&.im
.sp
+7 -8
View File
@@ -8,7 +8,7 @@ defmodule Ejabberd.MixProject do
elixir: elixir_required_version(),
elixirc_paths: ["lib"],
compile_path: ".",
compilers: [:asn1] ++ Mix.compilers,
compilers: [:asn1] ++ Mix.compilers(),
erlc_options: erlc_options(),
erlc_paths: ["asn1", "src"],
# Elixir tests are starting the part of ejabberd they need
@@ -114,7 +114,7 @@ defmodule Ejabberd.MixProject do
{:p1_utils, "~> 1.0"},
{:pkix, "~> 1.0"},
{:stringprep, ">= 1.0.26"},
{:xmpp, ">= 1.6.2"},
{:xmpp, git: "https://github.com/processone/xmpp.git", ref: "68cb07d5d0f36d5c51bfea496c638f3ee9b36027", override: true},
{:yconf, "~> 1.0"}]
++ cond_deps()
end
@@ -138,7 +138,7 @@ defmodule Ejabberd.MixProject do
{config(:zlib), {:ezlib, "~> 1.0"}},
{if_version_below('22', true), {:lager, "~> 3.9.1"}},
{config(:lua), {:luerl, "~> 1.0"}},
{config(:mysql), {:p1_mysql, "~> 1.0.20"}},
{config(:mysql), {:p1_mysql, " >= 1.0.22"}},
{config(:pgsql), {:p1_pgsql, "~> 1.1"}},
{config(:sqlite), {:sqlite3, "~> 1.1"}},
{config(:stun), {:stun, "~> 1.0"}}], do:
@@ -260,7 +260,7 @@ defmodule Ejabberd.MixProject do
end
# Mix/Elixir lower than 1.11.0 use config/releases.exs instead of runtime.exs
case Version.match?(System.version, "~> 1.11") do
case Version.match?(System.version(), "~> 1.11") do
true ->
:ok
false ->
@@ -271,8 +271,7 @@ defmodule Ejabberd.MixProject do
Mix.Generator.copy_template("ejabberdctl.example1", "ejabberdctl.example2", assigns)
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2> ejabberdctl.example2a")
Mix.Generator.copy_template("ejabberdctl.example2a", "ejabberdctl.example2b", assigns)
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2b > ejabberdctl.example3")
execute.("sed -e 's|^ERLANG_NODE=ejabberd@localhost|ERLANG_NODE=ejabberd|g' ejabberdctl.example3 > ejabberdctl.example4")
execute.("sed -e 's|{{\\(\[_a-z\]*\\)}}|<%= @\\1 %>|g' ejabberdctl.example2b > ejabberdctl.example4")
execute.("sed -e 's|^ERLANG_OPTS=\"|ERLANG_OPTS=\"-boot ../releases/#{release.version}/start_clean -boot_var RELEASE_LIB ../lib |' ejabberdctl.example4 > ejabberdctl.example5")
execute.("sed -e 's|^INSTALLUSER=|ERL_OPTIONS=\"-setcookie \\$\\(cat \"\\${SCRIPT_DIR%/*}/releases/COOKIE\")\"\\nINSTALLUSER=|g' ejabberdctl.example5 > ejabberdctl.example6")
Mix.Generator.copy_template("ejabberdctl.example6", "#{ro}/bin/ejabberdctl", assigns)
@@ -339,7 +338,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
def run(args) do
{opts, _, _} = OptionParser.parse(args, switches: [force: :boolean])
project = Mix.Project.config
project = Mix.Project.config()
source_paths = project[:asn1_paths] || ["asn1"]
dest_paths = project[:asn1_target] || ["src"]
mappings = Enum.zip(source_paths, dest_paths)
@@ -361,7 +360,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
end
def manifests, do: [manifest()]
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
def clean, do: Erlang.clean(manifest())
end
+10 -10
View File
@@ -1,12 +1,12 @@
%{
"base64url": {:hex, :base64url, "1.0.1", "f8c7f2da04ca9a5d0f5f50258f055e1d699f0e8bf4cfdb30b750865368403cf6", [:rebar3], [], "hexpm", "f9b3add4731a02a9b0410398b475b33e7566a695365237a6bdee1bb447719f5c"},
"cache_tab": {:hex, :cache_tab, "1.0.30", "6d35eecfb65fbe5fc85988503a27338d32de01243f3fc8ea3ee7161af08725a4", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "6d8a5e00d8f84c42627706a6dbedb02e34d58495f3ed61935c8475ca0531cda0"},
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"eimp": {:hex, :eimp, "1.0.22", "fa9b376ef0b50e8455db15c7c11dea4522c6902e04412288aab436d26335f6eb", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "b3b9ffb1d9a5f4a2ba88ac418a819164932d9a9d3a2fc3d32ca338ce855c4392"},
"epam": {:hex, :epam, "1.0.12", "2a5625d4133bca4b3943791a3f723ba764455a461ae9b6ba5debb262efcf4b40", [:rebar3], [], "hexpm", "54c166c4459cef72f2990a3d89a8f0be27180fe0ab0f24b28ddcc3b815f49f7f"},
"eredis": {:hex, :eredis, "1.2.0", "0b8e9cfc2c00fa1374cd107ea63b49be08d933df2cf175e6a89b73dd9c380de4", [:rebar3], [], "hexpm", "d9b5abef2c2c8aba8f32aa018203e0b3dc8b1157773b254ab1d4c2002317f1e1"},
"esip": {:hex, :esip, "1.0.49", "7949c288d1e094cb44bff5499231939e34c2ace06de8bef950a341edb1743357", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.7", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "a1379ced50c3a2a8f82a77b3184e94c3b87782e90e5ddc0d2baf5b654ecfaa11"},
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
"esip": {:hex, :esip, "1.0.50", "e657d3af332c711311f4eb540e73eb540ea485a25977aef8736fb8cd3845ed9f", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}, {:stun, "1.2.10", [hex: :stun, repo: "hexpm", optional: false]}], "hexpm", "7dfb9f16c65c5e49eeba77025d0f894b5fb240be745d11b978ea1438cd47533d"},
"ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"},
"ezlib": {:hex, :ezlib, "1.0.12", "ffe906ba10d03aaee7977e1e0e81d9ffc3bb8b47fb9cd8e2e453507a2e56221f", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "30e94355fb42260aab6e12582cb0c56bf233515e655c8aeaf48760e7561e4ebb"},
"fast_tls": {:hex, :fast_tls, "1.1.16", "85fa7f3112ea4ff5ccb4f3abadc130a8c855ad74eb00869487399cb0c322d208", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "aa08cca89b4044e74f1f12e399817d8beaeae3ee006c98a893c0bfb1d81fba51"},
"fast_xml": {:hex, :fast_xml, "1.1.49", "67d9bfcadd04efd930e0ee1412b5ea09d3e791f1fdbd4d3e9a8c8f29f8bfed8c", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "01da064d2f740818956961036637fee2475c17bf8aab9442217f90dc77883593"},
@@ -16,20 +16,20 @@
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"luerl": {:hex, :luerl, "1.0.0", "1b68c30649323590d5339b967b419260500ffe520cd3abc1987482a82d3b5a6c", [:rebar3], [], "hexpm", "c17bc45cb4b0845ec975387f9a5d8c81ab60456698527a29c96f78992af86bd1"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [: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", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"mqtree": {:hex, :mqtree, "1.0.15", "bc54d8b88698fdaebc1e27a9ac43688b927e3dbc05bd5cee4057e69a89a8cf17", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "294ac43c9b3d372e24eeea56c259e19c655522dcff64a55c401a639663b9d829"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
"p1_acme": {:hex, :p1_acme, "1.0.22", "b40a8031ef0f4592e97e6a8e08e53dbd31a2198cb8377b249f0caea4f8025a1d", [:rebar3], [{:base64url, "1.0.1", [hex: :base64url, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jiffy, "1.1.1", [hex: :jiffy, repo: "hexpm", optional: false]}, {:jose, "1.11.5", [hex: :jose, repo: "hexpm", optional: false]}, {:yconf, "1.0.15", [hex: :yconf, repo: "hexpm", optional: false]}], "hexpm", "c2b25a7b295a435dac4f278a73d8417ff2b0020c45e1683504e8692ef03e2057"},
"p1_mysql": {:hex, :p1_mysql, "1.0.21", "5972add935e7b1b03d981fa88a0d01e96de357443eaf96ca2fb62e465a717f47", [:rebar3], [], "hexpm", "16f197adb99dab034139c429b256d65948a4057d3e4d553adbe5ce3236c4aabf"},
"p1_mysql": {:hex, :p1_mysql, "1.0.22", "593107adbce3df1bb460fd442320ff540dfba0232f45278441ef8d2de8d08163", [:rebar3], [], "hexpm", "188f04b3ba265a6e7657c67a6780a2f5f973fe86670159ef593aaf44dbd3d5a2"},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.11", "96b4e85c08355720523c2f892011a81a07994d15c179ce4dd82d704fecad15b2", [:rebar3], [], "hexpm", "9c3c6ae59382b9525473bb02a32949889808f33f95f6db10594fd92acd1f63db"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.22", "f2ca59b87e8c5dbbbe84d08e307c21f078384232e8fb78a783dbeffa6d37da28", [:rebar3], [{:xmpp, "1.6.2", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "bbc38d3878c7b58ab86c257a2a2ce1bacbd68a5034ebea2735db6a70c1aa12bc"},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.23", "4a8c17b642dcf5265a910d1a0b86ffb2a9dd057c7b51c3def5eacbcc4f27ced9", [:rebar3], [{:xmpp, "1.7.0", [hex: :xmpp, repo: "hexpm", optional: false]}], "hexpm", "819222bcb5a74581263282ff9cdc679adeefc663dcf49ddc778aeaae90be05a6"},
"p1_utils": {:hex, :p1_utils, "1.0.25", "2d39b5015a567bbd2cc7033eeb93a7c60d8c84efe1ef69a3473faa07fa268187", [:rebar3], [], "hexpm", "9219214428f2c6e5d3187ff8eb9a8783695c2427420be9a259840e07ada32847"},
"pkix": {:hex, :pkix, "1.0.9", "eb20b2715d71a23b4fe7e754dae9281a964b51113d0bba8adf9da72bf9d65ac2", [:rebar3], [], "hexpm", "daab2c09cdd4eda05c9b45a5c00e994a1a5f27634929e1377e2e59b707103e3a"},
"sqlite3": {:hex, :sqlite3, "1.1.14", "f9ea0cff8540865fdfdb7e24eef34dc46677364b1c070896e99b5bf08c8a7fd7", [:rebar3], [], "hexpm", "85054b6ca297343c159ed6794a473ff2c8eeabd854b6fe02f711c0bfd373ce86"},
"stringprep": {:hex, :stringprep, "1.0.29", "02f23e8c3a219a3dfe40a22e908bece3a2f68af0ff599ea8a7b714ecb21e62ee", [:rebar3], [{:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "928eba304c3006eb1512110ebd7b87db163b00859a09375a1e4466152c6c462a"},
"stun": {:hex, :stun, "1.2.7", "d6bdcf0aa72c927fbe8b779fc4ef1f3916c5450b2ff136c800a7a0361fb1ddff", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "3fb1f07aaa630b2276e83d267557d1ceb3d2ce52d1145de71864160210655852"},
"stun": {:hex, :stun, "1.2.10", "53f8be69e14f9476dcaf1dfb626b9dad2380f3fba8faf2c30bdf74311cfdc008", [:rebar3], [{:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:p1_utils, "1.0.25", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "19d3eecbfcc6935f0880f8ef7e77ff373900c604092937a1acda166ae3fb40e9"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"xmpp": {:hex, :xmpp, "1.6.2", "8045dfea83e8996415b9a5161f685cb97dc3c40c0b9c46763a5eca2408017221", [:rebar3], [{:ezlib, "1.0.12", [hex: :ezlib, repo: "hexpm", optional: false]}, {:fast_tls, "1.1.16", [hex: :fast_tls, repo: "hexpm", optional: false]}, {:fast_xml, "1.1.49", [hex: :fast_xml, repo: "hexpm", optional: false]}, {:idna, "6.0.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", "db2ee6115961fe159bc2629093797ac4535083176817cdbe2ae186a0ff540fde"},
"xmpp": {:git, "https://github.com/processone/xmpp.git", "68cb07d5d0f36d5c51bfea496c638f3ee9b36027", [ref: "68cb07d5d0f36d5c51bfea496c638f3ee9b36027"]},
"yconf": {:hex, :yconf, "1.0.15", "e22998b3d7728270bdd06162a9515bd142b14fae8927cbdbd3ef639c32aa6f7a", [:rebar3], [{:fast_yaml, "1.0.36", [hex: :fast_yaml, repo: "hexpm", optional: false]}], "hexpm", "7ff2ab24d3c9833842716b9aaaa01a8f96641a7695cbb701b03445c4def01117"},
}
+539
View File
@@ -0,0 +1,539 @@
%% Generated automatically
%% DO NOT EDIT: run `make translations` instead
%% To improve translations please read:
%% https://docs.ejabberd.im/developer/extending-ejabberd/localization/
{" (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 a hat to a user","Добави шапка към потребител"}.
{"Add Jabber ID","Добави Jabber ID"}.
{"Add New","Добави нов"}.
{"Add User","Добави потребител"}.
{"Administration of ","Администриране на "}.
{"Administration","Администриране"}.
{"Administrator privileges required","Изискватт се администраторски права"}.
{"All activity","Цялата статистика"}.
{"All Users","Всички потребители"}.
{"Allow subscription","Разреши абониране"}.
{"Allow this Jabber ID to subscribe to this pubsub node?","Позволявате ли това Jabber ID да се абонира за pubsub нода?"}.
{"Allow this person to register with the room?","Позволявате ли този потребителя да се регистрира в стаята?"}.
{"Allow users to change the subject","Позволи потребителите да сменят темата"}.
{"Allow users to query other users","Позволи на потребителите да правят заявки към други потребители"}.
{"Allow users to send invites","Разреши на потребителите да изпращат покани"}.
{"Allow users to send private messages","Разреши на потребителите да изпращат лични съобщения"}.
{"Allow visitors to change nickname","Разреши на посетителите да променят псевдонима си"}.
{"Allow visitors to send private messages to","Разреши на потребителите да изпращат лични съобщения до"}.
{"Allow visitors to send status text in presence updates","Разреши на посетителите да изпращат текст за състоянието в актуализациите за присъствие"}.
{"Allow visitors to send voice requests","Разреши на посетителите да изпращат заявки за гласово повикване"}.
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Асоциирана LDAP група, която определя членството в стая; това трябва да бъде LDAP отличително име според специфично за изпълнението/внедряването определение на група."}.
{"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","Всеки може да асоциира \"leaf\" нодове с колекцията"}.
{"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","Всеки"}.
{"April","Април"}.
{"Attribute 'channel' is required for this request","Атрибутът 'канал' е задължителен за тази заявка"}.
{"Attribute 'id' is mandatory for MIX messages","Атрибутът 'id' е задължителен за MIX съобщения"}.
{"Attribute 'jid' is not allowed here","Атрибутът 'jid' не е разрешен тук"}.
{"Attribute 'node' is not allowed here","Атрибутът 'нод' не е разрешен тук"}.
{"Attribute 'to' of stanza that triggered challenge","Атрибут 'до' на строфата, който е предизвикал предизвикателството"}.
{"August","Август"}.
{"Automatic node creation is not enabled","Автоматичното създаване на нод не е включено"}.
{"Backup Management","Управление на архивирането"}.
{"Backup of ~p","Резервно копие на ~p"}.
{"Backup to File at ","Архивиране във файл на "}.
{"Backup","Резервно копие"}.
{"Bad format","Лош формат"}.
{"Birthday","Рожден ден"}.
{"Both the username and the resource are required","Изискват се потребителското име и ресурсът"}.
{"Bytestream already activated","Bytestream вече е активиран"}.
{"Cannot remove active list","Активният списък не може да бъде премахнат"}.
{"Cannot remove default list","Не можете да премахнете списъка по подразбиране"}.
{"CAPTCHA web page","CAPTCHA уеб страница"}.
{"Challenge ID","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","Конфигурацията на стаята за чат е променена"}.
{"Chatroom is created","Стаята за чат е създадена"}.
{"Chatroom is destroyed","Стаята за чат е унищожена"}.
{"Chatroom is started","Стаята за чат е стартирана"}.
{"Chatroom is stopped","Стаята за чат е спряна"}.
{"Chatrooms","Чат стаи"}.
{"Choose a username and password to register with this server","Изберете потребителско име и парола, за да се регистрирате на този сървър"}.
{"Choose storage type of tables","Изберете тип за съхранение на таблици"}.
{"Choose whether to approve this entity's subscription.","Изберете дали да одобрите абонамента на този субект."}.
{"City","Град"}.
{"Client acknowledged more stanzas than sent by server","Клиентът потвърди повече строфи от изпратените от сървъра"}.
{"Commands","Команди"}.
{"Conference room does not exist","Конферентната стая не съществува"}.
{"Configuration of room ~s","Конфигурация на стая ~s"}.
{"Configuration","Конфигурация"}.
{"Connected Resources:","Свързани ресурси:"}.
{"Contact Addresses (normally, room owner or owners)","Адреси за контакт (обикновено собственик или собственици на стая)"}.
{"Contrib Modules","Сътруднически модули"}.
{"Country","Държава"}.
{"CPU Time:","Процесорно време:"}.
{"Current Discussion Topic","Текуща тема на дискусита"}.
{"Database failure","Грешка в базата данни"}.
{"Database Tables at ~p","Таблици на базата данни при ~p"}.
{"Database Tables Configuration at ","Конфигурация на таблиците в базата данни при "}.
{"Database","База данни"}.
{"December","Декември"}.
{"Default users as participants","Потребители по подразбиране като участници"}.
{"Delete content","Изтрий съдържанието"}.
{"Delete message of the day on all hosts","Изтрий съобщението на деня от всички нодове"}.
{"Delete message of the day","Изтрий съобщението на деня"}.
{"Delete Selected","Изтрий избраните"}.
{"Delete table","Изтрий таблицата"}.
{"Delete User","Изтрий потребителя"}.
{"Deliver event notifications","Достави известията за събития"}.
{"Deliver payloads with event notifications","Достави прикачените обекти с известията за събития"}.
{"Description:","Описание:"}.
{"Disc only copy","Копие само на диска"}.
{"'Displayed groups' not added (they do not exist!): ","'Показаните групи' не са добавени (не съществуват!): "}.
{"Displayed:","Показва се:"}.
{"Don't tell your password to anybody, not even the administrators of the XMPP server.","Не казвайте паролата си на никого, дори на администраторите на XMPP сървъра."}.
{"Dump Backup to Text File at ","Архивиране в текстов файл при "}.
{"Dump to Text File","Архивиране в текстов файл"}.
{"Duplicated groups are not allowed by RFC6121","Дублирани групи не са разрешени от RFC6121"}.
{"Dynamically specify a replyto of the item publisher","Динамично задаване на отговор към публикувалия елемента"}.
{"Edit Properties","Редактиране на свойства"}.
{"Either approve or decline the voice request.","Одобрете или отхвърлете заявката за гласова връзка."}.
{"Elements","Елементи"}.
{"Email Address","Имейл адрес"}.
{"Email","Илейл"}.
{"Enable hats","Активиране на шапки"}.
{"Enable logging","Активирай запис на хронологията"}.
{"Enable message archiving","Активирай архивиране на съобщенията"}.
{"Enabling push without 'node' attribute is not supported","Активиране на известията без атрибут 'нод' не се поддържа"}.
{"End User Session","Прекрати сесията на потребителя"}.
{"Enter nickname you want to register","Въведете псевдонима, който желаете да регистрирате"}.
{"Enter path to backup file","Въведете пътя към архивния файл"}.
{"Enter path to jabberd14 spool dir","Въведете пътя към jabberd14 spool директорията"}.
{"Enter path to jabberd14 spool file","Въведете пътя към jabberd14 spool файла"}.
{"Enter path to text file","Въведете пътя към текстовия файл"}.
{"Enter the text you see","Въведете текста, който виждате"}.
{"Erlang XMPP Server","Erlang XMPP сървър"}.
{"Error","Грешка"}.
{"Exclude Jabber IDs from CAPTCHA challenge","Изключи CAPTCHA предизвикателство за следните 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):"}.
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Експортирай данните за потребителите на този хост в PIEFXIS файлове (XEP-0227):"}.
{"External component failure","Неуспех породен от външен компонент"}.
{"External component timeout","Времето за изчакване на външен компонент изтече"}.
{"Failed to activate bytestream","Неуспешно активиране на bytestream"}.
{"Failed to extract JID from your voice request approval","Неуспешно извличане на JID от одобрението за гласова заявка"}.
{"Failed to map delegated namespace to external component","Неуспешно съпоставяне на делегирано пространство от имена с външен компонент"}.
{"Failed to parse HTTP response","Неуспешно анализиран HTTP отговор"}.
{"Failed to process option '~s'","Неуспешо обработена опция '~s'"}.
{"Family Name","Фамилно име"}.
{"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"}.
{"From","От"}.
{"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","Виж чакащи"}.
{"Get User Last Login Time","Покажи времето, когато потребителят е влязъл за последно"}.
{"Get User Password","Покажи паролата на потребителя"}.
{"Get User Statistics","Покажи статистика за потребителя"}.
{"Given Name","Име"}.
{"Grant voice to this person?","Предоставяне на глас за потребителя?"}.
{"Groups that will be displayed to the members","Групи, които ще се показват на членовете"}.
{"Groups","Групи"}.
{"Group","Група"}.
{"Hat title","Заглавие на шапката"}.
{"Hat URI","URI адрес за шапка"}.
{"Hats limit exceeded","Превишен е лимитът за шапка"}.
{"Host unknown","Неизвестен хост"}.
{"Host","Хост"}.
{"HTTP File Upload","Качване на файл по HTTP"}.
{"Idle connection","Неактивна връзка"}.
{"If you don't see the CAPTCHA image here, visit the web page.","Ако не виждате CAPTCHA изображението, посетете уеб страницата."}.
{"Import Directory","Импорт на директория"}.
{"Import File","Импорт на файл"}.
{"Import user data from jabberd14 spool file:","Импорт на потребители от jabberd14 Spool файл:"}.
{"Import User from File at ","Импорт на потребител от файл на "}.
{"Import users data from a PIEFXIS file (XEP-0227):","Импорт на потребителски данни от PIEFXIS файл (XEP-0227):"}.
{"Import users data from jabberd14 spool directory:","Импорт на потребители от jabberd14 Spool директория:"}.
{"Import Users from Dir at ","Импорт на потребители от директория на "}.
{"Import Users From jabberd14 Spool Files","Импорт на потребители от jabberd14 Spool файлове"}.
{"Improper domain part of 'from' attribute","Неправилна част за домейн в атрибута 'from'"}.
{"Improper message type","Неправилен тип съобщение"}.
{"Incoming s2s Connections:","Входящи s2s връзки:"}.
{"Incorrect CAPTCHA submit","Неправилно CAPTCHA въвеждане"}.
{"Incorrect data form","Неправилна форма на данните"}.
{"Incorrect password","Грешна парола"}.
{"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' в препратеното съобщение"}.
{"Invalid node name","Невалидно име на нода"}.
{"Invalid 'previd' value","Невалидна стойност на 'previd'"}.
{"Invitations are not allowed in this conference","Поканите не са разрешени в тази конференция"}.
{"IP addresses","IP адреси"}.
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не е позволено да изпращате съобщения за грешки в стаята. Участникът (~s) е изпратил съобщение за грешка (~s) и е бил отстранен от стаята"}.
{"It is not allowed to send private messages of type \"groupchat\"","Изпращането на лични съобщения от тип \"групов чат\" не е разрешено"}.
{"It is not allowed to send private messages to the conference","Изпращането на лични съобщения до конференцията не е разрешено"}.
{"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 канали:"}.
{"July","Юли"}.
{"June","Юни"}.
{"Just created","Току що създаден"}.
{"Label:","Етикет:"}.
{"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 хеш на текст трябва да са равни на шестнайсетичния етикет"}.
{"List of rooms","Списък на стаите"}.
{"List of users with hats","Списък на потребителите с шапки"}.
{"List users with hats","Избройте потребителите с шапки"}.
{"Logging","Регистриране на събития"}.
{"Low level update script","Скрипт за актуализация на ниско ниво"}.
{"Make participants list public","Направи списъка с участниците публичен"}.
{"Make room CAPTCHA protected","Защити стаята с CAPTCHA"}.
{"Make room members-only","Направи стаята само за членове"}.
{"Make room moderated","Направи стая модерирана"}.
{"Make room password protected","Защити стаята с парола"}.
{"Make room persistent","Направи стая постоянна"}.
{"Make room public searchable","Направи стаята да е публично търсена"}.
{"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","Май"}.
{"Members not added (inexistent vhost!): ","Членовете не са добавени (несъществуващ vhost!): "}.
{"Membership is required to enter this room","Изисква се членство, за вход в тази стая"}.
{"Members:","Членове:"}.
{"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 няма автоматичен начин за възстановяване на паролата в случай, че я забравите."}.
{"Memory","Памет"}.
{"Mere Availability in XMPP (No Show Value)","Наличност в XMPP (Не показвай стойност)"}.
{"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","Модератор"}.
{"Modified modules","Модифицирани модули"}.
{"Module failed to handle the query","Модулът не успя да обработи заявката"}.
{"Monday","Понеделник"}.
{"Multicast","Мултикаст"}.
{"Multiple <item/> elements are not allowed by RFC6121","Повече от един <item/> елемента не се разрешават от RFC6121"}.
{"Multi-User Chat","Групов чат (MUC)"}.
{"Name in the rosters where this group will be displayed","Име в списъците с контакти, където ще се показва тази група"}.
{"Name","Име"}.
{"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","Никога"}.
{"New Password:","Нова парола:"}.
{"Nickname can't be empty","Псевдонимът не може да бъде празен"}.
{"Nickname Registration at ","Регистрация на псевдоним в "}.
{"Nickname ~s does not exist in the room","Псевдонимът ~s не присъства в стаята"}.
{"Nickname","Псевдоним"}.
{"No address elements found","Не е намерен адресен елемент"}.
{"No addresses element found","Не са намерени адресни елементи"}.
{"No 'affiliation' attribute found","Атрибут 'affiliation' не е намерен"}.
{"No available resource found","Не е намерен наличен ресурс"}.
{"No body provided for announce message","Не е предоставен текст за съобщение тип обява"}.
{"No child elements found","Не са открити подчинени елементи"}.
{"No data form found","Не е намерена форма за данни"}.
{"No Data","Няма данни"}.
{"No features available","Няма налични функции"}.
{"No <forwarded/> element found","Елементът <forwarded/> не е намерен"}.
{"No hook has processed this command","Никоя кука не е обработила тази команда"}.
{"No info about last activity found","Няма информация за последна активновт"}.
{"No 'item' element found","Елементът 'item' не е намерен"}.
{"No items found in this query","Няма намерени елементи в тази заявка"}.
{"No limit","Няма ограничение"}.
{"No module is handling this query","Нито един модул не обработва тази заявка"}.
{"No node specified","Не е посочен нод"}.
{"No 'password' found in data form","Не е намерен 'password' във формата за данни"}.
{"No 'password' found in this query","В заявката не е намерен 'password'"}.
{"No 'path' found in data form","Не е намерен 'path' във формата за данни"}.
{"No pending subscriptions found","Не са намерени чакащи абонаменти"}.
{"No privacy list with this name found","Не е намерен списък за поверителност с това име"}.
{"No private data found in this query","Няма открити лични данни в тази заявка"}.
{"No running node found","Не е намерен работещ нод"}.
{"No 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","Индексът на нода не е намерен"}.
{"Node not found","Нодът не е намерен"}.
{"Node ~p","Нод ~p"}.
{"Nodeprep has failed","Nodeprep е неуспешен"}.
{"Nodes","Нодове"}.
{"Node","Нод"}.
{"None","Нито един"}.
{"Not allowed","Не е разрешено"}.
{"Not Found","Не е намерен"}.
{"Not subscribed","Няма абонамент"}.
{"Notify subscribers when items are removed from the node","Уведоми абонатите, когато елементите бъдат премахнати от нода"}.
{"Notify subscribers when the node configuration changes","Уведоми абонатите, когато конфигурацията на нода се промени"}.
{"Notify subscribers when the node is deleted","Уведоми абонатите, когато нодът бъде изтрит"}.
{"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","Октомври"}.
{"Offline Messages","Офлайн съобщения"}.
{"Offline Messages:","Офлайн съобщения:"}.
{"OK","ДОБРЕ"}.
{"Old Password:","Стара парола:"}.
{"Online Users","Онлайн потребители"}.
{"Online Users:","Онлайн потребители:"}.
{"Online","Онлайн"}.
{"Only admins can see this","Само администратори могат да видят това"}.
{"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 subscribe and retrieve items","Само тези в белия списък могат да се абонират и да извличат елементи"}.
{"Organization Name","Име на организацията"}.
{"Organization Unit","Отдел"}.
{"Other Modules Available:","Други налични модули:"}.
{"Outgoing s2s Connections","Изходящи s2s връзки"}.
{"Outgoing s2s Connections:","Изходящи s2s връзки:"}.
{"Owner privileges required","Изискват се привилегии на собственик"}.
{"Packet relay is denied by service policy","Предаването на пакети е отказано от политиката на услугата"}.
{"Packet","Пакет"}.
{"Participant ID","ID на участник"}.
{"Participant","Участник"}.
{"Password Verification","Проверка на паролата"}.
{"Password Verification:","Проверка на паролата:"}.
{"Password","Парола"}.
{"Password:","Парола:"}.
{"Path to Dir","Път към директория"}.
{"Path to File","Път до файл"}.
{"Pending","В очакване"}.
{"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, трябва да направите резервно копие на SQL базата данни отделно."}.
{"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","PID от предишната сесия е унищожен"}.
{"Previous session PID has exited","Предишният PID на сесията е излязъл"}.
{"Previous session PID is dead","PID от предишната сесия не съществува"}.
{"Previous session timed out","Времето на предишната сесия изтече"}.
{"Public","Публичен"}.
{"Publish model","Модел за публикуване"}.
{"Publish-Subscribe","Публикуване-Абониране"}.
{"PubSub subscriber request","Заявка от абонат за PubSub"}.
{"Purge all items when the relevant publisher goes offline","Изчисти всички елементи, когато съответният издател премине в режим офлайн"}.
{"Push record not found","Push записът не е намерен"}.
{"Queries to the conference members are not allowed in this room","В тази зала не се допускат запитвания към членовете на конференцията"}.
{"Query to another users is forbidden","Заявка към други потребители е забранена"}.
{"RAM and disc copy","Копие в RAM и на диск"}.
{"RAM copy","Копие в RAM"}.
{"Really delete message of the day?","Наистина ли желаете да изтриете съобщението на деня?"}.
{"Receive notification from all descendent nodes","Получаване на известие от всички низходящи нодове"}.
{"Receive notification from direct child nodes only","Получаване на известия само от директни подчинени нодове"}.
{"Receive notification of new items only","Получаване на известия само за нови елементи"}.
{"Receive notification of new nodes only","Получаване на известия само за нови нодове"}.
{"Recipient is not in the conference room","Получателят не е в конферентната зала"}.
{"Register an XMPP account","Регистрирай XMPP акаунт"}.
{"Registered Users","Регистрирани потребители"}.
{"Registered Users:","Регистрирани потребители:"}.
{"Register","Регистрирай"}.
{"Remote copy","Отдалечено копие"}.
{"Remove a hat from a user","Премахни шапка от потребител"}.
{"Remove All Offline Messages","Премахни всички офлайн съобщения"}.
{"Remove User","Премахни потребител"}.
{"Remove","Премахни"}.
{"Replaced by new connection","Заменен от нова връзка"}.
{"Request has timed out","Времето за заявка изтече"}.
{"Request is ignored","Заявката е игнорирано"}.
{"Requested role","Заявена роля"}.
{"Resources","Ресурси"}.
{"Restart Service","Рестартирай услугата"}.
{"Restart","Рестартирай"}.
{"Restore Backup from File at ","Възстанови резервно копие от файл в "}.
{"Restore binary backup after next ejabberd restart (requires less memory):","Възстановяване на бинарно копие след следващото рестартиране на ejabberd (изисква по-малко памет):"}.
{"Restore binary backup immediately:","Възстанови незабавно двоично копие:"}.
{"Restore plain text backup immediately:","Възстановете незабавно копие от обикновен текст:"}.
{"Restore","Възстанови"}.
{"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","Създаването на стая е отказано поради политика на услугата"}.
{"Room description","Описание на стаята"}.
{"Room Occupants","Обитатели на стаята"}.
{"Room terminates","Стаята се прекратява"}.
{"Room title","Заглавие на стаята"}.
{"Roster groups allowed to subscribe","Групи от списъци с контакти, на които е разрешено да се абонират"}.
{"Roster of ~ts","Списък с контакти на ~ts"}.
{"Roster size","Размер на списъка с контакти"}.
{"Roster:","Списък с контакти:"}.
{"RPC Call Error","Грешка при RPC повикване"}.
{"Running Nodes","Работещи нодове"}.
{"Saturday","Събота"}.
{"Script check","Проверка на скрипт"}.
{"Search from the date","Търси от дата"}.
{"Search Results for ","Резултати от търсенето за "}.
{"Search the text","Търси текста"}.
{"Search until the date","Търси до дата"}.
{"Search users in ","Търси потребители в "}.
{"Select All","Избери всички"}.
{"Send announcement to all online users on all hosts","Изпрати съобщение до всички онлайн потребители на всички хостове"}.
{"Send announcement to all online users","Изпрати съобщение до всички онлайн потребители"}.
{"Send announcement to all users on all hosts","Изпрати съобщение до всички потребители на всички хостове"}.
{"Send announcement to all users","Изпрати съобщение до всички потребители"}.
{"September","Септември"}.
{"Server:","Сървър:"}.
{"Service list retrieval timed out","Времето за изчакване на извличането на списъка с услуги изтече"}.
{"Session state copying timed out","Времето за изчакване на копирането на състоянието на сесията изтече"}.
{"Set message of the day and send to online users","Задай съобщение на деня и го изпрати на онлайн потребителите"}.
{"Set message of the day on all hosts and send to online users","Задавай съобщение на деня на всички хостове и изпрати на онлайн потребителите"}.
{"Shared Roster Groups","Споделени групи от списъци с контакти"}.
{"Show Integral Table","Покажи интегрална таблица"}.
{"Show Ordinary Table","Покажи обикновена таблица"}.
{"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","Невалидно ID на строфата"}.
{"Stanza ID","ID на строфа"}.
{"Statically specify a replyto of the node owner(s)","Статично задаване на replyto на собственика(ците) на нода"}.
{"Statistics of ~p","Статистики на ~p"}.
{"Statistics","Статистики"}.
{"Stopped Nodes","Спрени нодове"}.
{"Stop","Спри"}.
{"Storage Type","Тип хранилище"}.
{"Store binary backup:","Запази бинарен архив:"}.
{"Store plain text backup:","Запази архив като обикновен текст:"}.
{"Stream management is already enabled","Управлението на потока вече е активирано"}.
{"Stream management is not enabled","Управлението на потока не е активирано"}.
{"Subject","Тема"}.
{"Submitted","Изпратено"}.
{"Submit","Изпрати"}.
{"Subscriber Address","Адрес на абоната"}.
{"Subscribers may publish","Абонатите могат да публикуват"}.
{"Subscription requests must be approved and only subscribers may retrieve items","Заявките за абонамент трябва да бъдат одобрени и само абонатите могат да извличат елементи"}.
{"Subscriptions are not allowed","Абонаментите не са разрешени"}.
{"Subscription","Абонамент"}.
{"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.","CAPTCHA предизвикателството е валидно."}.
{"The CAPTCHA verification has failed","Проверката на CAPTCHA предизвикателството е неуспешна"}.
{"The captcha you entered is wrong","Въведеният captcha код е грешен"}.
{"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 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 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 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/> елемент"}.
{"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 трансформацията, която може да се приложи към формата на прикачените данни, за да се генерира валиден резултат от Data Forms, който клиентът може да покаже с помощта на общ механизъм за визуализация на Data Forms."}.
{"View Roster","Преглед на списъка с контакти"}.
+7 -6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Una Pàgina Web"}.
{"Accept","Acceptar"}.
{"Access denied by service policy","Accés denegat per la política del servei"}.
{"Access model of authorize","Model d'Accés de autoritzar"}.
{"Access model of open","Model d'Accés de obert"}.
{"Access model of presence","Model d'Accés de presència"}.
{"Access model of roster","Model d'Accés de contactes"}.
{"Access model of whitelist","Model d'Accés de llista blanca"}.
{"Access model","Model d'Accés"}.
{"Account doesn't exist","El compte no existeix"}.
{"Action on user","Acció en l'usuari"}.
@@ -53,6 +48,7 @@
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Qualsevol amb una subscripció de presencia de 'both' o 'from' pot subscriure's i publicar elements"}.
{"Anyone with Voice","Qualsevol amb Veu"}.
{"Anyone","Qualsevol"}.
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentment el teu compte no te privilegis d'administrador en este servidor. Per favor consulta com obtindre privilegis d'administrador en: https://docs.ejabberd.im/admin/installation/#administration-account"}.
{"April","Abril"}.
{"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"}.
@@ -230,7 +226,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No està permés enviar missatges d'error a la sala. El participant (~s) ha enviat un missatge d'error (~s) i ha sigut expulsat de la sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","No està permés enviar missatges del tipus \"groupchat\""}.
{"It is not allowed to send private messages to the conference","No està permès l'enviament de missatges privats a la sala"}.
{"It is not allowed to send private messages","No està permés enviar missatges privats"}.
{"Jabber ID","ID Jabber"}.
{"January","Gener"}.
{"JID normalization denied by service policy","S'ha denegat la normalització del JID per política del servei"}.
@@ -375,6 +370,7 @@
{"Only members may query archives of this room","Només membres poden consultar l'arxiu de missatges d'aquesta sala"}.
{"Only moderators and participants are allowed to change the subject in this room","Només els moderadors i participants poden canviar el tema d'aquesta sala"}.
{"Only moderators are allowed to change the subject in this room","Només els moderadors poden canviar el tema d'aquesta sala"}.
{"Only moderators are allowed to retract messages","Només els moderadors tenen permís per a retractar missatges"}.
{"Only moderators can approve voice requests","Només els moderadors poden aprovar les peticions de veu"}.
{"Only occupants are allowed to send messages to the conference","Sols els ocupants poden enviar missatges a la sala"}.
{"Only occupants are allowed to send queries to the conference","Sols els ocupants poden enviar sol·licituds a la sala"}.
@@ -398,6 +394,7 @@
{"Password:","Contrasenya:"}.
{"Path to Dir","Ruta al directori"}.
{"Path to File","Ruta al fitxer"}.
{"Payload semantic type information","Informació sobre el tipus semàntic de la carrega útil"}.
{"Pending","Pendent"}.
{"Period: ","Període: "}.
{"Persist items to storage","Persistir elements al guardar"}.
@@ -496,6 +493,7 @@
{"Specify the access model","Especificar el model d'accés"}.
{"Specify the event message type","Especifica el tipus de missatge d'event"}.
{"Specify the publisher model","Especificar el model del publicant"}.
{"Stanza id is not valid","L'identificador del paquet no es vàlid"}.
{"Stanza ID","ID del paquet"}.
{"Statically specify a replyto of the node owner(s)","Especifica estaticament una adreça on respondre al propietari del node"}.
{"Statistics of ~p","Estadístiques de ~p"}.
@@ -560,6 +558,7 @@
{"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 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"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","El paquet DEU contindre només un element <active/>, un element <default/>, o un element <list/>"}.
{"The subscription identifier associated with the subscription request","L'identificador de subscripció associat amb la petició de subscripció"}.
@@ -661,6 +660,7 @@
{"Whether to allow subscriptions","Permetre subscripcions"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Si fer totes les subscripcions temporals, basat en la presencia del subscriptor"}.
{"Whether to notify owners about new subscribers and unsubscribes","Si notificar als propietaris sobre noves subscripcions i desubscripcions"}.
{"Who can send private messages","Qui pot enviar missatges privats"}.
{"Who may associate leaf nodes with a collection","Qui pot associar nodes fulla amb una col·lecció"}.
{"Wrong parameters in the web formulary","Paràmetres incorrectes en el formulari web"}.
{"Wrong xmlns","El xmlns ès incorrecte"}.
@@ -672,6 +672,7 @@
{"XMPP Show Value of XA (Extended Away)","Valor 'show' de XMPP: XA (Molt Ausent)"}.
{"XMPP URI of Associated Publish-Subscribe Node","URI XMPP del Node Associat Publish-Subscribe"}.
{"You are being removed from the room because of a system shutdown","Has sigut expulsat de la sala perquè el sistema va a apagar-se"}.
{"You are not allowed to send private messages","No tens permés enviar missatges privats"}.
{"You are not joined to the channel","No t'has unit al canal"}.
{"You can later change your password using an XMPP client.","Podràs canviar la teva contrasenya més endavant utilitzant un client XMPP."}.
{"You have been banned from this room","Has sigut bloquejat en aquesta sala"}.
-1
View File
@@ -159,7 +159,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Není povoleno posílat chybové zprávy do místnosti. Účastník (~s) odeslal chybovou zprávu (~s) a byl vyhozen z místnosti"}.
{"It is not allowed to send private messages of type \"groupchat\"","Není dovoleno odeslání soukromé zprávy typu \"skupinová zpráva\" "}.
{"It is not allowed to send private messages to the conference","Není povoleno odesílat soukromé zprávy v této místnosti"}.
{"It is not allowed to send private messages","Je zakázáno posílat soukromé zprávy"}.
{"Jabber ID","Jabber ID"}.
{"January",". ledna"}.
{"joins the room","vstoupil(a) do místnosti"}.
+3 -6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Eine Webseite"}.
{"Accept","Akzeptieren"}.
{"Access denied by service policy","Zugriff aufgrund der Dienstrichtlinien verweigert"}.
{"Access model of authorize","Zugriffsmodell von 'authorize'"}.
{"Access model of open","Zugriffsmodell von 'open'"}.
{"Access model of presence","Zugriffsmodell von 'presence'"}.
{"Access model of roster","Zugriffsmodell der Kontaktliste"}.
{"Access model of whitelist","Zugriffsmodell von 'whitelist'"}.
{"Access model","Zugriffsmodell"}.
{"Account doesn't exist","Konto existiert nicht"}.
{"Action on user","Aktion auf Benutzer"}.
@@ -229,11 +224,11 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Es ist nicht erlaubt Fehlermeldungen an den Raum zu senden. Der Teilnehmer (~s) hat eine Fehlermeldung (~s) gesendet und wurde aus dem Raum geworfen"}.
{"It is not allowed to send private messages of type \"groupchat\"","Es ist nicht erlaubt private Nachrichten des Typs \"groupchat\" zu senden"}.
{"It is not allowed to send private messages to the conference","Es ist nicht erlaubt private Nachrichten an die Konferenz zu senden"}.
{"It is not allowed to send private messages","Es ist nicht erlaubt private Nachrichten zu senden"}.
{"Jabber ID","Jabber-ID"}.
{"January","Januar"}.
{"JID normalization denied by service policy","JID-Normalisierung aufgrund der Dienstrichtlinien verweigert"}.
{"JID normalization failed","JID-Normalisierung fehlgeschlagen"}.
{"Joined MIX channels of ~ts","Beigetretene MIX-Channels von ~ts"}.
{"Joined MIX channels:","Beigetretene MIX-Channels:"}.
{"joins the room","betritt den Raum"}.
{"July","Juli"}.
@@ -373,6 +368,7 @@
{"Only members may query archives of this room","Nur Mitglieder dürfen den Verlauf dieses Raumes abrufen"}.
{"Only moderators and participants are allowed to change the subject in this room","Nur Moderatoren und Teilnehmer dürfen das Thema in diesem Raum ändern"}.
{"Only moderators are allowed to change the subject in this room","Nur Moderatoren dürfen das Thema in diesem Raum ändern"}.
{"Only moderators are allowed to retract messages","Nur Moderatoren dürfen Nachrichten zurückziehen"}.
{"Only moderators can approve voice requests","Nur Moderatoren können Sprachrecht-Anforderungen genehmigen"}.
{"Only occupants are allowed to send messages to the conference","Nur Teilnehmer dürfen Nachrichten an die Konferenz senden"}.
{"Only occupants are allowed to send queries to the conference","Nur Teilnehmer dürfen Anfragen an die Konferenz senden"}.
@@ -494,6 +490,7 @@
{"Specify the access model","Geben Sie das Zugangsmodell an"}.
{"Specify the event message type","Geben Sie den Ereignisnachrichtentyp an"}.
{"Specify the publisher model","Geben Sie das Veröffentlichermodell an"}.
{"Stanza id is not valid","Stanza-ID ist ungültig"}.
{"Stanza ID","Stanza-ID"}.
{"Statically specify a replyto of the node owner(s)","Ein 'replyto' des/der Nodebesitzer(s) statisch angeben"}.
{"Statistics of ~p","Statistiken von ~p"}.
-6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Μία ιστοσελίδα"}.
{"Accept","Αποδοχή"}.
{"Access denied by service policy","Άρνηση πρόσβασης, λόγω τακτικής παροχής υπηρεσιών"}.
{"Access model of authorize","Μοντέλο πρόσβασης της πιστοποίησης"}.
{"Access model of open","Μοντέλο πρόσβασης του ανοικτού"}.
{"Access model of presence","Μοντέλο πρόσβασης της παρουσίας"}.
{"Access model of roster","Μοντέλο πρόσβασης της Λίστας Επαφών"}.
{"Access model of whitelist","Μοντέλο πρόσβασης της Λευκής Λίστας"}.
{"Access model","Καθορίστε το μοντέλο πρόσβασης"}.
{"Account doesn't exist","Ο λογαριασμός δεν υπάρχει"}.
{"Action on user","Eνέργεια για το χρήστη"}.
@@ -219,7 +214,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Δεν επιτρέπεται η αποστολή μηνυμάτων σφάλματος στο δωμάτιο. Ο συμμετέχων (~s) έχει στείλει ένα μήνυμα σφάλματος (~s) και έχει πεταχτεί έξω από την αίθουσα"}.
{"It is not allowed to send private messages of type \"groupchat\"","Δεν επιτρέπεται η αποστολή προσωπικών μηνυμάτων του τύπου \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Δεν επιτρέπεται να στείλει προσωπικά μηνύματα για τη διάσκεψη"}.
{"It is not allowed to send private messages","Δεν επιτρέπεται η αποστολή προσωπικών μηνυμάτων"}.
{"Jabber ID","Ταυτότητα Jabber"}.
{"January","Ιανουάριος"}.
{"JID normalization denied by service policy","Απετράπη η κανονικοποίηση του JID, λόγω της τακτικής Παροχής Υπηρεσιών"}.
-4
View File
@@ -12,9 +12,6 @@
{"A Web Page","Retpaĝo"}.
{"Accept","Akcepti"}.
{"Access denied by service policy","Atingo rifuzita de serv-politiko"}.
{"Access model of open","Atingomodelo de malfermo"}.
{"Access model of presence","Atingomodelo de ĉeesto"}.
{"Access model of whitelist","Atingomodelo de permesolisto"}.
{"Access model","Atingomodelo"}.
{"Account doesn't exist","Konto ne ekzistas"}.
{"Action on user","Ago je uzanto"}.
@@ -163,7 +160,6 @@
{"is now known as","nun nomiĝas"}.
{"It is not allowed to send private messages of type \"groupchat\"","Malpermesas sendi mesaĝojn de tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Nur partoprenantoj rajtas sendi privatajn mesaĝojn al la babilejo"}.
{"It is not allowed to send private messages","Ne estas permesata sendi privatajn mesaĝojn"}.
{"Jabber ID","Jabber ID"}.
{"January","Januaro"}.
{"joins the room","eniras la babilejo"}.
+8 -7
View File
@@ -12,11 +12,6 @@
{"A Web Page","Una página web"}.
{"Accept","Aceptar"}.
{"Access denied by service policy","Acceso denegado por la política del servicio"}.
{"Access model of authorize","Modelo de acceso de Autorizar"}.
{"Access model of open","Modelo de acceso de Abierto"}.
{"Access model of presence","Modelo de acceso de Presencia"}.
{"Access model of roster","Modelo de acceso de Roster"}.
{"Access model of whitelist","Modelo de acceso de Lista Blanca"}.
{"Access model","Modelo de Acceso"}.
{"Account doesn't exist","La cuenta no existe"}.
{"Action on user","Acción en el usuario"}.
@@ -53,6 +48,7 @@
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Cualquiera con una suscripción a la presencia de 'ambos' o 'de' puede suscribirse y recibir elementos"}.
{"Anyone with Voice","Cualquiera con Voz"}.
{"Anyone","Cualquiera"}.
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentemente tu cuenta no tiene permisos de administración en este servidor. Por favor consulta cómo concederle privilegios de administrador en: https://docs.ejabberd.im/admin/installation/#administration-account"}.
{"April","Abril"}.
{"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"}.
@@ -230,7 +226,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","No está permitido enviar mensajes de error a la sala. Este participante (~s) ha enviado un mensaje de error (~s) y fue expulsado de la sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","No está permitido enviar mensajes privados del tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir el envio de mensajes privados a la sala"}.
{"It is not allowed to send private messages","No está permitido enviar mensajes privados"}.
{"Jabber ID","Jabber ID"}.
{"January","Enero"}.
{"JID normalization denied by service policy","Se ha denegado la normalización del JID por política del servicio"}.
@@ -290,7 +285,7 @@
{"Modified modules","Módulos modificados"}.
{"Module failed to handle the query","El módulo falló al gestionar la petición"}.
{"Monday","Lunes"}.
{"Multicast","Multicast"}.
{"Multicast","Multidifusión"}.
{"Multiple <item/> elements are not allowed by RFC6121","No se permiten múltiples elementos <item/> en RFC6121"}.
{"Multi-User Chat","Salas de Charla"}.
{"Name in the rosters where this group will be displayed","Nombre del grupo con que aparecerá en las listas de contactos"}.
@@ -375,6 +370,7 @@
{"Only members may query archives of this room","Solo miembros pueden consultar el archivo de mensajes de la sala"}.
{"Only moderators and participants are allowed to change the subject in this room","Solo los moderadores y participantes pueden cambiar el asunto de esta sala"}.
{"Only moderators are allowed to change the subject in this room","Solo los moderadores pueden cambiar el asunto de esta sala"}.
{"Only moderators are allowed to retract messages","Solo los moderadores pueden retractarse de los mensajes"}.
{"Only moderators can approve voice requests","Solo los moderadores pueden aprobar peticiones de voz"}.
{"Only occupants are allowed to send messages to the conference","Solo los ocupantes pueden enviar mensajes a la sala"}.
{"Only occupants are allowed to send queries to the conference","Solo los ocupantes pueden enviar solicitudes a la sala"}.
@@ -398,6 +394,7 @@
{"Password:","Contraseña:"}.
{"Path to Dir","Ruta al directorio"}.
{"Path to File","Ruta al fichero"}.
{"Payload semantic type information","Información sobre el tipo semántico de la carga útil"}.
{"Pending","Pendiente"}.
{"Period: ","Periodo: "}.
{"Persist items to storage","Persistir elementos al almacenar"}.
@@ -496,6 +493,7 @@
{"Specify the access model","Especifica el modelo de acceso"}.
{"Specify the event message type","Especifica el tipo del mensaje de evento"}.
{"Specify the publisher model","Especificar el modelo del publicante"}.
{"Stanza id is not valid","El identificador de la estrofa no es válido"}.
{"Stanza ID","ID del paquete"}.
{"Statically specify a replyto of the node owner(s)","Especificar de forma estática un 'replyto' de dueño(s) del nodo"}.
{"Statistics of ~p","Estadísticas de ~p"}.
@@ -560,6 +558,7 @@
{"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 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"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","El paquete DEBE contener solo un elemento <active/>, un elemento <default/>, o un elemento <list/>"}.
{"The subscription identifier associated with the subscription request","El identificador de suscripción asociado con la petición de suscripción"}.
@@ -661,6 +660,7 @@
{"Whether to allow subscriptions","Permitir subscripciones"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Si hacer que todas las suscripciones sean temporales, basado en la presencia del suscriptor"}.
{"Whether to notify owners about new subscribers and unsubscribes","Si notificar a los dueños sobre nuevas suscripciones y desuscripciones"}.
{"Who can send private messages","Quién puede enviar mensajes privados"}.
{"Who may associate leaf nodes with a collection","Quien puede asociar nodos hoja con una colección"}.
{"Wrong parameters in the web formulary","Parámetros incorrectos en el formulario web"}.
{"Wrong xmlns","XMLNS incorrecto"}.
@@ -672,6 +672,7 @@
{"XMPP Show Value of XA (Extended Away)","Valor 'Show' de XMPP: XA (Ausente Extendido)"}.
{"XMPP URI of Associated Publish-Subscribe Node","URI XMPP del Nodo Asociado de Publicar-Subscribir"}.
{"You are being removed from the room because of a system shutdown","Estás siendo expulsado de la sala porque el sistema se va a detener"}.
{"You are not allowed to send private messages","No tienes permitido enviar mensajes privados"}.
{"You are not joined to the channel","No has entrado en el canal"}.
{"You can later change your password using an XMPP client.","Puedes cambiar tu contraseña después, usando un cliente XMPP."}.
{"You have been banned from this room","Has sido bloqueado en esta sala"}.
-6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Une page Web"}.
{"Accept","Accepter"}.
{"Access denied by service policy","L'accès au service est refusé"}.
{"Access model of authorize","Modèle daccès de « autoriser »"}.
{"Access model of open","Modèle daccès de « ouvrir »"}.
{"Access model of presence","Modèle daccès de « présence »"}.
{"Access model of roster","Modèle daccès de « liste »"}.
{"Access model of whitelist","Modèle daccès de « liste blanche »"}.
{"Access model","Modèle daccès"}.
{"Account doesn't exist","Le compte nexiste pas"}.
{"Action on user","Action sur l'utilisateur"}.
@@ -220,7 +215,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","L'envoyer de messages d'erreur au salon n'est pas autorisé. Le participant (~s) à envoyé un message d'erreur (~s) et à été expulsé du salon"}.
{"It is not allowed to send private messages of type \"groupchat\"","Il n'est pas permis d'envoyer des messages privés de type \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Il n'est pas permis d'envoyer des messages privés à la conférence"}.
{"It is not allowed to send private messages","L'envoi de messages privés n'est pas autorisé"}.
{"Jabber ID","Jabber ID"}.
{"January","Janvier"}.
{"joins the room","rejoint le salon"}.
-1
View File
@@ -159,7 +159,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Non está permitido enviar mensaxes de erro á sala. Este participante (~s) enviou unha mensaxe de erro (~s) e foi expulsado da sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","Non está permitido enviar mensaxes privadas do tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir o envio de mensaxes privadas á sala"}.
{"It is not allowed to send private messages","Non está permitido enviar mensaxes privadas"}.
{"Jabber ID","Jabber ID"}.
{"January","Xaneiro"}.
{"joins the room","entra na sala"}.
-1
View File
@@ -148,7 +148,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","אין זה מותר לשלוח הודעות שגיאה לחדר. משתתף זה (~s) שלח הודעת שגיאה (~s) ונבעט מתוך החדר"}.
{"It is not allowed to send private messages of type \"groupchat\"","אין זה מותר לשלוח הודעות פרטיות מן טיפוס \"groupchat\""}.
{"It is not allowed to send private messages to the conference","אין זה מותר לשלוח הודעות פרטיות לועידה"}.
{"It is not allowed to send private messages","אין זה מותר לשלוח הודעות פרטיות"}.
{"Jabber ID","מזהה Jabber"}.
{"January","ינואר"}.
{"joins the room","נכנס/ת אל החדר"}.
-1
View File
@@ -170,7 +170,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Nem engedélyezett hibaüzeneteket küldeni a szobába. A résztvevő (~s) hibaüzenetet (~s) küldött, és ki lett rúgva a szobából"}.
{"It is not allowed to send private messages of type \"groupchat\"","Nem engedélyezett „groupchat” típusú személyes üzeneteket küldeni"}.
{"It is not allowed to send private messages to the conference","Nem engedélyezett személyes üzeneteket küldeni a konferenciába"}.
{"It is not allowed to send private messages","Nem engedélyezett személyes üzeneteket küldeni"}.
{"Jabber ID","Jabber-azonosító"}.
{"January","január"}.
{"JID normalization denied by service policy","A Jabber-azonosító normalizálása megtagadva a szolgáltatási irányelv miatt"}.
-6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Halaman web"}.
{"Accept","Diterima"}.
{"Access denied by service policy","Akses ditolak oleh kebijakan layanan"}.
{"Access model of authorize","Model akses otorisasi"}.
{"Access model of open","Model akses terbuka"}.
{"Access model of presence","Model akses kehadiran"}.
{"Access model of roster","model akses daftar kontak"}.
{"Access model of whitelist","Model akses daftar putih"}.
{"Access model","Model akses"}.
{"Account doesn't exist","Akun tidak ada"}.
{"Action on user","Tindakan pada pengguna"}.
@@ -207,7 +202,6 @@
{"is now known as","sekarang dikenal sebagai"}.
{"It is not allowed to send private messages of type \"groupchat\"","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi jenis \"groupchat \""}.
{"It is not allowed to send private messages to the conference","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi ke konferensi"}.
{"It is not allowed to send private messages","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi"}.
{"Jabber ID","Jabber ID"}.
{"January","Januari"}.
{"joins the room","bergabung ke ruangan"}.
-1
View File
@@ -125,7 +125,6 @@
{"is now known as","è ora conosciuta/o come"}.
{"It is not allowed to send private messages of type \"groupchat\"","Non è consentito l'invio di messaggi privati di tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Non è consentito l'invio di messaggi privati alla conferenza"}.
{"It is not allowed to send private messages","Non è consentito l'invio di messaggi privati"}.
{"Jabber ID","Jabber ID (Jabber ID)"}.
{"January","Gennaio"}.
{"joins the room","entra nella stanza"}.
-1
View File
@@ -166,7 +166,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","このルームにエラーメッセージを送ることは許可されていません。参加者(~s)はエラーメッセージを(~s)を送信してルームからキックされました。"}.
{"It is not allowed to send private messages of type \"groupchat\"","種別が\"groupchat\" であるプライベートメッセージを送信することはできません"}.
{"It is not allowed to send private messages to the conference","この会議にプライベートメッセージを送信することはできません"}.
{"It is not allowed to send private messages","プライベートメッセージを送信することはできません"}.
{"Jabber ID","Jabber ID"}.
{"January","1月"}.
{"joins the room","がチャットルームに参加しました"}.
-1
View File
@@ -129,7 +129,6 @@
{"is now known as","heet nu"}.
{"It is not allowed to send private messages of type \"groupchat\"","Er mogen geen privéberichten van het type \"groupchat\" worden verzonden"}.
{"It is not allowed to send private messages to the conference","Er mogen geen privéberichten naar de chatruimte worden verzonden"}.
{"It is not allowed to send private messages","Het is niet toegestaan priveberichten te sturen"}.
{"Jabber ID","Jabber ID"}.
{"January","Januari"}.
{"joins the room","betrad de chatruimte"}.
-3
View File
@@ -8,8 +8,6 @@
{"A password is required to enter this room","Et passord kreves for tilgang til samtalerommet"}.
{"Accept","Godta"}.
{"Access denied by service policy","Tilgang nektes på grunn av en tjenesteregel"}.
{"Access model of presence","Tilgangsmodell for tilstedeværelse"}.
{"Access model of roster","Tilgangsmodell for kontaktliste"}.
{"Action on user","Handling på bruker"}.
{"Add Jabber ID","Legg til Jabber-ID"}.
{"Add New","Legg til ny"}.
@@ -141,7 +139,6 @@
{"IP addresses","IP-adresser"}.
{"is now known as","er nå kjent som"}.
{"It is not allowed to send private messages to the conference","Det er ikke tillatt å sende private meldinger til konferansen"}.
{"It is not allowed to send private messages","Det er ikke tillatt å sende private meldinger"}.
{"Jabber ID","Jabber-ID"}.
{"January","januar"}.
{"JID normalization failed","JID-normalisering mislyktes"}.
-1
View File
@@ -159,7 +159,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Użytkownik nie może wysyłać wiadomości o błędach do pokoju. Użytkownik (~s) wysłał błąd (~s) i został wyrzucony z pokoju"}.
{"It is not allowed to send private messages of type \"groupchat\"","Nie można wysyłać prywatnych wiadomości typu \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Nie wolno wysyłac prywatnych wiadomości na konferencję"}.
{"It is not allowed to send private messages","Wysyłanie prywatnych wiadomości jest zabronione"}.
{"Jabber ID","Jabber ID"}.
{"January","Styczeń"}.
{"joins the room","dołącza do pokoju"}.
+11 -10
View File
@@ -12,11 +12,6 @@
{"A Web Page","Uma página da web"}.
{"Accept","Aceito"}.
{"Access denied by service policy","Acesso negado pela política do serviço"}.
{"Access model of authorize","Modelo de acesso da autorização"}.
{"Access model of open","Modelo para acesso aberto"}.
{"Access model of presence","Modelo para acesso presença"}.
{"Access model of roster","Modelo para acesso lista"}.
{"Access model of whitelist","Modelo de acesso da lista branca"}.
{"Access model","Modelo de acesso"}.
{"Account doesn't exist","A conta não existe"}.
{"Action on user","Ação no usuário"}.
@@ -53,6 +48,7 @@
{"Anyone with a presence subscription of both or from may subscribe and retrieve items","Qualquer pessoa com uma assinatura presente dos dois ou de ambos pode se inscrever e recuperar os itens"}.
{"Anyone with Voice","Qualquer pessoa com voz"}.
{"Anyone","Qualquer pessoa"}.
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Aparentemente, a sua conta não tem direitos de administração neste servidor. Verifique como conceder os direitos administrativos em: https://docs.ejabberd.im/admin/installation/#administration-account"}.
{"April","Abril"}.
{"Attribute 'channel' is required for this request","O atributo 'canal' é necessário para esta solicitação"}.
{"Attribute 'id' is mandatory for MIX messages","O atributo 'id' é obrigatório para mensagens MIX"}.
@@ -182,7 +178,7 @@
{"Get User Last Login Time","Obter a Data do Último Login"}.
{"Get User Password","Obter Senha do Usuário"}.
{"Get User Statistics","Obter Estatísticas do Usuário"}.
{"Given Name","Sobrenome"}.
{"Given Name","Prenome"}.
{"Grant voice to this person?","Dar voz a esta pessoa?"}.
{"Group","Grupo"}.
{"Groups that will be displayed to the members","Os grupos que serão exibidos para os membros"}.
@@ -230,7 +226,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro para a sala. O membro (~s) enviou uma mensagem de erro (~s) e foi expulso da sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Não é permitido enviar mensagens privadas para a sala de conferência"}.
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
{"Jabber ID","ID Jabber"}.
{"January","Janeiro"}.
{"JID normalization denied by service policy","Normalização JID negada por causa da política de serviços"}.
@@ -250,7 +245,7 @@
{"Least significant bits of SHA-256 hash of text should equal hexadecimal label","Bits menos significativos do hash sha-256 do texto devem ser iguais ao rótulo hexadecimal"}.
{"leaves the room","Sair da sala"}.
{"List of rooms","Lista de salas"}.
{"List of users with hats","Lista os usuários com chapéus"}.
{"List of users with hats","Lista dos usuários com chapéus"}.
{"List users with hats","Lista os usuários com chapéus"}.
{"Logging","Registrando no log"}.
{"Low level update script","Script de atualização low level"}.
@@ -351,7 +346,7 @@
{"Notify subscribers when the node is deleted","Notificar assinantes quando o nó for eliminado se elimine"}.
{"November","Novembro"}.
{"Number of answers required","Quantidade de respostas necessárias"}.
{"Number of occupants","Número de participantes"}.
{"Number of occupants","Quantidade de ocupantes"}.
{"Number of Offline Messages","Quantidade das mensagens offline"}.
{"Number of online users","Número de usuários online"}.
{"Number of registered users","Número de usuários registrados"}.
@@ -375,6 +370,7 @@
{"Only members may query archives of this room","Somente os membros podem procurar nos arquivos desta sala"}.
{"Only moderators and participants are allowed to change the subject in this room","Somente os moderadores e os participamentes podem alterar o assunto desta sala"}.
{"Only moderators are allowed to change the subject in this room","Somente os moderadores podem alterar o assunto desta sala"}.
{"Only moderators are allowed to retract messages","Apenas moderadores estão autorizados a retirar mensagens"}.
{"Only moderators can approve voice requests","Somente moderadores podem aprovar requisições de voz"}.
{"Only occupants are allowed to send messages to the conference","Somente os ocupantes podem enviar mensagens à sala de conferência"}.
{"Only occupants are allowed to send queries to the conference","Somente os ocupantes podem enviar consultas à sala de conferência"}.
@@ -398,6 +394,7 @@
{"Password:","Senha:"}.
{"Path to Dir","Caminho para o diretório"}.
{"Path to File","Caminho do arquivo"}.
{"Payload semantic type information","Informações de tipo semântico de carga útil"}.
{"Pending","Pendente"}.
{"Period: ","Período: "}.
{"Persist items to storage","Persistir elementos ao armazenar"}.
@@ -458,7 +455,7 @@
{"Room Configuration","Configuração de salas"}.
{"Room creation is denied by service policy","Sala não pode ser criada devido à política do serviço"}.
{"Room description","Descrição da Sala"}.
{"Room Occupants","Número de participantes"}.
{"Room Occupants","Ocupantes do quarto"}.
{"Room terminates","Terminação da sala"}.
{"Room title","Título da sala"}.
{"Roster groups allowed to subscribe","Listar grupos autorizados"}.
@@ -496,6 +493,7 @@
{"Specify the access model","Especificar os modelos de acesso"}.
{"Specify the event message type","Especificar o tipo de mensagem para o evento"}.
{"Specify the publisher model","Especificar o modelo do publicante"}.
{"Stanza id is not valid","A Stanza ID não é válido"}.
{"Stanza ID","ID da estrofe"}.
{"Statically specify a replyto of the node owner(s)","Defina uma resposta fixa do(s) proprietário(s) do nó"}.
{"Statistics of ~p","Estatísticas de ~p"}.
@@ -560,6 +558,7 @@
{"The query is only allowed from local users","Esta consulta só é permitida a partir de usuários locais"}.
{"The query must not contain <item/> elements","A consulta não pode conter elementos <item/>"}.
{"The room subject can be modified by participants","O tema da sala pode ser alterada pelos próprios participantes"}.
{"The semantic type information of data in the node, usually specified by the namespace of the payload (if any)","Informações de tipo semântico dos dados no nó, geralmente especificadas pelo espaço de nomes da carga útil (se houver)"}.
{"The sender of the last received message","O remetente da última mensagem que foi recebida"}.
{"The stanza MUST contain only one <active/> element, one <default/> element, or one <list/> element","A instância DEVE conter apenas um elemento <active/>, um elemento <default/>, ou um elemento <list/>"}.
{"The subscription identifier associated with the subscription request","O identificador da assinatura associado à solicitação da assinatura"}.
@@ -661,6 +660,7 @@
{"Whether to allow subscriptions","Permitir subscrições"}.
{"Whether to make all subscriptions temporary, based on subscriber presence","Caso todas as assinaturas devam ser temporárias, com base na presença do assinante"}.
{"Whether to notify owners about new subscribers and unsubscribes","Caso deva notificar os proprietários sobre os novos assinantes e aqueles que cancelaram a assinatura"}.
{"Who can send private messages","Quem pode enviar mensagens privadas"}.
{"Who may associate leaf nodes with a collection","Quem pode associar as folhas dos nós em uma coleção"}.
{"Wrong parameters in the web formulary","O formulário web está com os parâmetros errados"}.
{"Wrong xmlns","Xmlns errado"}.
@@ -672,6 +672,7 @@
{"XMPP Show Value of XA (Extended Away)","XMPP Exiba o valor do XA (Ausência Estendida)"}.
{"XMPP URI of Associated Publish-Subscribe Node","XMPP URI da publicação do nó associado da assinatura"}.
{"You are being removed from the room because of a system shutdown","Você está sendo removido da sala por causa do desligamento do sistema"}.
{"You are not allowed to send private messages","Você não tem permissão para enviar mensagens privadas"}.
{"You are not joined to the channel","Você não está inscrito no canal"}.
{"You can later change your password using an XMPP client.","Você pode alterar a sua senha mais tarde usando um cliente XMPP."}.
{"You have been banned from this room","Você foi banido desta sala"}.
-6
View File
@@ -12,11 +12,6 @@
{"A Web Page","Uma página da web"}.
{"Accept","Aceito"}.
{"Access denied by service policy","Acesso negado pela política de serviço"}.
{"Access model of authorize","Modelo de acesso da autorização"}.
{"Access model of open","Modelo para acesso aberto"}.
{"Access model of presence","Modelo para acesso presença"}.
{"Access model of roster","Modelo para acesso lista"}.
{"Access model of whitelist","Modelo de acesso da lista branca"}.
{"Access model","Modelo de acesso"}.
{"Account doesn't exist","A conta não existe"}.
{"Action on user","Acção no utilizador"}.
@@ -230,7 +225,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Não é permitido o envio de mensagens de erro para a sala. O membro (~s) enviou uma mensagem de erro (~s) e foi expulso da sala"}.
{"It is not allowed to send private messages of type \"groupchat\"","Não é permitido enviar mensagens privadas do tipo \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Impedir o envio de mensagens privadas para a sala"}.
{"It is not allowed to send private messages","Não é permitido enviar mensagens privadas"}.
{"Jabber ID","ID Jabber"}.
{"January","Janeiro"}.
{"JID normalization denied by service policy","Normalização JID negada por causa da política de serviços"}.
-1
View File
@@ -180,7 +180,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Запрещено посылать сообщения об ошибках в эту комнату. Участник (~s) послал сообщение об ошибке (~s) и был выкинут из комнаты"}.
{"It is not allowed to send private messages of type \"groupchat\"","Нельзя посылать частные сообщения типа \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Не разрешается посылать частные сообщения прямо в конференцию"}.
{"It is not allowed to send private messages","Запрещено посылать приватные сообщения"}.
{"Jabber ID","Jabber ID"}.
{"January","января"}.
{"joins the room","вошёл(а) в комнату"}.
-1
View File
@@ -124,7 +124,6 @@
{"is now known as","sa premenoval(a) na"}.
{"It is not allowed to send private messages of type \"groupchat\"","Nie je dovolené odoslanie súkromnej správy typu \"Skupinová správa\" "}.
{"It is not allowed to send private messages to the conference","Nie je povolené odosielať súkromné správy do konferencie"}.
{"It is not allowed to send private messages","Nieje povolené posielať súkromné správy"}.
{"Jabber ID","Jabber ID"}.
{"January","Január"}.
{"joins the room","vstúpil(a) do miestnosti"}.
-1
View File
@@ -142,7 +142,6 @@
{"IP addresses","Adresa IP"}.
{"is now known as","tani njihet si"}.
{"It is not allowed to send private messages to the conference","Nuk lejohet të dërgohen mesazhe private te konferenca"}.
{"It is not allowed to send private messages","Nuk lejohet të dërgohen mesazhe private"}.
{"Jabber ID","ID Jabber"}.
{"January","Janar"}.
{"JID normalization failed","Normalizimi JID dështoi"}.
-1
View File
@@ -110,7 +110,6 @@
{"is now known as","är känd som"}.
{"It is not allowed to send private messages of type \"groupchat\"","Det är inte tillåtet att skicka privata medelanden med typen \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Det är inte tillåtet att skicka privata medelanden till den här konferensen"}.
{"It is not allowed to send private messages","Det ar inte tillåtet att skicka privata meddelanden"}.
{"Jabber ID","Jabber ID"}.
{"January","Januari"}.
{"joins the room","joinar rummet"}.
-1
View File
@@ -123,7 +123,6 @@
{"is now known as","isim değiştirdi :"}.
{"It is not allowed to send private messages of type \"groupchat\"","\"groupchat\" tipinde özel mesajlar gönderilmesine izin verilmiyor"}.
{"It is not allowed to send private messages to the conference","Konferansa özel mesajlar gönderilmesine izin verilmiyor"}.
{"It is not allowed to send private messages","Özel mesaj gönderilmesine izin verilmiyor"}.
{"Jabber ID","Jabber ID"}.
{"January","Ocak"}.
{"joins the room","odaya katıldı"}.
+38 -33
View File
@@ -34,75 +34,81 @@
{"Allow visitors to send private messages to","Дозволити відвідувачам відсилати приватні повідомлення"}.
{"Allow visitors to send status text in presence updates","Дозволити відвідувачам відсилати текст статусу в оновленнях присутності"}.
{"Allow visitors to send voice requests","Дозволити відвідувачам надсилати голосові запрошення"}.
{"An associated LDAP group that defines room membership; this should be an LDAP Distinguished Name according to an implementation-specific or deployment-specific definition of a group.","Асоційована група LDAP, яка визначає членство в кімнаті; це повинно бути відмінне ім'я LDAP відповідно до специфічного для реалізації або специфічного для розгортання визначення групи."}.
{"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 Voice","Усі, хто має голос"}.
{"April","квітня"}.
{"Attribute 'channel' is required for this request","Для цього запиту потрібен атрибут \"канал\""}.
{"Attribute 'id' is mandatory for MIX messages","Для MIX повідомлень потрібен атрибут \"id\""}.
{"Attribute 'jid' is not allowed here","Атрибут 'jid' тут заборонений"}.
{"Attribute 'node' is not allowed here","Атрибут \"вузол\" тут заборонений"}.
{"August","серпня"}.
{"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","Будь-хто"}.
{"Apparently your account has no administration rights in this server. Please check how to grant admin rights in: https://docs.ejabberd.im/admin/installation/#administration-account","Очевидно, ваш обліковий запис не має прав адміністратора на цьому сервері. Будь ласка, перевірте, як отримати права адміністратора за посиланням: https://docs.ejabberd.im/admin/installation/#administration-account"}.
{"April","Квітень"}.
{"Attribute 'channel' is required for this request","Атрибут \"канал\" є обов'язковим для цього запиту"}.
{"Attribute 'id' is mandatory for MIX messages","Атрибут 'id' обов'язковий для MIX повідомлень"}.
{"Attribute 'jid' is not allowed here","Атрибут 'jid' заборонений"}.
{"Attribute 'node' is not allowed here","Атрибут \"вузол\" заборонений"}.
{"Attribute 'to' of stanza that triggered challenge","Атрибут \"до\" рядка, який спровокував виклик"}.
{"August","Серпень"}.
{"Automatic node creation is not enabled","Автоматичне створення вузлів не ввімкнено"}.
{"Backup Management","Керування резервним копіюванням"}.
{"Backup of ~p","Резервне копіювання ~p"}.
{"Backup to File at ","Резервне копіювання в файл на "}.
{"Backup","Резервне копіювання"}.
{"Bad format","Неправильний формат"}.
{"Bad format","Невірний формат"}.
{"Birthday","День народження"}.
{"Both the username and the resource are required","Потрібне ім'я користувача та ресурс"}.
{"Both the username and the resource are required","Обов'язково потрібне ім'я користувача та джерело"}.
{"Bytestream already activated","Потік байтів вже активовано"}.
{"Cannot remove active list","Неможливо видалити активний список"}.
{"Cannot remove default list","Неможливо видалити список за промовчанням"}.
{"CAPTCHA web page","Адреса капчі"}.
{"Cannot remove default list","Неможливо видалити список за замовчуванням"}.
{"CAPTCHA web page","Веб-сторінка CAPTCHA"}.
{"Challenge ID","ID виклику"}.
{"Change Password","Змінити пароль"}.
{"Change User Password","Змінити Пароль Користувача"}.
{"Change User Password","Змінити пароль користувача"}.
{"Changing password is not allowed","Зміна пароля заборонена"}.
{"Changing role/affiliation is not allowed","Зміна ролі/рангу заборонена"}.
{"Channel already exists","Канал уже існує"}.
{"Channel does not exist","Канал не існує"}.
{"Channel already exists","Канал вже існує"}.
{"Channel does not exist","Каналу не існує"}.
{"Channel JID","Канали JID"}.
{"Channels","Канали"}.
{"Characters not allowed:","Заборонені символи:"}.
{"Chatroom configuration modified","Конфігурація кімнати змінилась"}.
{"Chatroom is created","Створено кімнату"}.
{"Chatroom is destroyed","Знищено кімнату"}.
{"Chatroom is started","Запущено кімнату"}.
{"Chatroom is stopped","Зупинено кімнату"}.
{"Chatroom configuration modified","Конфігурацію кімнати змінено"}.
{"Chatroom is created","Кімнату створено"}.
{"Chatroom is destroyed","Кімнату видалено"}.
{"Chatroom is started","Кімнату запущено"}.
{"Chatroom is stopped","Кімнату зупинено"}.
{"Chatrooms","Кімнати"}.
{"Choose a username and password to register with this server","Виберіть назву користувача та пароль для реєстрації на цьому сервері"}.
{"Choose a username and password to register with this server","Виберіть логін і пароль для реєстрації на цьому сервері"}.
{"Choose storage type of tables","Оберіть тип збереження таблиць"}.
{"Choose whether to approve this entity's subscription.","Вирішіть, чи задовольнити запит цього об'єкту на підписку."}.
{"Choose whether to approve this entity's subscription.","Виберіть, чи підтверджувати підписку."}.
{"City","Місто"}.
{"Client acknowledged more stanzas than sent by server","Клієнт підтвердив більше повідомлень, ніж було відправлено сервером"}.
{"Commands","Команди"}.
{"Conference room does not exist","Конференція не існує"}.
{"Conference room does not exist","Кімната для переговорів відсутня"}.
{"Configuration of room ~s","Конфігурація кімнати ~s"}.
{"Configuration","Конфігурація"}.
{"Connected Resources:","Підключені ресурси:"}.
{"Contact Addresses (normally, room owner or owners)","Контактні адреси (зазвичай, власника або власників кімнати)"}.
{"Country","Країна"}.
{"CPU Time:","Процесорний час:"}.
{"CPU Time:","Час роботи процесора:"}.
{"Current Discussion Topic","Поточна тема обговорення"}.
{"Database failure","Помилка база даних"}.
{"Database failure","Збій бази даних"}.
{"Database Tables at ~p","Таблиці бази даних на ~p"}.
{"Database Tables Configuration at ","Конфігурація таблиць бази даних на "}.
{"Database","База даних"}.
{"December","грудня"}.
{"Default users as participants","Зробити користувачів учасниками за замовчуванням"}.
{"December","Грудень"}.
{"Default users as participants","Користувачі за замовчуванням як учасники"}.
{"Delete content","Видалити вміст"}.
{"Delete message of the day on all hosts","Видалити повідомлення дня на усіх хостах"}.
{"Delete message of the day","Видалити повідомлення дня"}.
{"Delete Selected","Видалити виділені"}.
{"Delete Selected","Видалити вибране"}.
{"Delete table","Видалити таблицю"}.
{"Delete User","Видалити Користувача"}.
{"Delete User","Видалити користувача"}.
{"Deliver event notifications","Доставляти сповіщення про події"}.
{"Deliver payloads with event notifications","Доставляти разом з повідомленнями про публікації самі публікації"}.
{"Description:","Опис:"}.
@@ -207,7 +213,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","Не дозволяється відправляти помилкові повідомлення в кімнату. Учасник (~s) відправив помилкове повідомлення (~s), та був виганий з кімнати"}.
{"It is not allowed to send private messages of type \"groupchat\"","Не дозволяється надсилати приватні повідомлення типу \"groupchat\""}.
{"It is not allowed to send private messages to the conference","Не дозволяється надсилати приватні повідомлення в конференцію"}.
{"It is not allowed to send private messages","Приватні повідомлення не дозволені"}.
{"Jabber ID","Jabber ID"}.
{"January","січня"}.
{"JID normalization failed","Помилка нормалізації JID"}.
-1
View File
@@ -132,7 +132,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","On n' pout nén evoyî des messaedjes d' aroke sol såle. Li pårticipan (~s) a-st evoyî on messaedje d' aroke (~s) ey a stî tapé foû."}.
{"It is not allowed to send private messages of type \"groupchat\"","C' est nén possibe d' evoyî des messaedjes privés del sôre «groupchat»"}.
{"It is not allowed to send private messages to the conference","On n' pout nén evoyî des messaedjes privés dins cisse conferince ci"}.
{"It is not allowed to send private messages","Ci n' est nén permetou d' evoyî des messaedjes privés"}.
{"Jabber ID","ID Jabber"}.
{"January","djanvî"}.
{"joins the room","arive sol såle"}.
-6
View File
@@ -12,11 +12,6 @@
{"A Web Page","网页"}.
{"Accept","接受"}.
{"Access denied by service policy","访问被服务策略拒绝"}.
{"Access model of authorize","授权的访问模型"}.
{"Access model of open","开启的访问模型"}.
{"Access model of presence","状态的访问模型"}.
{"Access model of roster","花名册的访问模型"}.
{"Access model of whitelist","白名单的访问模型"}.
{"Access model","访问模型"}.
{"Account doesn't exist","账号不存在"}.
{"Action on user","对用户的动作"}.
@@ -230,7 +225,6 @@
{"It is not allowed to send error messages to the room. The participant (~s) has sent an error message (~s) and got kicked from the room","不允许将错误消息发送到该房间. 参与者(~s)已发送过一条消息(~s)并已被踢出房间"}.
{"It is not allowed to send private messages of type \"groupchat\"","\"群组聊天\"类型不允许发送私聊消息"}.
{"It is not allowed to send private messages to the conference","不允许向会议发送私聊消息"}.
{"It is not allowed to send private messages","不可以发送私聊消息"}.
{"Jabber ID","Jabber ID"}.
{"January","一月"}.
{"JID normalization denied by service policy","JID规范化被服务策略拒绝"}.
+59 -18
View File
@@ -18,6 +18,10 @@
%%%
%%%----------------------------------------------------------------------
%%%
%%% Dependencies
%%%
{deps, [{base64url, ".*", {git, "https://github.com/dvv/base64url", {tag, "1.0.1"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.30"}}},
{eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.22"}}},
@@ -30,7 +34,7 @@
{if_var_true, redis,
{eredis, ".*", {git, "https://github.com/wooga/eredis", {tag, "v1.2.0"}}}},
{if_var_true, sip,
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.49"}}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.50"}}}},
{if_var_true, zlib,
{ezlib, ".*", {git, "https://github.com/processone/ezlib", {tag, "1.0.12"}}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.16"}}},
@@ -59,10 +63,10 @@
{mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.15"}}},
{p1_acme, ".*", {git, "https://github.com/processone/p1_acme", {tag, "1.0.22"}}},
{if_var_true, mysql,
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.21"}}}},
{p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", {tag, "1.0.22"}}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.11"}}},
{if_var_true, pgsql,
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.22"}}}},
{p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", {tag, "1.1.23"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.25"}}},
{pkix, ".*", {git, "https://github.com/processone/pkix", {tag, "1.0.9"}}},
{if_not_rebar3, %% Needed because modules are not fully migrated to new structure and mix
@@ -72,8 +76,8 @@
{sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3", {tag, "1.1.14"}}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.29"}}},
{if_var_true, stun,
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.7"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.6.2"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.2.10"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.7.0"}}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.15"}}}
]}.
@@ -101,6 +105,21 @@
xmpp,
yconf]}}.
%%%
%%% Compile
%%%
{recursive_cmds, ['configure-deps']}.
{post_hook_configure, [{"eimp", []},
{if_var_true, pam, {"epam", []}},
{if_var_true, sip, {"esip", []}},
{if_var_true, zlib, {"ezlib", []}},
{"fast_tls", []},
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{"fast_yaml", []},
{"stringprep", []}]}.
{erl_first_files, ["src/ejabberd_sql_pt.erl", "src/ejabberd_config.erl",
"src/gen_mod.erl", "src/mod_muc_room.erl",
"src/mod_push.erl", "src/xmpp_socket.erl"]}.
@@ -149,9 +168,22 @@
{keep_build_info, true}.
%%%
%%% Test
%%%
{xref_warnings, false}.
{xref_checks, [deprecated_function_calls]}.
{if_rebar3,
{xref_checks,
[deprecated_function_calls, deprecated_functions, locals_not_used,
undefined_function_calls, undefined_functions]}
}.
{if_not_rebar3,
{xref_checks,
[deprecated_function_calls, deprecated_functions,
undefined_function_calls, undefined_functions]}
}.
{xref_exclusions, [
"(\"gen_transport\":_/_)",
@@ -166,26 +198,35 @@
{if_var_false, sqlite, "(\"sqlite3\":_/_)"},
{if_var_false, zlib, "(\"ezlib\":_/_)"}]}.
{xref_ignores, [{eldap_filter_yecc, return_error, 2} ]}.
{eunit_compile_opts, [{i, "tools"},
{i, "include"}]}.
{dialyzer, [{get_warnings, false}, % Show warnings of dependencies
{if_version_above, "25",
{plt_extra_apps,
[asn1, odbc, public_key, stdlib, syntax_tools,
eredis, idna, jiffy, luerl, jose,
cache_tab, eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
sqlite3, stringprep, stun, xmpp, yconf]},
{plt_extra_apps, % For Erlang/OTP 25 and older
[cache_tab, eimp, epam, esip, ezlib, fast_tls, fast_xml, fast_yaml,
mqtree, p1_acme, p1_mysql, p1_oauth2, p1_pgsql, p1_utils, pkix,
sqlite3, stringprep, stun, xmpp, yconf]}
} ]}.
{ct_opts, [{keep_logs, 20}]}.
{cover_enabled, true}.
{cover_export_enabled, true}.
{coveralls_coverdata, "_build/test/cover/ct.coverdata"}.
{coveralls_service_name, "github"}.
{recursive_cmds, ['configure-deps']}.
{overrides, [
{del, [{erl_opts, [warnings_as_errors]}]}]}.
{post_hook_configure, [{"eimp", []},
{if_var_true, pam, {"epam", []}},
{if_var_true, sip, {"esip", []}},
{if_var_true, zlib, {"ezlib", []}},
{"fast_tls", []},
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{"fast_yaml", []},
{"stringprep", []}]}.
%%%
%%% OTP Release
%%%
{relx, [{release, {ejabberd, {cmd, "grep {vsn, vars.config | sed 's|{vsn, \"||;s|\"}.||' | tr -d '\012'"}},
[ejabberd]},
+4 -2
View File
@@ -232,7 +232,8 @@ LibDir = fun(Name, Suffix) ->
GlobalDepsFilter =
fun(Deps) ->
DepNames = lists:map(fun({DepName, _, _}) -> DepName;
({DepName, _}) -> DepName
({DepName, _}) -> DepName;
(DepName) -> DepName
end, Deps),
lists:filtermap(fun(Dep) ->
case LibDir(atom_to_list(Dep), "") of
@@ -357,7 +358,8 @@ ProcessRelx = fun(Relx, Deps) ->
_ -> []
end,
DepApps = lists:map(fun({DepName, _, _}) -> DepName;
({DepName, _}) -> DepName
({DepName, _}) -> DepName;
(DepName) -> DepName
end, Deps),
[{release, NameVersion, DefaultApps ++ VarsApps ++ ProfileApps ++ DepApps} | RelxTail]
end,
+9 -9
View File
@@ -39,14 +39,14 @@
-type acl_rule() :: {user, {binary(), binary()} | binary()} |
{server, binary()} |
{resource, binary()} |
{user_regexp, {re:mp(), binary()} | re:mp()} |
{server_regexp, re:mp()} |
{resource_regexp, re:mp()} |
{node_regexp, {re:mp(), re:mp()}} |
{user_glob, {re:mp(), binary()} | re:mp()} |
{server_glob, re:mp()} |
{resource_glob, re:mp()} |
{node_glob, {re:mp(), re:mp()}} |
{user_regexp, {re_mp(), binary()} | re_mp()} |
{server_regexp, re_mp()} |
{resource_regexp, re_mp()} |
{node_regexp, {re_mp(), re_mp()}} |
{user_glob, {re_mp(), binary()} | re_mp()} |
{server_glob, re_mp()} |
{resource_glob, re_mp()} |
{node_glob, {re_mp(), re_mp()}} |
{shared_group, {binary(), binary()} | binary()} |
{ip, ip_mask()}.
-type access() :: [{action(), [access_rule()]}].
@@ -348,7 +348,7 @@ node_validator(UV, SV) ->
%%%===================================================================
%%% Aux
%%%===================================================================
-spec match_regexp(iodata(), re:mp()) -> boolean().
-spec match_regexp(iodata(), re_mp()) -> boolean().
match_regexp(Data, RegExp) ->
re:run(Data, RegExp) /= nomatch.
+17 -10
View File
@@ -116,6 +116,11 @@ get_commands_spec() ->
desc = "Stop ejabberd gracefully",
module = ?MODULE, function = stop,
args = [], result = {res, rescode}},
#ejabberd_commands{name = halt, tags = [server],
desc = "Halt ejabberd abruptly with status code 1",
note = "added in 23.10",
module = ejabberd, function = halt,
args = [], result = {res, rescode}},
#ejabberd_commands{name = restart, tags = [server],
desc = "Restart ejabberd gracefully",
module = ?MODULE, function = restart,
@@ -136,8 +141,8 @@ get_commands_spec() ->
desc = "Inform users and rooms, wait, and stop the server",
longdesc = "Provide the delay in seconds, and the "
"announcement quoted, for example: \n"
"ejabberdctl stop_kindly 60 "
"\\\"The server will stop in one minute.\\\"",
"`ejabberdctl stop_kindly 60 "
"\\\"The server will stop in one minute.\\\"`",
module = ?MODULE, function = stop_kindly,
args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
args_example = [60, <<"Server will stop now.">>],
@@ -288,8 +293,9 @@ get_commands_spec() ->
args = [{host, binary}], result = {res, rescode}},
#ejabberd_commands{name = import_prosody, tags = [mnesia, sql],
desc = "Import data from Prosody",
longdesc = "Note: this requires ejabberd compiled with --enable-lua "
"and include the optional 'luerl' library.",
longdesc = "Note: this requires ejabberd to be "
"[compiled with `--enable-lua`](http://localhost:8098/admin/installation/#configure) "
"(which installs the `luerl` library).",
module = prosody2ejabberd, function = from_dir,
args_desc = ["Full path to the Prosody data directory"],
args_example = ["/var/lib/prosody/datadump/"],
@@ -367,7 +373,7 @@ get_commands_spec() ->
result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [cluster],
desc = "Set master node of the clustered Mnesia tables",
longdesc = "If you provide as nodename \"self\", this "
longdesc = "If you provide as nodename `self`, this "
"node will be set as its own master.",
module = ?MODULE, function = set_master,
args_desc = ["Name of the erlang node that will be considered master of this node"],
@@ -395,7 +401,7 @@ get_commands_spec() ->
"binary backup file the internal Mnesia "
"database. This will consume a lot of memory if "
"you have a large database, you may prefer "
"'install_fallback'.",
"http://./#install-fallback[install_fallback].",
module = ?MODULE, function = restore_mnesia,
args_desc = ["Full path to the backup file"],
args_example = ["/var/lib/ejabberd/database.backup"],
@@ -417,8 +423,9 @@ get_commands_spec() ->
longdesc = "Restore immediately. This is not "
"recommended for big databases, as it will "
"consume much time, memory and processor. In "
"that case it's preferable to use 'backup' and "
"'install_fallback'.",
"that case it's preferable to use "
"http://./#backup[backup] and "
"http://./#install-fallback[install_fallback].",
module = ?MODULE, function = load_mnesia,
args_desc = ["Full path to the text file"],
args_example = ["/var/lib/ejabberd/database.txt"],
@@ -440,8 +447,8 @@ get_commands_spec() ->
"restore the database at the next ejabberd "
"start. This means that, after running this "
"command, you have to restart ejabberd. This "
"command requires less memory than
'restore'.",
"command requires less memory than "
"http://./#restore[restore].",
module = ?MODULE, function = install_fallback_mnesia,
args_desc = ["Full path to the fallback file"],
args_example = ["/var/lib/ejabberd/database.fallback"],
+34 -20
View File
@@ -398,15 +398,29 @@ user_exists(_User, <<"">>) ->
user_exists(User, Server) ->
case validate_credentials(User, Server) of
{ok, LUser, LServer} ->
lists:any(
fun(M) ->
case db_user_exists(LUser, LServer, M) of
{error, _} ->
false;
Else ->
Else
end
end, auth_modules(LServer));
{Exists, PerformExternalUserCheck} =
lists:foldl(
fun(M, {Exists0, PerformExternalUserCheck0}) ->
case db_user_exists(LUser, LServer, M) of
{{error, _}, Check} ->
{Exists0, PerformExternalUserCheck0 orelse Check};
{Else, Check2} ->
{Exists0 orelse Else, PerformExternalUserCheck0 orelse Check2}
end
end, {false, false}, auth_modules(LServer)),
case (not Exists) andalso PerformExternalUserCheck andalso
ejabberd_option:auth_external_user_exists_check(Server) andalso
gen_mod:is_loaded(Server, mod_last) of
true ->
case mod_last:get_last_info(User, Server) of
not_found ->
false;
_ ->
true
end;
_ ->
Exists
end;
_ ->
false
end.
@@ -420,11 +434,11 @@ user_exists_in_other_modules_loop([], _User, _Server) ->
false;
user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
case db_user_exists(User, Server, AuthModule) of
true ->
{true, _} ->
true;
false ->
{false, _} ->
user_exists_in_other_modules_loop(AuthModules, User, Server);
{error, _} ->
{{error, _}, _} ->
maybe
end.
@@ -628,9 +642,9 @@ db_get_password(User, Server, Mod) ->
db_user_exists(User, Server, Mod) ->
case db_get_password(User, Server, Mod) of
{ok, _} ->
true;
{true, false};
not_found ->
false;
{false, false};
error ->
case {Mod:store_type(Server), use_cache(Mod, Server)} of
{external, true} ->
@@ -649,18 +663,18 @@ db_user_exists(User, Server, Mod) ->
end,
case Val of
{ok, _} ->
true;
{true, Mod /= ejabberd_auth_anonymous} ;
not_found ->
false;
{false, Mod /= ejabberd_auth_anonymous};
error ->
false;
{false, Mod /= ejabberd_auth_anonymous};
{error, _} = Err ->
Err
{Err, Mod /= ejabberd_auth_anonymous}
end;
{external, false} ->
ets_cache:untag(Mod:user_exists(User, Server));
{ets_cache:untag(Mod:user_exists(User, Server)), Mod /= ejabberd_auth_anonymous};
_ ->
false
{false, false}
end
end.
+29 -2
View File
@@ -45,7 +45,31 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(_Host) -> ok.
start(Host) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"users">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"password">>, type = text},
#sql_column{name = <<"serverkey">>, type = {text, 128},
default = true},
#sql_column{name = <<"salt">>, type = {text, 128},
default = true},
#sql_column{name = <<"iterationcount">>, type = integer,
default = true},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
stop(_Host) -> ok.
@@ -161,7 +185,10 @@ set_password_t(LUser, LServer, Password) ->
"users",
["!username=%(LUser)s",
"!server_host=%(LServer)s",
"password=%(Password)s"]).
"password=%(Password)s",
"serverkey=''",
"salt=''",
"iterationcount=0"]).
get_password_scram(LServer, LUser) ->
ejabberd_sql:sql_query(
+2 -2
View File
@@ -73,7 +73,7 @@ get_commands_spec() ->
"documentation should be stored",
"Regexp matching names of commands or modules "
"that will be included inside generated document",
"Comma separated list of languages (chosen from java, perl, xmlrpc, json)"
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
"that will have example invocation include in markdown document"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
@@ -87,7 +87,7 @@ get_commands_spec() ->
"documentation should be stored",
"Regexp matching names of commands or modules "
"that will be included inside generated document",
"Comma separated list of languages (chosen from java, perl, xmlrpc, json)"
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
"that will have example invocation include in markdown document"],
result_desc = "0 if command failed, 1 when succeeded",
args_example = ["/home/me/docs/api.html", "mod_admin", "java,json"],
+1 -1
View File
@@ -472,7 +472,7 @@ generate_html_output(File, RegExp, Languages) ->
ok.
maybe_add_policy_arguments(#ejabberd_commands{args=Args1, policy=user}=Cmd) ->
Args2 = [{user, binary}, {server, binary} | Args1],
Args2 = [{user, binary}, {host, binary} | Args1],
Cmd#ejabberd_commands{args = Args2};
maybe_add_policy_arguments(Cmd) ->
Cmd.
+19 -1
View File
@@ -502,7 +502,13 @@ read_file(File, Opts) ->
end,
case Ret of
{ok, Y} ->
validate(Y);
InstalledModules = maybe_install_contrib_modules(Y),
ValResult = validate(Y),
case InstalledModules of
[] -> ok;
_ -> spawn(fun() -> timer:sleep(5000), ?MODULE:reload() end)
end,
ValResult;
Err ->
Err
end.
@@ -527,6 +533,18 @@ read_erlang_file(File, _) ->
Err
end.
-spec maybe_install_contrib_modules(term()) -> [atom()].
maybe_install_contrib_modules(Options) ->
case {lists:keysearch(allow_contrib_modules, 1, Options),
lists:keysearch(install_contrib_modules, 1, Options)} of
{Allow, {value, {_, InstallContribModules}}}
when (Allow == false) or
(Allow == {value, {allow_contrib_modules, true}}) ->
ext_mod:install_contrib_modules(InstallContribModules, Options);
_ ->
[]
end.
-spec validate(term()) -> {ok, [{atom(), term()}]} | error_return().
validate(Y1) ->
case pre_validate(Y1) of
+30 -5
View File
@@ -335,8 +335,8 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
ArgsFormatted,
CI2,
Version),
format_result(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1, A2], _} | _]}} ->
format_result_preliminary(Result, ResultFormat);
{'EXIT', {function_clause,[{lists,zip,[A1,A2|_], _} | _]}} ->
{NumCompa, TextCompa} =
case {length(A1), length(A2)} of
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
@@ -385,6 +385,11 @@ format_arg2(Arg, Parse)->
%% Format result
%%-----------------------------
format_result_preliminary(Result, {A, {list, B}}) ->
format_result(Result, {A, {top_result_list, B}});
format_result_preliminary(Result, ResultFormat) ->
format_result(Result, ResultFormat).
format_result({error, ErrorAtom}, _) ->
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
@@ -421,6 +426,16 @@ format_result(Code, {_Name, rescode}) ->
format_result({Code, Text}, {_Name, restuple}) ->
{io_lib:format("~ts", [Text]), make_status(Code)};
format_result([], {_Name, {top_result_list, _ElementsDef}}) ->
"";
format_result([FirstElement | Elements], {_Name, {top_result_list, ElementsDef}}) ->
[format_result(FirstElement, ElementsDef) |
lists:map(
fun(Element) ->
["\n" | format_result(Element, ElementsDef)]
end,
Elements)];
%% The result is a list of something: [something()]
format_result([], {_Name, {list, _ElementsDef}}) ->
"";
@@ -430,7 +445,7 @@ format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
%% If there are more elements, put always first a newline character
lists:map(
fun(Element) ->
["\n" | format_result(Element, ElementsDef)]
[";" | format_result(Element, ElementsDef)]
end,
Elements)];
@@ -755,7 +770,10 @@ print_usage_help(MaxC, ShCode) ->
"\n",
"Please note that 'ejabberdctl' shows all ejabberd commands,\n",
"even those that cannot be used in the shell with ejabberdctl.\n",
"Those commands can be identified because their description starts with: *"],
"Those commands can be identified because their description starts with: *\n",
"\n",
"Some commands return lists, like get_roster and get_user_subscriptions.\n",
"In those commands, the elements in the list are separated with: ;\n"],
ArgsDef = [],
C = #ejabberd_commands{
name = help,
@@ -817,6 +835,11 @@ filter_commands_regexp(All, Glob) ->
end,
All).
maybe_add_policy_arguments(Args, user) ->
[{user, binary}, {host, binary} | Args];
maybe_add_policy_arguments(Args, _) ->
Args.
-spec print_usage_command(Cmd::string(), MaxC::integer(),
ShCode::boolean(), Version::integer()) -> ok.
print_usage_command(Cmd, MaxC, ShCode, Version) ->
@@ -829,13 +852,15 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
tags = TagsAtoms,
definer = Definer,
desc = Desc,
args = ArgsDef,
args = ArgsDefPreliminary,
policy = Policy,
longdesc = LongDesc,
result = ResultDef} = C,
NameFmt = [" ", ?B("Command Name"), ": ", ?C(Cmd), "\n"],
%% Initial indentation of result is 13 = length(" Arguments: ")
ArgsDef = maybe_add_policy_arguments(ArgsDefPreliminary, Policy),
Args = [format_usage_ctype(ArgDef, 13) || ArgDef <- ArgsDef],
ArgsMargin = lists:duplicate(13, $\s),
ArgsListFmt = case Args of
+84 -3
View File
@@ -108,6 +108,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
{Port2, ExtraOpts} = case Port of
<<"unix:", Path/binary>> ->
SO = lists:keydelete(ip, 1, SockOpts),
setup_provisional_udsocket_dir(Path),
file:delete(Path),
{0, [{ip, {local, Path}} | SO]};
_ ->
@@ -119,6 +120,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
{reuseaddr, true} |
ExtraOpts2]) of
{ok, Socket} ->
set_definitive_udsocket(Port, Opts),
case inet:sockname(Socket) of
{ok, {Addr, Port1}} ->
proc_lib:init_ack({ok, self()}),
@@ -149,6 +151,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) ->
init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) ->
case listen_tcp(Port, SockOpts) of
{ok, ListenSocket} ->
set_definitive_udsocket(Port, Opts),
case inet:sockname(ListenSocket) of
{ok, {Addr, Port1}} ->
proc_lib:init_ack({ok, self()}),
@@ -186,8 +189,10 @@ listen_tcp(Port, SockOpts) ->
{Port2, ExtraOpts} = case Port of
<<"unix:", Path/binary>> ->
SO = lists:keydelete(ip, 1, SockOpts),
Prov = setup_provisional_udsocket_dir(Path),
file:delete(Path),
{0, [{ip, {local, Path}} | SO]};
file:delete(Prov),
{0, [{ip, {local, Prov}} | SO]};
_ ->
{Port, SockOpts}
end,
@@ -205,6 +210,72 @@ listen_tcp(Port, SockOpts) ->
Err
end.
%%%
%%% Unix Domain Socket utility functions
%%%
setup_provisional_udsocket_dir(DefinitivePath) ->
ProvisionalPath = get_provisional_udsocket_path(DefinitivePath),
SocketDir = filename:dirname(ProvisionalPath),
file:make_dir(SocketDir),
file:change_mode(SocketDir, 8#00700),
?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s",
[ProvisionalPath, DefinitivePath]),
ProvisionalPath.
get_provisional_udsocket_path(Path) ->
MnesiaDir = mnesia:system_info(directory),
SocketDir = filename:join(MnesiaDir, "socket"),
PathBase64 = misc:term_to_base64(Path),
PathBuild = filename:join(SocketDir, PathBase64),
%% Shorthen the path, a long path produces a crash when opening the socket.
binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}).
get_definitive_udsocket_path(<<"unix", _>> = Unix) ->
Unix;
get_definitive_udsocket_path(ProvisionalPath) ->
PathBase64 = filename:basename(ProvisionalPath),
{term, Path} = misc:base64_to_term(PathBase64),
Path.
set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) ->
Prov = get_provisional_udsocket_path(Path),
timer:sleep(5000),
Usd = maps:get(unix_socket, Opts),
case maps:get(mode, Usd, undefined) of
undefined -> ok;
Mode -> ok = file:change_mode(Prov, Mode)
end,
case maps:get(owner, Usd, undefined) of
undefined -> ok;
Owner ->
try
ok = file:change_owner(Prov, Owner)
catch
error:{badmatch, {error, eperm}} ->
?ERROR_MSG("Error trying to set owner ~p for socket ~p", [Owner, Prov]),
throw({error_setting_socket_owner, Owner, Prov})
end
end,
case maps:get(group, Usd, undefined) of
undefined -> ok;
Group ->
try
ok = file:change_group(Prov, Group)
catch
error:{badmatch, {error, eperm}} ->
?ERROR_MSG("Error trying to set group ~p for socket ~p", [Group, Prov]),
throw({error_setting_socket_group, Group, Prov})
end
end,
file:rename(Prov, Path);
set_definitive_udsocket(_Port, _Opts) ->
ok.
%%%
%%%
%%%
-spec split_opts(transport(), opts()) -> {opts(), [gen_tcp:option()]}.
split_opts(Transport, Opts) ->
maps:fold(
@@ -493,8 +564,11 @@ format_error(Reason) ->
-spec format_endpoint(endpoint()) -> string().
format_endpoint({Port, IP, _Transport}) ->
case Port of
<<"unix:", _/binary>> ->
Port;
Unix when is_binary(Unix) ->
<<"unix:", Unix/binary>>;
Def = get_definitive_udsocket_path(Unix),
<<"unix:", Def/binary>>;
_ ->
IPStr = case tuple_size(IP) of
4 -> inet:ntoa(IP);
@@ -699,6 +773,12 @@ listen_opt_type(shaper) ->
econf:shaper();
listen_opt_type(access) ->
econf:acl();
listen_opt_type(unix_socket) ->
econf:options(
#{group => econf:non_neg_int(),
owner => econf:non_neg_int(),
mode => econf:octal()},
[unique, {return, map}]);
listen_opt_type(use_proxy_protocol) ->
econf:bool().
@@ -708,6 +788,7 @@ listen_options() ->
{ip, {0,0,0,0}},
{accept_interval, 0},
{send_timeout, 15000},
{backlog, 5},
{backlog, 128},
{unix_socket, #{}},
{use_proxy_protocol, false},
{supervisor, true}].
+2 -2
View File
@@ -244,10 +244,9 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}.
get_client_identity(<<"">>, Ctx) ->
{ok, {Ctx, {client, unknown_client}}};
get_client_identity({client, ClientID}, Ctx) ->
get_client_identity(ClientID, Ctx) when is_binary(ClientID) ->
{ok, {Ctx, {client, ClientID}}}.
verify_redirection_uri(_ClientID, RedirectURI, Ctx) ->
@@ -753,6 +752,7 @@ json_error(Code, Error, Reason) ->
json_response(Code, Body).
json_error_desc(access_denied) -> <<"Access denied">>;
json_error_desc(badpass) -> <<"Bad password">>;
json_error_desc(unsupported_grant_type) -> <<"Unsupported grant type">>;
json_error_desc(invalid_scope) -> <<"Invalid scope">>.
+27
View File
@@ -41,8 +41,35 @@
-include("logger.hrl").
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"oauth_token">>,
columns =
[#sql_column{name = <<"token">>, type = text},
#sql_column{name = <<"jid">>, type = text},
#sql_column{name = <<"scope">>, type = text},
#sql_column{name = <<"expire">>, type = bigint}],
indices = [#sql_index{
columns = [<<"token">>],
unique = true}]},
#sql_table{
name = <<"oauth_client">>,
columns =
[#sql_column{name = <<"client_id">>, type = text},
#sql_column{name = <<"client_name">>, type = text},
#sql_column{name = <<"grant_type">>, type = text},
#sql_column{name = <<"options">>, type = text}],
indices = [#sql_index{
columns = [<<"client_id">>],
unique = true}]}]}].
store(R) ->
Token = R#oauth_token.token,
{User, Server} = R#oauth_token.us,
+26
View File
@@ -14,6 +14,7 @@
-export([auth_cache_life_time/0]).
-export([auth_cache_missed/0]).
-export([auth_cache_size/0]).
-export([auth_external_user_exists_check/0, auth_external_user_exists_check/1]).
-export([auth_method/0, auth_method/1]).
-export([auth_opts/0, auth_opts/1]).
-export([auth_password_format/0, auth_password_format/1]).
@@ -52,6 +53,7 @@
-export([host_config/0]).
-export([hosts/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]).
-export([jwt_jid_field/0, jwt_jid_field/1]).
-export([jwt_key/0, jwt_key/1]).
@@ -142,6 +144,7 @@
-export([sm_use_cache/0, sm_use_cache/1]).
-export([sql_connect_timeout/0, sql_connect_timeout/1]).
-export([sql_database/0, sql_database/1]).
-export([sql_flags/0, sql_flags/1]).
-export([sql_keepalive_interval/0, sql_keepalive_interval/1]).
-export([sql_odbc_driver/0, sql_odbc_driver/1]).
-export([sql_password/0, sql_password/1]).
@@ -159,6 +162,7 @@
-export([sql_type/0, sql_type/1]).
-export([sql_username/0, sql_username/1]).
-export([trusted_proxies/0]).
-export([update_sql_schema/0]).
-export([use_cache/0, use_cache/1]).
-export([validate_stream/0]).
-export([version/0]).
@@ -222,6 +226,13 @@ auth_cache_missed() ->
auth_cache_size() ->
ejabberd_config:get_option({auth_cache_size, global}).
-spec auth_external_user_exists_check() -> boolean().
auth_external_user_exists_check() ->
auth_external_user_exists_check(global).
-spec auth_external_user_exists_check(global | binary()) -> boolean().
auth_external_user_exists_check(Host) ->
ejabberd_config:get_option({auth_external_user_exists_check, Host}).
-spec auth_method() -> [atom()].
auth_method() ->
auth_method(global).
@@ -449,6 +460,10 @@ include_config_file() ->
include_config_file(Host) ->
ejabberd_config:get_option({include_config_file, Host}).
-spec install_contrib_modules() -> [atom()].
install_contrib_modules() ->
ejabberd_config:get_option({install_contrib_modules, global}).
-spec jwt_auth_only_rule() -> atom().
jwt_auth_only_rule() ->
jwt_auth_only_rule(global).
@@ -959,6 +974,13 @@ sql_database() ->
sql_database(Host) ->
ejabberd_config:get_option({sql_database, Host}).
-spec sql_flags() -> ['mysql_alternative_upsert'].
sql_flags() ->
sql_flags(global).
-spec sql_flags(global | binary()) -> ['mysql_alternative_upsert'].
sql_flags(Host) ->
ejabberd_config:get_option({sql_flags, Host}).
-spec sql_keepalive_interval() -> 'undefined' | pos_integer().
sql_keepalive_interval() ->
sql_keepalive_interval(global).
@@ -1075,6 +1097,10 @@ sql_username(Host) ->
trusted_proxies() ->
ejabberd_config:get_option({trusted_proxies, global}).
-spec update_sql_schema() -> boolean().
update_sql_schema() ->
ejabberd_config:get_option({update_sql_schema, global}).
-spec use_cache() -> boolean().
use_cache() ->
use_cache(global).
+14
View File
@@ -81,6 +81,8 @@ opt_type(auth_password_format) ->
econf:enum([plain, scram]);
opt_type(auth_scram_hash) ->
econf:enum([sha, sha256, sha512]);
opt_type(auth_external_user_exists_check) ->
econf:bool();
opt_type(auth_use_cache) ->
econf:bool();
opt_type(c2s_cafile) ->
@@ -183,6 +185,8 @@ opt_type(hosts) ->
econf:non_empty(econf:list(econf:domain(), [unique]));
opt_type(include_config_file) ->
econf:any();
opt_type(install_contrib_modules) ->
econf:list(econf:atom());
opt_type(language) ->
econf:lang();
opt_type(ldap_backups) ->
@@ -256,6 +260,8 @@ opt_type(net_ticktime) ->
econf:timeout(second);
opt_type(new_sql_schema) ->
econf:bool();
opt_type(update_sql_schema) ->
econf:bool();
opt_type(oauth_access) ->
econf:acl();
opt_type(oauth_cache_life_time) ->
@@ -422,6 +428,8 @@ opt_type(sql_username) ->
econf:binary();
opt_type(sql_prepared_statements) ->
econf:bool();
opt_type(sql_flags) ->
econf:list_or_single(econf:enum([mysql_alternative_upsert]), [sorted, unique]);
opt_type(trusted_proxies) ->
econf:either(all, econf:list(econf:ip_mask()));
opt_type(use_cache) ->
@@ -517,6 +525,7 @@ options() ->
{access_rules, []},
{acme, #{}},
{allow_contrib_modules, true},
{install_contrib_modules, []},
{allow_multiple_connections, false},
{anonymous_protocol, sasl_anon},
{api_permissions,
@@ -537,6 +546,7 @@ options() ->
{auth_opts, []},
{auth_password_format, plain},
{auth_scram_hash, sha},
{auth_external_user_exists_check, true},
{auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{c2s_cafile, undefined},
@@ -599,6 +609,7 @@ options() ->
{negotiation_timeout, timer:seconds(30)},
{net_ticktime, timer:seconds(60)},
{new_sql_schema, ?USE_NEW_SQL_SCHEMA_DEFAULT},
{update_sql_schema, false},
{oauth_access, none},
{oauth_cache_life_time,
fun(Host) -> ejabberd_config:get_option({cache_life_time, Host}) end},
@@ -705,6 +716,7 @@ options() ->
{sql_start_interval, timer:seconds(30)},
{sql_username, <<"ejabberd">>},
{sql_prepared_statements, true},
{sql_flags, []},
{trusted_proxies, []},
{validate_stream, false},
{websocket_origin, []},
@@ -736,6 +748,7 @@ globals() ->
fqdn,
hosts,
host_config,
install_contrib_modules,
listen,
loglevel,
log_rotate_count,
@@ -746,6 +759,7 @@ globals() ->
negotiation_timeout,
net_ticktime,
new_sql_schema,
update_sql_schema,
node_start,
oauth_cache_life_time,
oauth_cache_missed,
+23
View File
@@ -303,6 +303,8 @@ doc() ->
#{value => "true | false",
desc =>
?T("Whether to allow installation of third-party modules or not. "
"See https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
"[ejabberd-contrib] documentation section. "
"The default value is 'true'.")}},
{allow_multiple_connections,
#{value => "true | false",
@@ -393,6 +395,14 @@ doc() ->
"You shouldn't change this if you already have passwords generated with "
"a different algorithm - users that have such passwords will not be able "
"to authenticate. The default value is 'sha'.")}},
{auth_external_user_exists_check,
#{value => "true | false",
note => "added in 23.10",
desc =>
?T("Supplement check for user existence based on 'mod_last' data, for authentication "
"methods that don't have a way to reliable tell if user exists (like is the case for "
"'jwt' and certificate based authentication). This helps with processing offline message "
"for those users. The default value is 'true'.")}},
{auth_use_cache,
#{value => "true | false",
desc =>
@@ -670,6 +680,14 @@ doc() ->
"file 'Filename'. The options that do not match this "
"criteria are not accepted. The default value is to include "
"all options.")}}]},
{install_contrib_modules,
#{value => "[Module, ...]",
note => "added in 23.10",
desc =>
?T("Modules to install from "
"https://docs.ejabberd.im/developer/extending-ejabberd/modules/#ejabberd-contrib"
"[ejabberd-contrib] at start time. "
"The default value is an empty list of modules: '[]'.")}},
{jwt_auth_only_rule,
#{value => ?T("AccessName"),
desc =>
@@ -893,6 +911,11 @@ doc() ->
"configuration flag '--enable-new-sql-schema' which is set "
"at compile time."),
[binary:part(ejabberd_config:version(), {0,5})]}}},
{update_sql_schema,
#{value => "true | false",
desc =>
?T("Allow ejabberd to update SQL schema. "
"The default value is 'true'.")}},
{oauth_access,
#{value => ?T("AccessName"),
desc => ?T("By default creating OAuth tokens is not allowed. "
+19
View File
@@ -37,6 +37,8 @@
%%% API
%%%===================================================================
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
Node = erlang:atom_to_binary(node(), latin1),
?DEBUG("Cleaning SQL 'route' table...", []),
case ejabberd_sql:sql_query(
@@ -48,6 +50,23 @@ init() ->
Err
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"route">>,
columns =
[#sql_column{name = <<"domain">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"pid">>, type = text},
#sql_column{name = <<"local_hint">>, type = text}],
indices = [#sql_index{
columns = [<<"domain">>, <<"server_host">>,
<<"node">>, <<"pid">>],
unique = true}]}]}].
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
PidS = misc:encode_pid(Pid),
LocalHintS = enc_local_hint(LocalHint),
+24
View File
@@ -48,6 +48,7 @@ init() ->
?DEBUG("Cleaning SQL SM table...", []),
lists:foldl(
fun(Host, ok) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
case ejabberd_sql:sql_query(
Host, ?SQL("delete from sm where node=%(Node)s")) of
{updated, _} ->
@@ -60,6 +61,29 @@ init() ->
Err
end, ok, ejabberd_sm:get_vh_by_backend(?MODULE)).
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"sm">>,
columns =
[#sql_column{name = <<"usec">>, type = bigint},
#sql_column{name = <<"pid">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"resource">>, type = text},
#sql_column{name = <<"priority">>, type = text},
#sql_column{name = <<"info">>, type = text}],
indices = [#sql_index{
columns = [<<"usec">>, <<"pid">>],
unique = true},
#sql_index{
columns = [<<"node">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>]}]}]}].
set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
priority = Priority, info = Info}) ->
InfoS = misc:term_to_expr(Info),
+82 -15
View File
@@ -73,7 +73,7 @@
-record(state,
{db_ref :: undefined | pid(),
db_type = odbc :: pgsql | mysql | sqlite | odbc | mssql,
db_version :: undefined | non_neg_integer(),
db_version :: undefined | non_neg_integer() | {non_neg_integer(), atom(), non_neg_integer()},
reconnect_count = 0 :: non_neg_integer(),
host :: binary(),
pending_requests :: p1_queue:queue(),
@@ -93,15 +93,16 @@
-endif.
-type state() :: #state{}.
-type sql_query_simple() :: [sql_query() | binary()] | #sql_query{} |
fun(() -> any()) | fun((atom(), _) -> any()).
-type sql_query() :: sql_query_simple() |
[{atom() | {atom(), any()}, sql_query_simple()}].
-type sql_query_result() :: {updated, non_neg_integer()} |
{error, binary() | atom()} |
{selected, [binary()], [[binary()]]} |
{selected, [any()]} |
ok.
-type sql_query_simple(T) :: [sql_query(T) | binary()] | binary() |
#sql_query{} |
fun(() -> T) | fun((atom(), _) -> T).
-type sql_query(T) :: sql_query_simple(T) |
[{atom() | {atom(), any()}, sql_query_simple(T)}].
-type sql_query_result(T) :: {updated, non_neg_integer()} |
{error, binary() | atom()} |
{selected, [binary()], [[binary()]]} |
{selected, [any()]} |
T.
%%%----------------------------------------------------------------------
%%% API
@@ -112,14 +113,14 @@ start_link(Host, I) ->
p1_fsm:start_link({local, Proc}, ?MODULE, [Host],
fsm_limit_opts() ++ ?FSMOPTS).
-spec sql_query(binary(), sql_query()) -> sql_query_result().
-spec sql_query(binary(), sql_query(T)) -> sql_query_result(T).
sql_query(Host, Query) ->
sql_call(Host, {sql_query, Query}).
%% SQL transaction based on a list of queries
%% This function automatically
-spec sql_transaction(binary(), [sql_query()] | fun(() -> any())) ->
{atomic, any()} |
-spec sql_transaction(binary(), [sql_query(T)] | fun(() -> T)) ->
{atomic, T} |
{aborted, any()}.
sql_transaction(Host, Queries)
when is_list(Queries) ->
@@ -177,7 +178,7 @@ sync_send_event(Proc, Msg, Timeout) ->
{error, Reason}
end.
-spec sql_query_t(sql_query()) -> sql_query_result().
-spec sql_query_t(sql_query(T)) -> sql_query_result(T).
%% This function is intended to be used from inside an sql_transaction:
sql_query_t(Query) ->
QRes = sql_query_internal(Query),
@@ -683,7 +684,14 @@ sql_query_internal(#sql_query{} = Query) ->
pgsql_sql_query(Query)
end;
mysql ->
generic_sql_query(Query);
case {Query#sql_query.flags, ejabberd_option:sql_prepared_statements(State#state.host)} of
{1, _} ->
generic_sql_query(Query);
{_, false} ->
generic_sql_query(Query);
_ ->
mysql_prepared_execute(Query, State)
end;
sqlite ->
sqlite_sql_query(Query)
end
@@ -862,6 +870,24 @@ pgsql_execute_sql_query(SQLQuery, State) ->
Res = pgsql_execute_to_odbc(ExecuteRes),
sql_query_format_res(Res, SQLQuery).
mysql_prepared_execute(#sql_query{hash = Hash} = Query, State) ->
ValEsc = #sql_escape{like_escape = fun() -> ignore end, _ = fun(X) -> X end},
TypesEsc = #sql_escape{string = fun(_) -> string end,
integer = fun(_) -> integer end,
boolean = fun(_) -> bool end,
in_array_string = fun(_) -> string end,
like_escape = fun() -> ignore end},
Val = [X || X <- (Query#sql_query.args)(ValEsc), X /= ignore],
Types = [X || X <- (Query#sql_query.args)(TypesEsc), X /= ignore],
QueryFn = fun() ->
PrepEsc = #sql_escape{like_escape = fun() -> <<>> end, _ = fun(_) -> <<"?">> end},
(Query#sql_query.format_query)((Query#sql_query.args)(PrepEsc))
end,
QueryTimeout = query_timeout(State#state.host),
Res = p1_mysql_conn:prepared_query(State#state.db_ref, QueryFn, Hash, Val, Types,
self(), [{timeout, QueryTimeout - 1000}]),
Res2 = mysql_to_odbc(Res),
sql_query_format_res(Res2, Query).
sql_query_format_res({selected, _, Rows}, SQLQuery) ->
Res =
@@ -1123,9 +1149,50 @@ get_db_version(#state{db_type = pgsql} = State) ->
?WARNING_MSG("Error getting pgsql version: ~p", [Res]),
State
end;
get_db_version(#state{db_type = mysql, host = Host} = State) ->
DefaultUpsert = case lists:member(mysql_alternative_upsert, ejabberd_option:sql_flags(Host)) of
true -> 1;
_ -> 0
end,
case mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
[<<"select version();">>], self(),
[{timeout, 5000},
{result_type, binary}])) of
{selected, _, [SVersion]} ->
case re:run(SVersion, <<"(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-([^-]*))?">>,
[{capture, all_but_first, binary}]) of
{match, [V1, V2, V3, Type]} ->
V = ((bin_to_int(V1)*1000)+bin_to_int(V2))*1000+bin_to_int(V3),
TypeA = binary_to_atom(Type, utf8),
Flags = case TypeA of
'MariaDB' -> DefaultUpsert;
_ when V >= 5007026 andalso V < 8000000 -> 1;
_ when V >= 8000020 -> 1;
_ -> DefaultUpsert
end,
State#state{db_version = {V, TypeA, Flags}};
{match, [V1, V2, V3]} ->
V = ((bin_to_int(V1)*1000)+bin_to_int(V2))*1000+bin_to_int(V3),
Flags = case V of
_ when V >= 5007026 andalso V < 8000000 -> 1;
_ when V >= 8000020 -> 1;
_ -> DefaultUpsert
end,
State#state{db_version = {V, unknown, Flags}};
_ ->
?WARNING_MSG("Error parsing mysql version: ~p", [SVersion]),
State
end;
Res ->
?WARNING_MSG("Error getting mysql version: ~p", [Res]),
State
end;
get_db_version(State) ->
State.
bin_to_int(<<>>) -> 0;
bin_to_int(V) -> binary_to_integer(V).
log(Level, Format, Args) ->
case Level of
debug -> ?DEBUG(Format, Args);
+78 -5
View File
@@ -42,7 +42,8 @@
used_vars = [],
use_new_schema,
need_timestamp_pass = false,
need_array_pass = false}).
need_array_pass = false,
has_list = false}).
-define(QUERY_RECORD, "sql_query").
@@ -268,9 +269,12 @@ parse1([$@, $( | S], Acc, State) ->
Convert =
case Type of
integer ->
erl_syntax:application(
erl_syntax:atom(binary_to_integer),
[EVar]);
erl_syntax:if_expr([
erl_syntax:clause(
[erl_syntax:application(erl_syntax:atom(is_binary), [EVar])],
[erl_syntax:application(erl_syntax:atom(binary_to_integer), [EVar])]),
erl_syntax:clause([erl_syntax:atom(true)], [EVar])
]);
string ->
EVar;
timestamp ->
@@ -339,6 +343,7 @@ parse1([$%, $( | S], Acc, State) ->
erl_syntax:variable(Name)]),
State2#state{'query' = [[{var, Var, Type}] | State2#state.'query'],
need_array_pass = true,
has_list = true,
args = [[Convert, ConvertArr] | State2#state.args],
params = [Var | State2#state.params],
param_pos = State2#state.param_pos + 1,
@@ -467,6 +472,7 @@ make_sql_query(State, Type) ->
Hash = erlang:phash2(State#state{loc = undefined, use_new_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,
EQuery =
lists:flatmap(
fun({str, S}) ->
@@ -515,6 +521,9 @@ make_sql_query(State, Type) ->
none,
[erl_syntax:tuple(State#state.res)]
)])),
erl_syntax:record_field(
erl_syntax:atom(flags),
erl_syntax:abstract(Flags)),
erl_syntax:record_field(
erl_syntax:atom(loc),
erl_syntax:abstract({get(?MOD), State#state.loc}))
@@ -567,7 +576,6 @@ parse_upsert_field1([$= | S], Acc, ParamPos, Loc) ->
parse_upsert_field1([C | S], Acc, ParamPos, Loc) ->
parse_upsert_field1(S, [C | Acc], ParamPos, Loc).
make_sql_upsert(Table, ParseRes, Pos) ->
check_upsert(ParseRes, Pos),
erl_syntax:fun_expr(
@@ -587,6 +595,11 @@ make_sql_upsert(Table, ParseRes, Pos) ->
erl_syntax:integer(90100))],
[make_sql_upsert_pgsql901(Table, ParseRes),
erl_syntax:atom(ok)]),
erl_syntax:clause(
[erl_syntax:atom(mysql), erl_syntax:tuple([erl_syntax:underscore(), erl_syntax:underscore(), erl_syntax:integer(1)])],
[],
[make_sql_upsert_mysql_select(Table, ParseRes),
erl_syntax:atom(ok)]),
erl_syntax:clause(
[erl_syntax:atom(mysql), erl_syntax:underscore()],
[],
@@ -682,6 +695,66 @@ make_sql_upsert_insert(Table, ParseRes) ->
]),
State.
make_sql_upsert_select(Table, ParseRes) ->
{Fields0, Where0} =
lists:foldl(
fun({Field, key, ST}, {Fie, Whe}) ->
{Fie, [ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'}] ++ Whe};
({Field, {true}, ST}, {Fie, Whe}) ->
{[ST#state{
'query' = [{str, Field}, {str, "="}] ++ ST#state.'query'}] ++ Fie, Whe};
(_, Acc) ->
Acc
end, {[], []}, ParseRes),
Fields = join_states(Fields0, " AND "),
Where = join_states(Where0, " AND "),
State =
concat_states(
[#state{'query' = [{str, "SELECT "}],
res_vars = [erl_syntax:variable("__VSel")],
res = [erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(to_bool),
[erl_syntax:variable("__VSel")])]},
Fields,
#state{'query' = [{str, " FROM "}, {str, Table}, {str, " WHERE "}]},
Where
]),
State.
make_sql_upsert_mysql_select(Table, ParseRes) ->
Select = make_sql_query(make_sql_upsert_select(Table, ParseRes)),
Insert = make_sql_query(make_sql_upsert_insert(Table, ParseRes)),
Update = make_sql_query(make_sql_upsert_update(Table, ParseRes)),
erl_syntax:case_expr(
erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Select]),
[erl_syntax:clause(
[erl_syntax:tuple([erl_syntax:atom(selected), erl_syntax:list([])])],
none,
[erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Insert])]),
erl_syntax:clause(
[erl_syntax:abstract({selected, [{true}]})],
[],
[erl_syntax:atom(ok)]),
erl_syntax:clause(
[erl_syntax:tuple([erl_syntax:atom(selected), erl_syntax:underscore()])],
none,
[erl_syntax:application(
erl_syntax:atom(ejabberd_sql),
erl_syntax:atom(sql_query_t),
[Update])]),
erl_syntax:clause(
[erl_syntax:variable("__SelectRes")],
none,
[erl_syntax:variable("__SelectRes")])]).
make_sql_upsert_mysql(Table, ParseRes) ->
Vals =
lists:map(
+936
View File
@@ -0,0 +1,936 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_sql.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : SQL schema versioning
%%% Created : 15 Aug 2023 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2023 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(ejabberd_sql_schema).
-author('alexey@process-one.net').
-export([start/1, update_schema/3,
get_table_schema/2, get_table_indices/2, test/0]).
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
start(Host) ->
case should_update_schema(Host) of
true ->
case table_exists(Host, <<"schema_version">>) of
true ->
ok;
false ->
Table = filter_table_sh(schema_table()),
Res = create_table(Host, Table),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to create table ~s: ~p",
[Table#sql_table.name, Error]),
{error, Error};
_ ->
ok
end
end;
false ->
ok
end.
schema_table() ->
#sql_table{
name = <<"schema_version">>,
columns = [#sql_column{name = <<"module">>, type = text},
#sql_column{name = <<"version">>, type = bigint}],
indices = [#sql_index{
columns = [<<"module">>],
unique = true}]}.
get_table_schema(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select "
" @(a.attname)s, "
" @(pg_catalog.format_type(a.atttypid, a.atttypmod))s "
" from "
" pg_class t, "
" pg_attribute a "
" where "
" a.attrelid = t.oid and "
" a.attnum > 0 and "
" a.atttypid > 0 and "
" t.relkind = 'r' and "
" t.relname=%(Table)s"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(i.name)s, @(i.type)s"
" from pragma_table_info(%(Table)s) as i"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(column_name)s, @(column_type)s"
" from information_schema.columns"
" where table_name=%(Table)s and"
" table_schema=schema()"
" order by ordinal_position"))
of
{selected, Cols} ->
[{Col, string_to_type(SType)} || {Col, SType} <- Cols]
end
end).
get_table_indices(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select "
" @(i.relname)s, "
" @(a.attname)s "
" from "
" pg_class t, "
" pg_class i, "
" pg_index ix, "
" pg_attribute a "
" where "
" t.oid = ix.indrelid and "
" i.oid = ix.indexrelid and "
" a.attrelid = t.oid and "
" a.attnum = ANY(ix.indkey) and "
" t.relkind = 'r' and "
" t.relname=%(Table)s "
" order by "
" i.relname, "
" array_position(ix.indkey, a.attnum)"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(i.name)s, @(c.name)s "
" from pragma_index_list(%(Table)s) as i,"
" pragma_index_xinfo(i.name) as c"
" where c.cid >= 0"
" order by i.name, c.seqno"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @(index_name)s, @(column_name)s"
" from information_schema.statistics"
" where table_name=%(Table)s and"
" table_schema=schema()"
" order by index_name, seq_in_index"))
of
{selected, Cols} ->
Indices =
lists:foldr(
fun({IdxName, ColName}, Acc) ->
maps:update_with(
IdxName,
fun(Cs) -> [ColName | Cs] end,
[ColName],
Acc)
end, #{}, Cols),
maps:to_list(Indices)
end
end).
find_index_name(Host, Table, Columns) ->
Indices = get_table_indices(Host, Table),
case lists:keyfind(Columns, 2, Indices) of
false ->
false;
{Name, _} ->
{ok, Name}
end.
get_version(Host, Module) ->
SModule = misc:atom_to_binary(Module),
ejabberd_sql:sql_query(
Host,
?SQL("select @(version)d"
" from schema_version"
" where module=%(SModule)s")).
store_version(Host, Module, Version) ->
SModule = misc:atom_to_binary(Module),
?SQL_UPSERT(
Host,
"schema_version",
["!module=%(SModule)s",
"version=%(Version)d"]).
table_exists(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(pgsql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists (select * from pg_tables "
" where tablename=%(Table)s)"))
of
{selected, [{Res}]} ->
Res
end;
(sqlite, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists"
" (select 0 from pragma_table_info(%(Table)s))"))
of
{selected, [{Res}]} ->
Res
end;
(mysql, _) ->
case
ejabberd_sql:sql_query_t(
?SQL("select @()b exists"
" (select 0 from information_schema.tables"
" where table_name=%(Table)s and"
" table_schema=schema())"))
of
{selected, [{Res}]} ->
Res
end
end).
filter_table_sh(Table) ->
case {ejabberd_sql:use_new_schema(), Table#sql_table.name} of
{true, _} ->
Table;
{_, <<"route">>} ->
Table;
{false, _} ->
Table#sql_table{
columns =
lists:keydelete(
<<"server_host">>, #sql_column.name, Table#sql_table.columns),
indices =
lists:map(
fun(Idx) ->
Idx#sql_index{
columns =
lists:delete(
<<"server_host">>, Idx#sql_index.columns)
}
end, Table#sql_table.indices)
}
end.
string_to_type(SType) ->
case string:lowercase(SType) of
<<"text">> -> text;
<<"mediumtext">> -> text;
<<"bigint">> -> bigint;
<<"bigint ", _/binary>> -> bigint;
<<"bigint(", _/binary>> -> bigint;
<<"integer">> -> integer;
<<"int">> -> integer;
<<"int(", _/binary>> -> integer;
<<"smallint">> -> smallint;
<<"smallint(", _/binary>> -> smallint;
<<"numeric">> -> numeric;
<<"decimal", _/binary>> -> numeric;
<<"bigserial">> -> bigserial;
<<"boolean">> -> boolean;
<<"tinyint(1)">> -> boolean;
<<"bytea">> -> blob;
<<"blob">> -> blob;
<<"timestamp", _/binary>> -> timestamp;
<<"character(", R/binary>> ->
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
{char, N};
<<"char(", R/binary>> ->
{ok, [N], []} = io_lib:fread("~d)", binary_to_list(R)),
{char, N};
<<"varchar(", _/binary>> -> text;
<<"character varying(", _/binary>> -> text;
T ->
?ERROR_MSG("Unknown SQL type '~s'", [T]),
{undefined, T}
end.
check_columns_compatibility(RequiredColumns, Columns) ->
lists:all(
fun(#sql_column{name = Name, type = Type}) ->
%io:format("col ~p~n", [{Name, Type}]),
case lists:keyfind(Name, 1, Columns) of
false ->
false;
{_, Type2} ->
%io:format("tt ~p~n", [{Type, Type2}]),
case {Type, Type2} of
{T, T} -> true;
{text, blob} -> true;
{{text, _}, blob} -> true;
{{text, _}, text} -> true;
{{text, _}, {varchar, _}} -> true;
{text, {varchar, _}} -> true;
{{char, _}, text} -> true;
{{varchar, _}, text} -> true;
{smallint, integer} -> true;
{smallint, bigint} -> true;
{smallint, numeric} -> true;
{integer, bigint} -> true;
{integer, numeric} -> true;
{bigint, numeric} -> true;
{bigserial, integer} -> true;
{bigserial, bigint} -> true;
{bigserial, numeric} -> true;
_ -> false
end
end
end, RequiredColumns).
guess_version(Host, Schemas) ->
LastSchema = lists:max(Schemas),
SomeTablesExist =
lists:any(
fun(Table) ->
table_exists(Host, Table#sql_table.name)
end, LastSchema#sql_schema.tables),
if
SomeTablesExist ->
CompatibleSchemas =
lists:filter(
fun(Schema) ->
lists:all(
fun(Table) ->
Table2 = filter_table_sh(Table),
CurrentColumns =
get_table_schema(
Host, Table2#sql_table.name),
check_columns_compatibility(
Table2#sql_table.columns,
CurrentColumns)
end, Schema#sql_schema.tables)
end, Schemas),
case CompatibleSchemas of
[] -> -1;
_ ->
(lists:max(CompatibleSchemas))#sql_schema.version
end;
true ->
0
end.
get_current_version(Host, Module, Schemas) ->
case get_version(Host, Module) of
{selected, [{Version}]} ->
Version;
{selected, []} ->
Version = guess_version(Host, Schemas),
if
Version > 0 ->
store_version(Host, Module, Version);
true ->
ok
end,
Version
end.
format_type(pgsql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"bytea">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"bigserial">>
end;
format_type(sqlite, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"blob">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"integer primary key autoincrement">>
end;
format_type(mysql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"text">>;
{text, big} -> <<"mediumtext">>;
{text, N} when is_integer(N), N < 191 ->
[<<"varchar(">>, integer_to_binary(N), <<")">>];
{text, _} -> <<"text">>;
bigint -> <<"bigint">>;
integer -> <<"integer">>;
smallint -> <<"smallint">>;
numeric -> <<"numeric">>;
boolean -> <<"boolean">>;
blob -> <<"blob">>;
timestamp -> <<"timestamp">>;
{char, N} -> [<<"character(">>, integer_to_binary(N), <<")">>];
bigserial -> <<"bigint auto_increment primary key">>
end.
format_default(pgsql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"''">>;
{text, _} -> <<"''">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"''">>;
timestamp -> <<"now()">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end;
format_default(sqlite, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"''">>;
{text, _} -> <<"''">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"''">>;
timestamp -> <<"CURRENT_TIMESTAMP">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end;
format_default(mysql, _DBVersion, Column) ->
case Column#sql_column.type of
text -> <<"('')">>;
{text, _} -> <<"('')">>;
bigint -> <<"0">>;
integer -> <<"0">>;
smallint -> <<"0">>;
numeric -> <<"0">>;
boolean -> <<"false">>;
blob -> <<"('')">>;
timestamp -> <<"CURRENT_TIMESTAMP">>
%{char, N} -> <<"''">>;
%bigserial -> <<"0">>
end.
escape_name(pgsql, _DBVersion, <<"type">>) ->
<<"\"type\"">>;
escape_name(_DBType, _DBVersion, ColumnName) ->
ColumnName.
format_column_def(DBType, DBVersion, Column) ->
[<<" ">>,
escape_name(DBType, DBVersion, Column#sql_column.name), <<" ">>,
format_type(DBType, DBVersion, Column),
<<" NOT NULL">>,
case Column#sql_column.default of
false -> [];
true ->
[<<" DEFAULT ">>, format_default(DBType, DBVersion, Column)]
end,
case lists:keyfind(sql_references, 1, Column#sql_column.opts) of
false -> [];
#sql_references{table = T, column = C} ->
[<<" REFERENCES ">>, T, <<"(">>, C, <<") ON DELETE CASCADE">>]
end].
format_mysql_index_column(Table, ColumnName) ->
{value, Column} =
lists:keysearch(
ColumnName, #sql_column.name, Table#sql_table.columns),
NeedsSizeLimit =
case Column#sql_column.type of
{text, N} when is_integer(N), N < 191 -> false;
{text, _} -> true;
text -> true;
_ -> false
end,
if
NeedsSizeLimit ->
[ColumnName, <<"(191)">>];
true ->
ColumnName
end.
format_create_index(pgsql, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
<<" USING btree (">>,
lists:join(
<<", ">>,
Index#sql_index.columns),
<<");">>];
format_create_index(sqlite, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name, <<" ON ">>, TableName,
<<"(">>,
lists:join(
<<", ">>,
Index#sql_index.columns),
<<");">>];
format_create_index(mysql, _DBVersion, Table, Index) ->
TableName = Table#sql_table.name,
Unique =
case Index#sql_index.unique of
true -> <<"UNIQUE ">>;
false -> <<"">>
end,
Name = [<<"i_">>, TableName, <<"_">>,
lists:join(
<<"_">>,
Index#sql_index.columns)],
[<<"CREATE ">>, Unique, <<"INDEX ">>, Name,
<<" USING BTREE ON ">>, TableName,
<<"(">>,
lists:join(
<<", ">>,
lists:map(
fun(Col) ->
format_mysql_index_column(Table, Col)
end, Index#sql_index.columns)),
<<");">>].
format_create_table(pgsql = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n);\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices);
format_create_table(sqlite = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n);\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices);
format_create_table(mysql = DBType, DBVersion, Table) ->
TableName = Table#sql_table.name,
[iolist_to_binary(
[<<"CREATE TABLE ">>, TableName, <<" (\n">>,
lists:join(
<<",\n">>,
lists:map(
fun(C) -> format_column_def(DBType, DBVersion, C) end,
Table#sql_table.columns)),
<<"\n) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n">>])] ++
lists:map(
fun(I) ->
iolist_to_binary(
[format_create_index(DBType, DBVersion, Table, I),
<<"\n">>])
end,
Table#sql_table.indices).
%format_create_table(DBType, _DBVersion, Table) ->
% ?ERROR_MSG("Can't create SQL table ~p on ~p",
% [Table#sql_table.name, DBType]),
% error.
create_table(Host, Table) ->
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
SQLs = format_create_table(DBType, DBVersion, Table),
?INFO_MSG("Creating table ~s:~n~s~n",
[Table#sql_table.name, SQLs]),
lists:foreach(
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end, SQLs),
case Table#sql_table.post_create of
undefined ->
ok;
F ->
F(DBType, DBVersion)
end
end).
create_tables(Host, Module, Schema) ->
lists:foreach(
fun(Table) ->
Table2 = filter_table_sh(Table),
Res = create_table(Host, Table2),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to create table ~s: ~p",
[Table2#sql_table.name, Error]),
error(Error);
_ ->
ok
end
end, Schema#sql_schema.tables),
store_version(Host, Module, Schema#sql_schema.version).
should_update_schema(Host) ->
SupportedDB =
case ejabberd_option:sql_type(Host) of
pgsql -> true;
sqlite -> true;
mysql -> true;
_ -> false
end,
case ejabberd_option:update_sql_schema() andalso SupportedDB of
true ->
case ejabberd_sql:use_new_schema() of
true ->
Host == ejabberd_config:get_myname();
false ->
true
end;
false ->
false
end.
update_schema(Host, Module, Schemas) ->
case should_update_schema(Host) of
true ->
Version = get_current_version(Host, Module, Schemas),
LastSchema = lists:max(Schemas),
LastVersion = LastSchema#sql_schema.version,
case Version of
_ when Version < 0 ->
?ERROR_MSG("Can't update SQL schema for module ~p, please do it manually", [Module]);
0 ->
create_tables(Host, Module, LastSchema);
LastVersion ->
ok;
_ when LastVersion < Version ->
?ERROR_MSG("The current SQL schema for module ~p is ~p, but the latest known schema in the module is ~p", [Module, Version, LastVersion]);
_ ->
lists:foreach(
fun(Schema) ->
if
Schema#sql_schema.version > Version ->
do_update_schema(Host, Module, Schema);
true ->
ok
end
end, lists:sort(Schemas))
end;
false ->
ok
end.
do_update_schema(Host, Module, Schema) ->
lists:foreach(
fun({add_column, TableName, ColumnName}) ->
{value, Table} =
lists:keysearch(
TableName, #sql_table.name, Schema#sql_schema.tables),
{value, Column} =
lists:keysearch(
ColumnName, #sql_column.name, Table#sql_table.columns),
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
Def = format_column_def(DBType, DBVersion, Column),
Default = format_default(DBType, DBVersion, Column),
SQLs =
[[<<"ALTER TABLE ">>,
TableName,
<<" ADD COLUMN\n">>,
Def,
<<" DEFAULT ">>,
Default, <<";\n">>]] ++
case Column#sql_column.default of
false ->
[[<<"ALTER TABLE ">>,
TableName,
<<" ALTER COLUMN ">>,
ColumnName,
<<" DROP DEFAULT;">>]];
_ ->
[]
end,
?INFO_MSG("Add column ~s/~s:~n~s~n",
[TableName,
ColumnName,
SQLs]),
lists:foreach(
fun(SQL) -> ejabberd_sql:sql_query_t(SQL) end,
SQLs)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({drop_column, TableName, ColumnName}) ->
Res =
ejabberd_sql:sql_query(
Host,
fun(_DBType, _DBVersion) ->
SQL = [<<"ALTER TABLE ">>,
TableName,
<<" DROP COLUMN ">>,
ColumnName,
<<";">>],
?INFO_MSG("Drop column ~s/~s:~n~s~n",
[TableName,
ColumnName,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({create_index, TableName, Columns}) ->
{value, Table1} =
lists:keysearch(
TableName, #sql_table.name, Schema#sql_schema.tables),
{value, Index1} =
lists:keysearch(
Columns, #sql_index.columns, Table1#sql_table.indices),
Table = filter_table_sh(Table1),
Index =
case ejabberd_sql:use_new_schema() of
true ->
Index1;
false ->
Index1#sql_index{
columns =
lists:delete(
<<"server_host">>, Index1#sql_index.columns)
}
end,
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, DBVersion) ->
SQL1 = format_create_index(
DBType, DBVersion, Table, Index),
SQL = iolist_to_binary(SQL1),
?INFO_MSG("Create index ~s/~p:~n~s~n",
[Table#sql_table.name,
Index#sql_index.columns,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end;
({drop_index, TableName, Columns1}) ->
Columns =
case ejabberd_sql:use_new_schema() of
true ->
Columns1;
false ->
lists:delete(
<<"server_host">>, Columns1)
end,
case find_index_name(Host, TableName, Columns) of
false ->
?ERROR_MSG("Can't find an index to drop for ~s/~p",
[TableName, Columns]);
{ok, IndexName} ->
Res =
ejabberd_sql:sql_query(
Host,
fun(DBType, _DBVersion) ->
SQL =
case DBType of
mysql ->
[<<"DROP INDEX ">>,
IndexName,
<<" ON ">>,
TableName,
<<";">>];
_ ->
[<<"DROP INDEX ">>,
IndexName, <<";">>]
end,
?INFO_MSG("Drop index ~s/~p:~n~s~n",
[TableName,
Columns,
SQL]),
ejabberd_sql:sql_query_t(SQL)
end),
case Res of
{error, Error} ->
?ERROR_MSG("Failed to update table ~s: ~p",
[TableName, Error]),
error(Error);
_ ->
ok
end
end
end, Schema#sql_schema.update),
store_version(Host, Module, Schema#sql_schema.version).
test() ->
Schemas =
[#sql_schema{
version = 2,
tables =
[#sql_table{
name = <<"archive2">>,
columns = [#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = text},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"origin_id">>, type = text},
#sql_column{name = <<"type">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"origin_id">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
]}],
update =
[{add_column, <<"archive2">>, <<"origin_id">>},
{create_index, <<"archive2">>,
[<<"server_host">>, <<"origin_id">>]},
{drop_index, <<"archive2">>,
[<<"server_host">>, <<"origin_id">>]},
{drop_column, <<"archive2">>, <<"origin_id">>}
]},
#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"archive2">>,
columns = [#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = {text, 10}},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
]}]}],
update_schema(<<"localhost">>, mod_foo, Schemas).
+3 -1
View File
@@ -51,7 +51,9 @@ start(Host) ->
type => supervisor,
modules => [?MODULE]},
case supervisor:start_child(ejabberd_db_sup, Spec) of
{ok, _} -> ok;
{ok, _} ->
ejabberd_sql_schema:start(Host),
ok;
{error, {already_started, Pid}} ->
%% Wait for the supervisor to fully start
_ = supervisor:count_children(Pid),
+40 -47
View File
@@ -44,9 +44,8 @@
{socket :: gen_udp:socket() | undefined,
destination :: inet:local_address() | undefined,
interval :: pos_integer() | undefined,
last_ping :: integer() | undefined}).
timer :: reference() | undefined}).
-type watchdog_timeout() :: pos_integer() | hibernate.
-type state() :: #state{}.
%%--------------------------------------------------------------------
@@ -71,8 +70,7 @@ stopping() ->
%%--------------------------------------------------------------------
%% gen_server callbacks.
%%--------------------------------------------------------------------
-spec init(any())
-> {ok, state()} | {ok, state(), watchdog_timeout()} | {stop, term()}.
-spec init(any()) -> {ok, state()} | {stop, term()}.
init(_Opts) ->
process_flag(trap_exit, true),
case os:getenv("NOTIFY_SOCKET") of
@@ -84,17 +82,10 @@ init(_Opts) ->
Destination = {local, Path},
case gen_udp:open(0, [local]) of
{ok, Socket} ->
Interval = get_watchdog_interval(),
State = #state{socket = Socket,
destination = Destination,
interval = Interval},
if is_integer(Interval), Interval > 0 ->
?INFO_MSG("Watchdog notifications enabled", []),
{ok, set_last_ping(State), Interval};
true ->
?INFO_MSG("Watchdog notifications disabled", []),
{ok, State}
end;
interval = get_watchdog_interval()},
{ok, maybe_start_timer(State)};
{error, Reason} ->
?CRITICAL_MSG("Cannot open IPC socket: ~p", [Reason]),
{stop, Reason}
@@ -105,47 +96,48 @@ init(_Opts) ->
end.
-spec handle_call(term(), {pid(), term()}, state())
-> {reply, {error, badarg}, state(), watchdog_timeout()}.
-> {reply, {error, badarg}, state()}.
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{reply, {error, badarg}, State, get_timeout(State)}.
{reply, {error, badarg}, State}.
-spec handle_cast({notify, binary()} | term(), state())
-> {noreply, state(), watchdog_timeout()}.
-spec handle_cast({notify, binary()} | term(), state()) -> {noreply, state()}.
handle_cast({notify, Notification},
#state{destination = undefined} = State) ->
?DEBUG("No NOTIFY_SOCKET, dropping ~s notification", [Notification]),
{noreply, State, get_timeout(State)};
{noreply, State};
handle_cast({notify, Notification}, State) ->
try notify(State, Notification)
catch _:Err ->
?ERROR_MSG("Cannot send ~s notification: ~p", [Notification, Err])
end,
{noreply, State, get_timeout(State)};
{noreply, State};
handle_cast(Msg, State) ->
?ERROR_MSG("Got unexpected message: ~p", [Msg]),
{noreply, State, get_timeout(State)}.
{noreply, State}.
-spec handle_info(timeout | term(), state())
-> {noreply, state(), watchdog_timeout()}.
handle_info(timeout, #state{interval = Interval} = State)
-spec handle_info(ping_watchdog | term(), state()) -> {noreply, state()}.
handle_info(ping_watchdog, #state{interval = Interval} = State)
when is_integer(Interval), Interval > 0 ->
try notify(State, <<"WATCHDOG=1">>)
catch _:Err ->
?ERROR_MSG("Cannot ping watchdog: ~p", [Err])
end,
{noreply, set_last_ping(State), Interval};
{noreply, start_timer(State)};
handle_info(Info, State) ->
?ERROR_MSG("Got unexpected info: ~p", [Info]),
{noreply, State, get_timeout(State)}.
{noreply, State}.
-spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
terminate(Reason, #state{socket = undefined}) ->
terminate(Reason, #state{socket = Socket} = State) ->
?DEBUG("Terminating ~s (~p)", [?MODULE, Reason]),
ok;
terminate(Reason, #state{socket = Socket}) ->
?DEBUG("Closing socket and terminating ~s (~p)", [?MODULE, Reason]),
ok = gen_udp:close(Socket).
cancel_timer(State),
case Socket of
undefined ->
ok;
_Socket ->
gen_udp:close(Socket)
end.
-spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) ->
@@ -166,24 +158,24 @@ get_watchdog_interval() ->
undefined
end.
-spec get_timeout(state()) -> watchdog_timeout().
get_timeout(#state{interval = undefined}) ->
?DEBUG("Watchdog interval is undefined, hibernating", []),
hibernate;
get_timeout(#state{interval = Interval, last_ping = LastPing}) ->
case Interval - (erlang:monotonic_time(millisecond) - LastPing) of
Timeout when Timeout > 0 ->
?DEBUG("Calculated new timeout value: ~B", [Timeout]),
Timeout;
_ ->
?DEBUG("Calculated new timeout value: 1", []),
1
end.
-spec maybe_start_timer(state()) -> state().
maybe_start_timer(#state{interval = Interval} = State)
when is_integer(Interval), Interval > 0 ->
?INFO_MSG("Watchdog notifications enabled", []),
start_timer(State);
maybe_start_timer(State) ->
?INFO_MSG("Watchdog notifications disabled", []),
State.
-spec set_last_ping(state()) -> state().
set_last_ping(State) ->
LastPing = erlang:monotonic_time(millisecond),
State#state{last_ping = LastPing}.
-spec start_timer(state()) -> state().
start_timer(#state{interval = Interval} = State) ->
?DEBUG("Pinging watchdog in ~B milliseconds", [Interval]),
State#state{timer = erlang:send_after(Interval, self(), ping_watchdog)}.
-spec cancel_timer(state()) -> ok.
cancel_timer(#state{timer = Timer}) ->
?DEBUG("Cancelling watchdog timer", []),
misc:cancel_timer(Timer).
-spec notify(state(), binary()) -> ok.
notify(#state{socket = Socket, destination = Destination},
@@ -193,4 +185,5 @@ notify(#state{socket = Socket, destination = Destination},
-spec cast_notification(binary()) -> ok.
cast_notification(Notification) ->
?DEBUG("Closing NOTIFY_SOCKET", []),
gen_server:cast(?MODULE, {notify, Notification}).
+20 -5
View File
@@ -397,13 +397,15 @@ logo_fill() ->
%%%% process_admin
process_admin(global, #request{path = [], lang = Lang}, AJID) ->
MenuItems = get_menu_items(global, cluster, Lang, AJID, 0),
Disclaimer = maybe_disclaimer_not_admin(MenuItems, AJID, Lang),
make_xhtml((?H1GL((translate:translate(Lang, ?T("Administration"))), <<"">>,
<<"Contents">>))
++
++ Disclaimer ++
[?XE(<<"ul">>,
[?LI([?ACT(MIU, MIN)])
|| {MIU, MIN}
<- get_menu_items(global, cluster, Lang, AJID, 0)])],
<- MenuItems])],
global, Lang, AJID, 0);
process_admin(Host, #request{path = [], lang = Lang}, AJID) ->
make_xhtml([?XCT(<<"h1">>, ?T("Administration")),
@@ -573,14 +575,16 @@ term_to_id(T) -> base64:encode((term_to_binary(T))).
%%%% list_vhosts
list_vhosts(Lang, JID) ->
list_vhosts2(Lang, list_vhosts_allowed(JID)).
list_vhosts_allowed(JID) ->
Hosts = ejabberd_option:hosts(),
HostsAllowed = lists:filter(fun (Host) ->
lists:filter(fun (Host) ->
any_rules_allowed(Host,
[configure, webadmin_view],
JID)
end,
Hosts),
list_vhosts2(Lang, HostsAllowed).
Hosts).
list_vhosts2(Lang, Hosts) ->
SHosts = lists:sort(Hosts),
@@ -616,6 +620,17 @@ list_vhosts2(Lang, Hosts) ->
end,
SHosts)))])].
maybe_disclaimer_not_admin(MenuItems, AJID, Lang) ->
case {MenuItems, list_vhosts_allowed(AJID)} of
{[_], []} ->
[?XREST(?T("Apparently your account has no administration rights in this server. "
"Please check how to grant admin rights in: "
"https://docs.ejabberd.im/admin/installation/#administration-account"))
];
_ ->
[]
end.
%%%==================================
%%%% list_users
+111 -29
View File
@@ -33,6 +33,7 @@
installed_command/0, installed/0, installed/1,
install/1, uninstall/1, upgrade/0, upgrade/1, add_paths/0,
add_sources/1, add_sources/2, del_sources/1, modules_dir/0,
install_contrib_modules/2,
config_dir/0, get_commands_spec/0]).
-export([modules_configs/0, module_ebin_dir/1]).
-export([compile_erlang_file/2, compile_elixir_file/2]).
@@ -48,7 +49,7 @@
-include("translate.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-define(REPOS, "https://github.com/processone/ejabberd-contrib").
-define(REPOS, "git@github.com:processone/ejabberd-contrib.git").
-record(state, {}).
@@ -215,10 +216,13 @@ installed_command() ->
[short_spec(Item) || Item <- installed()].
install(Module) when is_atom(Module) ->
install(misc:atom_to_binary(Module));
install(misc:atom_to_binary(Module), undefined);
install(Package) when is_binary(Package) ->
install(Package, undefined).
install(Package, Config) when is_binary(Package) ->
Spec = [S || {Mod, S} <- available(), misc:atom_to_binary(Mod)==Package],
case {Spec, installed(Package), is_contrib_allowed()} of
case {Spec, installed(Package), is_contrib_allowed(Config)} of
{_, _, false} ->
{error, not_allowed};
{[], _, _} ->
@@ -227,13 +231,14 @@ install(Package) when is_binary(Package) ->
{error, conflict};
{[Attrs], _, _} ->
Module = misc:binary_to_atom(Package),
case compile_and_install(Module, Attrs) of
case compile_and_install(Module, Attrs, Config) of
ok ->
code:add_pathsz([module_ebin_dir(Module)|module_deps_dirs(Module)]),
ejabberd_config:reload(),
ejabberd_config_reload(Config),
copy_commit_json(Package, Attrs),
case erlang:function_exported(Module, post_install, 0) of
true -> Module:post_install();
ModuleRuntime = get_runtime_module_name(Module),
case erlang:function_exported(ModuleRuntime, post_install, 0) of
true -> ModuleRuntime:post_install();
_ -> ok
end;
Error ->
@@ -242,20 +247,29 @@ install(Package) when is_binary(Package) ->
end
end.
ejabberd_config_reload(Config) when is_list(Config) ->
%% Don't reload config when ejabberd is starting
%% because it will be reloaded after installing
%% all the external modules from install_contrib_modules
ok;
ejabberd_config_reload(undefined) ->
ejabberd_config:reload().
uninstall(Module) when is_atom(Module) ->
uninstall(misc:atom_to_binary(Module));
uninstall(Package) when is_binary(Package) ->
case installed(Package) of
true ->
Module = misc:binary_to_atom(Package),
case erlang:function_exported(Module, pre_uninstall, 0) of
true -> Module:pre_uninstall();
ModuleRuntime = get_runtime_module_name(Module),
case erlang:function_exported(ModuleRuntime, pre_uninstall, 0) of
true -> ModuleRuntime:pre_uninstall();
_ -> ok
end,
[catch gen_mod:stop_module(Host, Module)
[catch gen_mod:stop_module(Host, ModuleRuntime)
|| Host <- ejabberd_option:hosts()],
code:purge(Module),
code:delete(Module),
code:purge(ModuleRuntime),
code:delete(ModuleRuntime),
[code:del_path(PathDelete) || PathDelete <- [module_ebin_dir(Module)|module_deps_dirs(Module)]],
delete_path(module_lib_dir(Module)),
ejabberd_config:reload();
@@ -280,7 +294,7 @@ add_sources(Module, Path) when is_atom(Module), is_list(Path) ->
add_sources(Package, Path) when is_binary(Package), is_list(Path) ->
DestDir = sources_dir(),
RepDir = filename:join(DestDir, module_name(Path)),
delete_path(RepDir),
delete_path(RepDir, binary_to_list(Package)),
case filelib:ensure_dir(RepDir) of
ok ->
case {string:left(Path, 4), string:right(Path, 2)} of
@@ -343,6 +357,8 @@ geturl(Url) ->
case httpc:request(get, {Url, [UA]}, User, [{body_format, binary}], ext_mod) of
{ok, {{_, 200, _}, Headers, Response}} ->
{ok, Headers, Response};
{ok, {{_, 403, Reason}, _Headers, _Response}} ->
{error, Reason};
{ok, {{_, Code, _}, _Headers, Response}} ->
{error, {Code, Response}};
{error, Reason} ->
@@ -390,8 +406,9 @@ extract_github_master(Repos, DestDir) ->
case extract(zip, geturl(Url++"/archive/master.zip"), DestDir) of
ok ->
RepDir = filename:join(DestDir, module_name(Repos)),
file:rename(RepDir++"-master", RepDir),
maybe_write_commit_json(Url, RepDir);
RepDirSpec = filename:join(DestDir, module_spec_name(RepDir)),
file:rename(RepDir++"-master", RepDirSpec),
maybe_write_commit_json(Url, RepDirSpec);
Error ->
Error
end.
@@ -426,6 +443,9 @@ delete_path(Path) ->
file:delete(Path)
end.
delete_path(Path, Package) ->
delete_path(filename:join(filename:dirname(Path), Package)).
modules_dir() ->
DefaultDir = filename:join(getenv("HOME"), ".ejabberd-modules"),
getenv("CONTRIB_MODULES_PATH", DefaultDir).
@@ -464,6 +484,14 @@ module_src_dir(Package) ->
module_name(Id) ->
filename:basename(filename:rootname(Id)).
module_spec_name(Path) ->
case filelib:wildcard(filename:join(Path++"-master", "*.spec")) of
"" ->
module_name(Path);
ModuleName ->
filename:basename(ModuleName, ".spec")
end.
module(Id) ->
misc:binary_to_atom(iolist_to_binary(module_name(Id))).
@@ -483,7 +511,13 @@ modules_spec(Dir, Path) ->
short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) ->
{Module, proplists:get_value(summary, Attrs, "")}.
is_contrib_allowed() ->
is_contrib_allowed(Config) when is_list(Config) ->
case lists:keyfind(allow_contrib_modules, 1, Config) of
false -> true;
{_, false} -> false;
{_, true} -> true
end;
is_contrib_allowed(undefined) ->
ejabberd_option:allow_contrib_modules().
%% -- build functions
@@ -526,7 +560,7 @@ check_sources(Module) ->
_ -> {error, Result}
end.
compile_and_install(Module, Spec) ->
compile_and_install(Module, Spec, Config) ->
SrcDir = module_src_dir(Module),
LibDir = module_lib_dir(Module),
case filelib:is_dir(SrcDir) of
@@ -534,7 +568,7 @@ compile_and_install(Module, Spec) ->
case compile_deps(SrcDir) of
ok ->
case compile(SrcDir) of
ok -> install(Module, Spec, SrcDir, LibDir);
ok -> install(Module, Spec, SrcDir, LibDir, Config);
Error -> Error
end;
Error ->
@@ -543,7 +577,7 @@ compile_and_install(Module, Spec) ->
false ->
Path = proplists:get_value(url, Spec, ""),
case add_sources(Module, Path) of
ok -> compile_and_install(Module, Spec);
ok -> compile_and_install(Module, Spec, Config);
Error -> Error
end
end.
@@ -648,7 +682,7 @@ compile_elixir_file(_, File) ->
{error, {compilation_failed, File}}.
-endif.
install(Module, Spec, SrcDir, LibDir) ->
install(Module, Spec, SrcDir, LibDir, Config) ->
{ok, CurDir} = file:get_cwd(),
file:set_cwd(SrcDir),
Files1 = [{File, copy(File, filename:join(LibDir, File))}
@@ -660,7 +694,7 @@ install(Module, Spec, SrcDir, LibDir) ->
Errors = lists:dropwhile(fun({_, ok}) -> true;
(_) -> false
end, Files1++Files2++Files3),
inform_module_configuration(Module, LibDir, Files1),
inform_module_configuration(Module, LibDir, Files1, Config),
Result = case Errors of
[{F, {error, E}}|_] ->
{error, {F, E}};
@@ -672,24 +706,37 @@ install(Module, Spec, SrcDir, LibDir) ->
file:set_cwd(CurDir),
Result.
inform_module_configuration(Module, LibDir, Files1) ->
inform_module_configuration(Module, LibDir, Files1, Config) ->
Res = lists:filter(fun({[$c, $o, $n, $f |_], ok}) -> true;
(_) -> false
end, Files1),
case Res of
[{ConfigPath, ok}] ->
AlreadyConfigured = lists:keymember(Module, 1, get_modules(Config)),
case {Res, AlreadyConfigured} of
{[{ConfigPath, ok}], false} ->
FullConfigPath = filename:join(LibDir, ConfigPath),
io:format("Module ~p has been installed and started.~n"
"It's configured in the file:~n ~s~n"
"Configure the module in that file, or remove it~n"
"and configure in your main ejabberd.yml~n",
[Module, FullConfigPath]);
[] ->
{[{ConfigPath, ok}], true} ->
FullConfigPath = filename:join(LibDir, ConfigPath),
file:rename(FullConfigPath, FullConfigPath++".example"),
io:format("Module ~p has been installed and started.~n"
"The ~p configuration in your ejabberd.yml is used.~n",
[Module, Module]);
{[], _} ->
io:format("Module ~p has been installed.~n"
"Now you can configure it in your ejabberd.yml~n",
[Module])
end.
get_modules(Config) when is_list(Config) ->
{modules, Modules} = lists:keyfind(modules, 1, Config),
Modules;
get_modules(undefined) ->
ejabberd_config:get_option(modules).
%% -- minimalist rebar spec parser, only support git
fetch_rebar_deps(SrcDir) ->
@@ -766,10 +813,14 @@ maybe_write_commit_json(Url, RepDir) ->
write_commit_json(Url, RepDir) ->
Url2 = string_replace(Url, "https://github.com", "https://api.github.com/repos"),
BranchUrl = lists:flatten(Url2 ++ "/branches/master"),
{ok, _Headers, Body} = geturl(BranchUrl),
{ok, F} = file:open(filename:join(RepDir, "COMMIT.json"), [raw, write]),
file:write(F, Body),
file:close(F).
case geturl(BranchUrl) of
{ok, _Headers, Body} ->
{ok, F} = file:open(filename:join(RepDir, "COMMIT.json"), [raw, write]),
file:write(F, Body),
file:close(F);
{error, Reason} ->
Reason
end.
find_commit_json(Attrs) ->
{_, FromPath} = lists:keyfind(path, 1, Attrs),
@@ -988,6 +1039,26 @@ get_module_status(Module) ->
unknown
end.
%% When a module named mod_whatever in ejabberd-modules
%% is written in Elixir, its runtime name is 'Elixir.ModWhatever'
get_runtime_module_name(Module) ->
case is_elixir_module(Module) of
true -> elixir_module_name(Module);
false -> Module
end.
is_elixir_module(Module) ->
LibDir = module_src_dir(Module),
Lib = filename:join(LibDir, "lib"),
Src = filename:join(LibDir, "src"),
case {filelib:wildcard(Lib++"/*.{ex}"),
filelib:wildcard(Src++"/*.{erl}")} of
{[_ | _], []} ->
true;
{[], [_ | _]} ->
false
end.
%% Converts mod_some_thing to Elixir.ModSomeThing
elixir_module_name(ModAtom) ->
list_to_atom("Elixir." ++ elixir_module_name("_" ++ atom_to_list(ModAtom), [])).
@@ -1213,3 +1284,14 @@ list_modules_parse_uninstall(Query) ->
end,
installed()),
ok.
install_contrib_modules(Modules, Config) ->
lists:filter(fun(Module) ->
case install(misc:atom_to_binary(Module), Config) of
{error, conflict} ->
false;
ok ->
true
end
end,
Modules).
+68 -8
View File
@@ -49,6 +49,7 @@
-record(ejabberd_module,
{module_host = {undefined, <<"">>} :: {atom(), binary()},
opts = [] :: opts() | '_' | '$2',
registrations = [] :: [registration()],
order = 0 :: integer()}).
-type opts() :: #{atom() => term()}.
@@ -57,7 +58,17 @@
value => string() | binary()}.
-type opt_doc() :: {atom(), opt_desc()} | {atom(), opt_desc(), [opt_doc()]}.
-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
-type component() :: ejabberd_sm | ejabberd_local.
-type registration() ::
{hook, atom(), atom(), integer()} |
{hook, atom(), module(), atom(), integer()} |
{iq_handler, component(), binary(), atom()} |
{iq_handler, component(), binary(), module(), atom()}.
-export_type([registration/0]).
-callback start(binary(), opts()) ->
ok | {ok, pid()} |
{ok, [registration()]} | {error, term()}.
-callback stop(binary()) -> any().
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback mod_opt_type(atom()) -> econf:validator().
@@ -155,6 +166,10 @@ start_module(Host, Module, Opts, Order) ->
try case Module:start(Host, Opts) of
ok -> ok;
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
{ok, Registrations} when is_list(Registrations) ->
store_options(Host, Module, Opts, Registrations, Order),
add_registrations(Host, Module, Registrations),
ok;
Err ->
ets:delete(ejabberd_modules, {Module, Host}),
erlang:error({bad_return, Module, Err})
@@ -246,9 +261,21 @@ update_module(Host, Module, Opts) ->
-spec store_options(binary(), module(), opts(), integer()) -> true.
store_options(Host, Module, Opts, Order) ->
case ets:lookup(ejabberd_modules, {Module, Host}) of
[M] ->
store_options(
Host, Module, Opts, M#ejabberd_module.registrations, Order);
[] ->
store_options(Host, Module, Opts, [], Order)
end.
-spec store_options(binary(), module(), opts(), [registration()], integer()) -> true.
store_options(Host, Module, Opts, Registrations, Order) ->
ets:insert(ejabberd_modules,
#ejabberd_module{module_host = {Module, Host},
opts = Opts, order = Order}).
opts = Opts,
registrations = Registrations,
order = Order}).
maybe_halt_ejabberd() ->
case is_app_running(ejabberd) of
@@ -281,16 +308,21 @@ stop_modules(Host) ->
stop_module_keep_config(Host, Module)
end, Modules).
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
-spec stop_module(binary(), atom()) -> error | ok.
stop_module(Host, Module) ->
case stop_module_keep_config(Host, Module) of
error -> error;
ok -> ok
end.
stop_module_keep_config(Host, Module).
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
stop_module_keep_config(Host, Module) ->
?DEBUG("Stopping ~ts at ~ts", [Module, Host]),
Registrations =
case ets:lookup(ejabberd_modules, {Module, Host}) of
[M] ->
M#ejabberd_module.registrations;
[] ->
[]
end,
del_registrations(Host, Module, Registrations),
try Module:stop(Host) of
_ ->
ets:delete(ejabberd_modules, {Module, Host}),
@@ -303,6 +335,34 @@ stop_module_keep_config(Host, Module) ->
error
end.
-spec add_registrations(binary(), module(), [registration()]) -> ok.
add_registrations(Host, Module, Registrations) ->
lists:foreach(
fun({hook, Hook, Function, Seq}) ->
ejabberd_hooks:add(Hook, Host, Module, Function, Seq);
({hook, Hook, Module1, Function, Seq}) ->
ejabberd_hooks:add(Hook, Host, Module1, Function, Seq);
({iq_handler, Component, NS, Function}) ->
gen_iq_handler:add_iq_handler(
Component, Host, NS, Module, Function);
({iq_handler, Component, NS, Module1, Function}) ->
gen_iq_handler:add_iq_handler(
Component, Host, NS, Module1, Function)
end, Registrations).
-spec del_registrations(binary(), module(), [registration()]) -> ok.
del_registrations(Host, Module, Registrations) ->
lists:foreach(
fun({hook, Hook, Function, Seq}) ->
ejabberd_hooks:delete(Hook, Host, Module, Function, Seq);
({hook, Hook, Module1, Function, Seq}) ->
ejabberd_hooks:delete(Hook, Host, Module1, Function, Seq);
({iq_handler, Component, NS, _Function}) ->
gen_iq_handler:remove_iq_handler(Component, Host, NS);
({iq_handler, Component, NS, _Module, _Function}) ->
gen_iq_handler:remove_iq_handler(Component, Host, NS)
end, Registrations).
-spec get_opt(atom(), opts()) -> any().
get_opt(Opt, Opts) ->
maps:get(Opt, Opts).
@@ -431,7 +491,7 @@ is_equal_opt(Opt, NewOpts, OldOpts) ->
%%%===================================================================
-spec format_module_error(atom(), start | reload, non_neg_integer(), opts(),
error | exit | throw, any(),
[erlang:stack_item()]) -> iolist().
[tuple()]) -> iolist().
format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
case {Class, Reason} of
{error, {bad_return, Module, {error, _} = Err}} ->
+13 -42
View File
@@ -42,49 +42,20 @@
-include_lib("xmpp/include/xmpp.hrl").
-include("translate.hrl").
start(Host, _Opts) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_COMMANDS, ?MODULE, process_local_iq),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_COMMANDS, ?MODULE, process_sm_iq),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
get_local_identity, 99),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
get_local_features, 99),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
get_local_commands, 99),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 99),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 99),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_commands, 99),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
ping_item, 100),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
ping_command, 100).
start(_Host, _Opts) ->
{ok, [{iq_handler, ejabberd_local, ?NS_COMMANDS, process_local_iq},
{iq_handler, ejabberd_sm, ?NS_COMMANDS, process_sm_iq},
{hook, disco_local_identity, get_local_identity, 99},
{hook, disco_local_features, get_local_features, 99},
{hook, disco_local_items, get_local_commands, 99},
{hook, disco_sm_identity, get_sm_identity, 99},
{hook, disco_sm_features, get_sm_features, 99},
{hook, disco_sm_items, get_sm_commands, 99},
{hook, adhoc_local_items, ping_item, 100},
{hook, adhoc_local_commands, ping_command, 100}]}.
stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host,
?MODULE, ping_command, 100),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
ping_item, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
get_sm_commands, 99),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 99),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 99),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_commands, 99),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, get_local_features, 99),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, get_local_identity, 99),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_COMMANDS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_COMMANDS).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+38 -38
View File
@@ -169,10 +169,12 @@ get_commands_spec() ->
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
desc = "Delete users that didn't log in last days, or that never logged",
longdesc = "To protect admin accounts, configure this for example:\n"
"```\n"
"access_rules:\n"
" protect_old_users:\n"
" - allow: admin\n"
" - deny: all\n",
" - deny: all\n"
"```\n",
module = ?MODULE, function = delete_old_users,
args = [{days, integer}],
args_example = [30],
@@ -183,10 +185,12 @@ get_commands_spec() ->
#ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
desc = "Delete users that didn't log in last days in vhost, or that never logged",
longdesc = "To protect admin accounts, configure this for example:\n"
"```\n"
"access_rules:\n"
" delete_old_users:\n"
" - deny: admin\n"
" - allow: all\n",
" - allow: all\n"
"```\n",
module = ?MODULE, function = delete_old_users_vhost,
args = [{host, binary}, {days, integer}],
args_example = [<<"myserver.com">>, 30],
@@ -215,7 +219,8 @@ get_commands_spec() ->
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password_hash, tags = [accounts],
desc = "Check if the password hash is correct",
longdesc = "Allows hash methods from crypto application",
longdesc = "Allows hash methods from the Erlang/OTP "
"[crypto](https://www.erlang.org/doc/man/crypto) application.",
module = ?MODULE, function = check_password_hash,
args = [{user, binary}, {host, binary}, {passwordhash, binary},
{hashmethod, binary}],
@@ -390,14 +395,14 @@ get_commands_spec() ->
"and its presence (show and status message) "
"for a given user.",
longdesc =
"The 'jid' value contains the user jid "
"with resource.\nThe 'show' value contains "
"The `jid` value contains the user JID "
"with resource.\n\nThe `show` value contains "
"the user presence flag. It can take "
"limited values:\n - available\n - chat "
"(Free for chat)\n - away\n - dnd (Do "
"not disturb)\n - xa (Not available, "
"extended away)\n - unavailable (Not "
"connected)\n\n'status' is a free text "
"limited values:\n\n - `available`\n - `chat` "
"(Free for chat)\n - `away`\n - `dnd` (Do "
"not disturb)\n - `xa` (Not available, "
"extended away)\n - `unavailable` (Not "
"connected)\n\n`status` is a free text "
"defined by the user client.",
module = ?MODULE, function = get_presence,
args = [{user, binary}, {host, binary}],
@@ -421,8 +426,8 @@ get_commands_spec() ->
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
<<"available">>,<<"away">>,<<"BB">>, <<"7">>],
args_desc = ["User name", "Server name", "Resource",
"Type: available, error, probe...",
"Show: away, chat, dnd, xa.", "Status text",
"Type: `available`, `error`, `probe`...",
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
"Priority, provide this value as an integer"],
result = {res, rescode}},
@@ -485,7 +490,7 @@ get_commands_spec() ->
#ejabberd_commands{name = add_rosteritem, tags = [roster],
desc = "Add an item to a user's roster (supports ODBC)",
longdesc = "Group can be several groups separated by ; for example: \"g1;g2;g3\"",
longdesc = "Group can be several groups separated by `;` for example: `g1;g2;g3`",
module = ?MODULE, function = add_rosteritem,
args = [{localuser, binary}, {localhost, binary},
{user, binary}, {host, binary},
@@ -569,7 +574,11 @@ get_commands_spec() ->
]}}
}}},
#ejabberd_commands{name = get_roster, tags = [roster],
desc = "Get roster of a local user",
desc = "Get list of contacts in a local user roster",
longdesc =
"Subscription can be: \"none\", \"from\", \"to\", \"both\". "
"Pending can be: \"in\", \"out\", \"none\".",
note = "improved in 23.10",
policy = user,
module = ?MODULE, function = get_roster,
args = [],
@@ -578,8 +587,8 @@ get_commands_spec() ->
{jid, string},
{nick, string},
{subscription, string},
{ask, string},
{group, string}
{pending, string},
{groups, {list, {group, string}}}
]}}}}},
#ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user",
@@ -616,7 +625,7 @@ get_commands_spec() ->
#ejabberd_commands{name = get_last, tags = [last],
desc = "Get last activity information",
longdesc = "Timestamp is UTC and XEP-0082 format, for example: "
"2017-02-23T22:25:28.063062Z ONLINE",
"`2017-02-23T22:25:28.063062Z ONLINE`",
module = ?MODULE, function = get_last,
args = [{user, binary}, {host, binary}],
args_example = [<<"user1">>,<<"myserver.com">>],
@@ -630,7 +639,7 @@ get_commands_spec() ->
#ejabberd_commands{name = set_last, tags = [last],
desc = "Set last activity information",
longdesc = "Timestamp is the seconds since "
"1970-01-01 00:00:00 UTC, for example: date +%s",
"`1970-01-01 00:00:00 UTC`. For example value see `date +%s`",
module = ?MODULE, function = set_last,
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
@@ -657,11 +666,11 @@ get_commands_spec() ->
desc = "Create a Shared Roster Group",
longdesc = "If you want to specify several group "
"identifiers in the Display argument,\n"
"put \\ \" around the argument and\nseparate the "
"identifiers with \\ \\ n\n"
"put `\\ \"` around the argument and\nseparate the "
"identifiers with `\\ \\ n`\n"
"For example:\n"
" ejabberdctl srg_create group3 myserver.com "
"name desc \\\"group1\\\\ngroup2\\\"",
" `ejabberdctl srg_create group3 myserver.com "
"name desc \\\"group1\\\\ngroup2\\\"`",
note = "changed in 21.07",
module = ?MODULE, function = srg_create,
args = [{group, binary}, {host, binary},
@@ -734,7 +743,7 @@ get_commands_spec() ->
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
longdesc = "When sending a groupchat message to a MUC room, "
"FROM must be the full JID of a room occupant, "
"`from` must be the full JID of a room occupant, "
"or the bare JID of a MUC service admin, "
"or the bare JID of a MUC/Sub subscribed user.",
module = ?MODULE, function = send_message,
@@ -742,13 +751,13 @@ get_commands_spec() ->
{subject, binary}, {body, binary}],
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
<<"Restart">>, <<"In 5 minutes">>],
args_desc = ["Message type: normal, chat, headline, groupchat", "Sender JID",
args_desc = ["Message type: `normal`, `chat`, `headline`, `groupchat`", "Sender JID",
"Receiver JID", "Subject, or empty string", "Body"],
result = {res, rescode}},
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
desc = "Send a stanza from an existing C2S session",
longdesc = "USER@HOST/RESOURCE must be an existing C2S session."
" As an alternative, use send_stanza instead.",
longdesc = "`user`@`host`/`resource` must be an existing C2S session."
" As an alternative, use http://./#send-stanza[send_stanza] instead.",
module = ?MODULE, function = send_stanza_c2s,
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
@@ -1329,25 +1338,16 @@ get_roster(User, Server) ->
make_roster_xmlrpc(Items)
end.
%% Note: if a contact is in several groups, the contact is returned
%% several times, each one in a different group.
make_roster_xmlrpc(Roster) ->
lists:foldl(
fun(#roster_item{jid = JID, name = Nick, subscription = Sub, ask = Ask} = Item, Res) ->
lists:map(
fun(#roster_item{jid = JID, name = Nick, subscription = Sub, ask = Ask, groups = Groups}) ->
JIDS = jid:encode(JID),
Subs = atom_to_list(Sub),
Asks = atom_to_list(Ask),
Groups = case Item#roster_item.groups of
[] -> [<<>>];
Gs -> Gs
end,
ItemsX = [{JIDS, Nick, Subs, Asks, Group} || Group <- Groups],
ItemsX ++ Res
{JIDS, Nick, Subs, Asks, Groups}
end,
[],
Roster).
%%-----------------------------
%% Push Roster from file
%%-----------------------------
+18 -1
View File
@@ -40,9 +40,26 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"motd">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"xml">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
set_motd_users(LServer, USRs) ->
F = fun() ->
lists:foreach(
+7 -16
View File
@@ -43,23 +43,14 @@
%%%===================================================================
%%% API
%%%===================================================================
start(Host, _Opts) ->
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_convert, 30),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_publish, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50).
start(_Host, _Opts) ->
{ok, [{hook, pubsub_publish_item, pubsub_publish_item, 50},
{hook, vcard_iq_set, vcard_iq_convert, 30},
{hook, vcard_iq_set, vcard_iq_publish, 100},
{hook, disco_sm_features, get_sm_features, 50}]}.
stop(Host) ->
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_convert, 30),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_publish, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+6 -14
View File
@@ -45,21 +45,13 @@
%%%===================================================================
%%% Callbacks and hooks
%%%===================================================================
start(Host, _Opts) ->
ejabberd_hooks:add(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ejabberd_hooks:add(roster_in_subscription, Host,
?MODULE, filter_subscription, 25),
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
start(_Host, _Opts) ->
{ok, [{hook, user_receive_packet, filter_packet, 25},
{hook, roster_in_subscription, filter_subscription, 25},
{hook, offline_message_hook, filter_offline_msg, 25}]}.
stop(Host) ->
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ejabberd_hooks:delete(roster_in_subscription, Host,
?MODULE, filter_subscription, 25),
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+5 -7
View File
@@ -37,14 +37,12 @@
-include("mod_privacy.hrl").
-include("translate.hrl").
start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_BLOCKING, ?MODULE, process_iq).
start(_Host, _Opts) ->
{ok, [{hook, disco_local_features, disco_features, 50},
{iq_handler, ejabberd_sm, ?NS_BLOCKING, process_iq}]}.
stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+16
View File
@@ -37,6 +37,8 @@
%%% API
%%%===================================================================
init() ->
ejabberd_sql_schema:update_schema(
ejabberd_config:get_myname(), ?MODULE, schemas()),
Node = erlang:atom_to_binary(node(), latin1),
?DEBUG("Cleaning SQL 'bosh' table...", []),
case ejabberd_sql:sql_query(
@@ -48,6 +50,20 @@ init() ->
Err
end.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"bosh">>,
columns =
[#sql_column{name = <<"sid">>, type = text},
#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"pid">>, type = text}],
indices = [#sql_index{
columns = [<<"sid">>],
unique = true}]}]}].
open_session(SID, Pid) ->
PidS = misc:encode_pid(Pid),
Node = erlang:atom_to_binary(node(Pid), latin1),
+17 -1
View File
@@ -37,9 +37,25 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"caps_features">>,
columns =
[#sql_column{name = <<"node">>, type = text},
#sql_column{name = <<"subnode">>, type = text},
#sql_column{name = <<"feature">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"node">>, <<"subnode">>]}]}]}].
caps_read(LServer, {Node, SubNode}) ->
case ejabberd_sql:sql_query(
LServer,
+11 -18
View File
@@ -48,25 +48,18 @@
-type direction() :: sent | received.
-type c2s_state() :: ejabberd_c2s:state().
start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler).
start(_Host, _Opts) ->
{ok, [{hook, disco_local_features, disco_features, 50},
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
{hook, user_send_packet, user_send_packet, 89},
{hook, user_receive_packet, user_receive_packet, 89},
{hook, c2s_copy_session, c2s_copy_session, 50},
{hook, c2s_session_resumed, c2s_session_resumed, 50},
{hook, c2s_session_opened, c2s_session_opened, 50},
{iq_handler, ejabberd_sm, ?NS_CARBONS_2, iq_handler}]}.
stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+13 -43
View File
@@ -44,50 +44,20 @@
-include("translate.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
get_local_items, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
get_local_features, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
get_local_identity, 50),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_items, 50),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 50),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
adhoc_local_items, 50),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
adhoc_local_commands, 50),
ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE,
adhoc_sm_items, 50),
ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE,
adhoc_sm_commands, 50),
ok.
start(_Host, _Opts) ->
{ok, [{hook, disco_local_items, get_local_items, 50},
{hook, disco_local_features, get_local_features, 50},
{hook, disco_local_identity, get_local_identity, 50},
{hook, disco_sm_items, get_sm_items, 50},
{hook, disco_sm_features, get_sm_features, 50},
{hook, disco_sm_identity, get_sm_identity, 50},
{hook, adhoc_local_items, adhoc_local_items, 50},
{hook, adhoc_local_commands, adhoc_local_commands, 50},
{hook, adhoc_sm_items, adhoc_sm_items, 50},
{hook, adhoc_sm_commands, adhoc_sm_commands, 50}]}.
stop(Host) ->
ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE,
adhoc_sm_commands, 50),
ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE,
adhoc_sm_items, 50),
ejabberd_hooks:delete(adhoc_local_commands, Host,
?MODULE, adhoc_local_commands, 50),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
adhoc_local_items, 50),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
get_sm_items, 50),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, get_local_identity, 50),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, get_local_features, 50),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_items, 50).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+11 -47
View File
@@ -51,16 +51,6 @@
-export_type([features_acc/0, items_acc/0]).
start(Host, Opts) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS, ?MODULE,
process_local_iq_items),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO, ?MODULE,
process_local_iq_info),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS, ?MODULE, process_sm_iq_items),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO, ?MODULE, process_sm_iq_info),
catch ets:new(disco_extra_domains,
[named_table, ordered_set, public,
{heir, erlang:group_leader(), none}]),
@@ -69,45 +59,19 @@ start(Host, Opts) ->
register_extra_domain(Host, Domain)
end,
ExtraDomains),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
get_local_services, 100),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
get_local_features, 100),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
get_local_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info,
100),
ok.
{ok, [{iq_handler, ejabberd_local, ?NS_DISCO_ITEMS, process_local_iq_items},
{iq_handler, ejabberd_local, ?NS_DISCO_INFO, process_local_iq_info},
{iq_handler, ejabberd_sm, ?NS_DISCO_ITEMS, process_sm_iq_items},
{iq_handler, ejabberd_sm, ?NS_DISCO_INFO, process_sm_iq_info},
{hook, disco_local_items, get_local_services, 100},
{hook, disco_local_features, get_local_features, 100},
{hook, disco_local_identity, get_local_identity, 100},
{hook, disco_sm_items, get_sm_items, 100},
{hook, disco_sm_features, get_sm_features, 100},
{hook, disco_sm_identity, get_sm_identity, 100},
{hook, disco_info, get_info, 100}]}.
stop(Host) ->
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
get_sm_items, 100),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, get_local_identity, 100),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, get_local_features, 100),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_services, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
get_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO),
catch ets:match_delete(disco_extra_domains,
{{'_', Host}}),
ok.
+6 -29
View File
@@ -46,15 +46,14 @@
%%--------------------------------------------------------------------
%% gen_mod callbacks.
%%--------------------------------------------------------------------
-spec start(binary(), gen_mod:opts()) -> ok.
start(Host, _Opts) ->
register_iq_handlers(Host),
register_hooks(Host).
-spec start(binary(), gen_mod:opts()) -> {ok, [gen_mod:registration()]}.
start(_Host, _Opts) ->
{ok, [{iq_handler, ejabberd_local, ?NS_JIDPREP_0, process_iq},
{hook, disco_local_features, disco_local_features, 50}]}.
-spec stop(binary()) -> ok.
stop(Host) ->
unregister_hooks(Host),
unregister_iq_handlers(Host).
stop(_Host) ->
ok.
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(_Host, _NewOpts, _OldOpts) ->
@@ -88,19 +87,6 @@ mod_doc() ->
"be used to control who is allowed to use this "
"service. The default value is 'local'.")}}]}.
%%--------------------------------------------------------------------
%% Register/unregister hooks.
%%--------------------------------------------------------------------
-spec register_hooks(binary()) -> ok.
register_hooks(Host) ->
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_local_features, 50).
-spec unregister_hooks(binary()) -> ok.
unregister_hooks(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_local_features, 50).
%%--------------------------------------------------------------------
%% Service discovery.
%%--------------------------------------------------------------------
@@ -123,15 +109,6 @@ disco_local_features(Acc, _From, _To, _Node, _Lang) ->
%%--------------------------------------------------------------------
%% IQ handlers.
%%--------------------------------------------------------------------
-spec register_iq_handlers(binary()) -> ok.
register_iq_handlers(Host) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_JIDPREP_0, ?MODULE, process_iq).
-spec unregister_iq_handlers(binary()) -> ok.
unregister_iq_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_JIDPREP_0).
-spec process_iq(iq()) -> iq().
process_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+8 -25
View File
@@ -62,32 +62,15 @@ start(Host, Opts) ->
Mod = gen_mod:db_mod(Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_LAST, ?MODULE, process_sm_iq),
ejabberd_hooks:add(privacy_check_packet, Host, ?MODULE,
privacy_check_packet, 30),
ejabberd_hooks:add(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE,
on_presence_update, 50).
{ok, [{iq_handler, ejabberd_local, ?NS_LAST, process_local_iq},
{iq_handler, ejabberd_sm, ?NS_LAST, process_sm_iq},
{hook, privacy_check_packet, privacy_check_packet, 30},
{hook, register_user, register_user, 50},
{hook, remove_user, remove_user, 50},
{hook, unset_presence_hook, on_presence_update, 50}]}.
stop(Host) ->
ejabberd_hooks:delete(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
?MODULE, on_presence_update, 50),
ejabberd_hooks:delete(privacy_check_packet, Host, ?MODULE,
privacy_check_packet, 30),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host,
?NS_LAST),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
?NS_LAST).
stop(_Host) ->
ok.
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
+17 -1
View File
@@ -38,9 +38,25 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"last">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"seconds">>, type = text},
#sql_column{name = <<"state">>, type = text}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
get_last(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,
+5 -10
View File
@@ -37,17 +37,12 @@
%%%===================================================================
%%% API
%%%===================================================================
start(Host, _Opts) ->
ejabberd_hooks:add(c2s_unauthenticated_packet, Host, ?MODULE,
c2s_unauthenticated_packet, 50),
ejabberd_hooks:add(c2s_pre_auth_features, Host, ?MODULE,
c2s_stream_features, 50).
start(_Host, _Opts) ->
{ok, [{hook, c2s_unauthenticated_packet, c2s_unauthenticated_packet, 50},
{hook, c2s_pre_auth_features, c2s_stream_features, 50}]}.
stop(Host) ->
ejabberd_hooks:delete(c2s_unauthenticated_packet, Host, ?MODULE,
c2s_unauthenticated_packet, 50),
ejabberd_hooks:delete(c2s_pre_auth_features, Host, ?MODULE,
c2s_stream_features, 50).
stop(_Host) ->
ok.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
+13 -7
View File
@@ -354,11 +354,15 @@ remove_mam_for_user_with_peer(User, Server, Peer) ->
{error, <<"Invalid peer JID">>}
end.
-spec remove_message_from_archive(User :: binary(), Server :: binary(), StanzaId :: integer()) ->
-spec remove_message_from_archive(
User :: binary() | {User :: binary(), Host :: binary()},
Server :: binary(), StanzaId :: integer()) ->
ok | {error, binary()}.
remove_message_from_archive(User, Server, StanzaId) ->
remove_message_from_archive(User, Server, StanzaId) when is_binary(User) ->
remove_message_from_archive({User, Server}, Server, StanzaId);
remove_message_from_archive({User, Host}, Server, StanzaId) ->
Mod = gen_mod:db_mod(Server, ?MODULE),
case Mod:remove_from_archive(User, Server, StanzaId) of
case Mod:remove_from_archive(User, Host, StanzaId) of
ok ->
ok;
{error, Bin} when is_binary(Bin) ->
@@ -461,6 +465,8 @@ offline_message({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
-spec muc_filter_message(message(), mod_muc_room:state(),
binary()) -> message().
muc_filter_message(#message{meta = #{mam_ignore := true}} = Pkt, _MUCState, _FromNick) ->
Pkt;
muc_filter_message(#message{from = From} = Pkt,
#state{config = Config, jid = RoomJID} = MUCState,
FromNick) ->
@@ -1464,9 +1470,9 @@ get_commands_spec() ->
[#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
desc = "Delete MAM messages older than DAYS",
longdesc = "Valid message TYPEs: "
"\"chat\", \"groupchat\", \"all\".",
"`chat`, `groupchat`, `all`.",
module = ?MODULE, function = delete_old_messages,
args_desc = ["Type of messages to delete (chat, groupchat, all)",
args_desc = ["Type of messages to delete (`chat`, `groupchat`, `all`)",
"Days to keep messages"],
args_example = [<<"all">>, 31],
args = [{type, binary}, {days, integer}],
@@ -1475,10 +1481,10 @@ get_commands_spec() ->
desc = "Delete MAM messages older than DAYS",
note = "added in 22.05",
longdesc = "Valid message TYPEs: "
"\"chat\", \"groupchat\", \"all\".",
"`chat`, `groupchat`, `all`.",
module = ?MODULE, function = delete_old_messages_batch,
args_desc = ["Name of host where messages should be deleted",
"Type of messages to delete (chat, groupchat, all)",
"Type of messages to delete (`chat`, `groupchat`, `all`)",
"Days to keep messages",
"Number of messages to delete per batch",
"Desired rate of messages to delete per minute"],
+51 -1
View File
@@ -43,9 +43,59 @@
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, _Opts) ->
init(Host, _Opts) ->
ejabberd_sql_schema:update_schema(Host, ?MODULE, schemas()),
ok.
schemas() ->
[#sql_schema{
version = 1,
tables =
[#sql_table{
name = <<"archive">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"timestamp">>, type = bigint},
#sql_column{name = <<"peer">>, type = text},
#sql_column{name = <<"bare_peer">>, type = text},
#sql_column{name = <<"xml">>, type = {text, big}},
#sql_column{name = <<"txt">>, type = {text, big}},
#sql_column{name = <<"id">>, type = bigserial},
#sql_column{name = <<"kind">>, type = {text, 10}},
#sql_column{name = <<"nick">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"timestamp">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"username">>, <<"bare_peer">>]},
#sql_index{
columns = [<<"server_host">>, <<"timestamp">>]}
],
post_create =
fun(mysql, _) ->
ejabberd_sql:sql_query_t(
<<"CREATE FULLTEXT INDEX i_archive_txt ON archive(txt);">>);
(_, _) ->
ok
end},
#sql_table{
name = <<"archive_prefs">>,
columns =
[#sql_column{name = <<"username">>, type = text},
#sql_column{name = <<"server_host">>, type = text},
#sql_column{name = <<"def">>, type = text},
#sql_column{name = <<"always">>, type = text},
#sql_column{name = <<"never">>, type = text},
#sql_column{name = <<"created_at">>, type = timestamp,
default = true}],
indices = [#sql_index{
columns = [<<"server_host">>, <<"username">>],
unique = true}]}]}].
remove_user(LUser, LServer) ->
ejabberd_sql:sql_query(
LServer,

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